Приватные методы для отладки

| Комментарии

Наверняка уже все знают, что у UIView есть полезный метод recursiveDescription. Вызов которого, вернет строку с описанием всей иерархии вью.

1
2
3
4
5
(lldb) po [[self view] recursiveDescription]
<UIView: 0x6a107c0; frame = (0 20; 320 460); autoresize = W+H; layer = […]
   | <UIRoundedRectButton: 0x6a103e0; frame = (124 196; 72 37); opaque = NO; […]
   |    | <UIButtonLabel: 0x6a117b0; frame = (19 8; 34 21); text = 'Test'; […]
   .....

Еще, многие знают, что при помощи рантайма, можно получить список переменных/методов/свойств/протоколов/… класса. И обычно для этих целей пишут вспомогательный код. Но оказывается, все уже давно написано сотрудниками Apple. В UIKit.framework реализована категория с именем IvarDescription.

1
2
3
4
5
6
7
@interface NSObject (IvarDescription)
- (id)_shortMethodDescription;
- (id)_methodDescription;
- (id)__methodDescriptionForClass:(Class)arg1;
- (id)_ivarDescription;
- (id)__ivarDescriptionForClass:(Class)arg1;
@end

Названия метдов говорят за себя. Но давайте посмотрим, что же они возвращают.

Полезные шорткаты для iTerm2

| Комментарии

Очистить строку ввода можно нажатием Ctrl+U. Вернуть удаленный текст обратно Ctrl+Y.

Чтобы добавить навигацию по строке, надо открыть настройки и добавить ярлыки с Action равным Send Escape Sequence.

Переход в начало строки:

Keyboard Shortcut: ⌘←
Esc+: [H

Переход в конец строки:

Keyboard Shortcut: ⌘→
Esc+: [F

Переход в начало строки:

Keyboard Shortcut: ⌥←
Esc+: b

Переход в начало строки:

Keyboard Shortcut: ⌥→  
Esc+: f

Правильный KVO

| Комментарии

Для реализации KVO, объект должен реализовать неформальный протокол NSKeyValueObserving. NSObject уже предоставляет реализацию этого протокола, поэтому разработчику нужно лишь грамотно использовать готовый механизм.

Вроде как в KVO нет ничего сложного.

Чтобы подписаться надо вызвать:

1
- (void)addObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context

Чтобы отписаться:

1
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context

Ну и остается только правильно реализовать метод в который приходят уведомления:

1
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

Большинство разработчиков умеют правильно подписываться/отписываться, но с observeValueForKeyPath еще возникают проблемы. Вот примеры неправильной реалзиции этого метода из реальных проектов:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
  if (context != (__bridge void *)kActionLoadingStateObservationContext)
    return;
  NSUInteger index = [self.actions indexOfObject:object];
  NSAssert(index != NSNotFound, @"Can not found action %@ in the current actions", object);
  // update loading state  
  YMTAction *action = self.actions[index];
  YMTActionView *actionView = self.subviews[index];
  actionView.loading = action.loading;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
  if (object == self.webView.scrollView && [keyPath isEqualToString:@"contentSize"]) {
    self.webViewHeightConstraint.constant = [change[NSKeyValueChangeNewKey] CGSizeValue].height;
  }
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  if ([keyPath isEqual:@keypath(YRCachedSettings.new, selectedSuburbanZone)]) {
    [self zoneDidChange];
  }
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
  [self reloadData];
}

Главная проблема у всех показанных реализаций, это отсутствие вызова [super observeValueForKeyPath:keyPath:change:context:]. Многие забывают о том, что базовый класс тоже может использовать KVO. Порой даже не забывают, а сознательно опускают вызов метода, так как, когда наткнулись на то, что базовы класс кинул NSInternalInconsistencyException.

Собсвтенно, чтобы не было проблем с базовым классом, [super observeValueForKeyPath:keyPath:change:context:] надо вызвать не при каждом уведомление, а только при тех, на которые вы не подписывались. Чтобы отличить свои уведомления от чужих (принадлежащих базовому классу) надо использовать контекст.

Вот пример правильного observeValueForKeyPath:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(__unused NSDictionary *)change context:(void *)context {
   if (context == &ja_kvoContext) {
     if ([keyPath isEqualToString:@"view"]) {
       if (self.centerPanel.isViewLoaded && self.recognizesPanGesture) {
         [self _addPanGestureToView:self.centerPanel.view];
       }
     } else if ([keyPath isEqualToString:@"viewControllers"] && object == self.centerPanel) {
       // view controllers have changed, need to replace the button  
       [self _placeButtonForLeftPanel];
     }
   } else {
     [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
   }
 }

Билд без Codesign

| Комментарии

Если попытаться собрать приложение с такой конфигурацией, то будет ошбика:

1
CodeSign error: code signing is required for product type 'Application'  

Чтобы xCode собирал билды без подписи, надо открыть один из следующих файлов в зависимости от Deployment Target:

1
2
3
4
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.1.sdk/SDKSettings.plist  
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator6.1.sdk/SDKSettings.plist  
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/SDKSettings.plist  
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.1.sdk/SDKSettings.plist  

И поставить ключ CODE_SIGNING_REQUIRED в NO.

Теперь можно собрать бинарник для которого команда “codesign —display —verbose=4 SomeApp” выведет:

1
SomeApp: code object is not signed at all