Objective-C: Правильная обработка общих свойств между подклассами NSControl и NSActionCell?

Одна из вещей, в которой я всегда был неуверен, - это как правильно обрабатывать связь между пользовательским подклассом NSControl и подклассом NSCell. На протяжении моего знакомства с Какао я видел, как он несколько раз упоминал, как родительский элемент управления предоставляет многие из тех же методов, средств доступа и мутаторов, что и реализация дочерней ячейки / ячеек. Например, классы NSControl и NSCell имеют -isEnabled и -setEnabled: в своих файлах заголовков:

NSControl.h

- (BOOL)isEnabled;
- (void)setEnabled:(BOOL)flag;

NSCell.h

- (BOOL)isEnabled;
- (void)setEnabled:(BOOL)flag;

Я понимаю, что класс NSControl предоставляет методы «прикрытия» для большинства свойств, найденных в NSCell. Что меня больше интересует, так это то, как они реализованы? Или, что еще лучше, как реализовать общие свойства своих собственных подклассов? Очевидно, что только инженеры Apple действительно знают, что происходит внутри их фреймворков, но я подумал, что, может быть, кто-нибудь сможет пролить свет на лучший способ имитировать подход метода обложек Apple в красивой и понятной форме.

Я не умею объяснять, поэтому приведу пример того, о чем я говорю. Скажем, я разделил NSControl на подклассы так:

BSDToggleSwitch.h

#import "BSDToggleSwitchCell.h"

@interface BSDToggleSwitch : NSControl

@property (nonatomic, strong) BSDToggleSwitchCell *cell;
@property (nonatomic, assign) BOOL sharedProp;

@end

И я разделил NSActionCell на подклассы:

BSDToggleSwitchCell.h

#import "BSDToggleSwitch.h"

@interface BSDToggleSwitchCell : NSActionCell

@property (nonatomic, weak) BSDToggleSwitch *controlView;
@property (nonatomic, assign) BOOL sharedProp;

@end

Как видите, у них обоих есть общее свойство под названием sharedProp.

У меня такой вопрос: каков стандартный способ эффективной синхронизации общего свойства между элементом управления и ячейкой? Это может показаться субъективным вопросом, и я полагаю, что это так, но я хотел бы думать, что в большинстве случаев существует «лучший способ» сделать это.

Раньше я использовал всевозможные методы, но я хотел бы сузить круг способов обработки и использовать только те методы, которые обеспечивают наилучшую целостность данных с наименьшими накладными расходами. Стоит ли использовать привязки? А как насчет реализации пользовательских мутаторов, которые вызывают метод сопоставления своих аналогов? КВО? Я безнадежный?

Вот некоторые из вещей, которые я делал в прошлом (некоторые или все из которых могут быть совершенно нелепыми или откровенно неправильными):

  1. Привязки какао - я бы просто привязал свойство элемента управления к свойству ячейки (или наоборот):

    [self bind:@"sharedProp" toObject:self.cell withKeyPath:@"sharedProp" options:nil];
    

    Это кажется довольно хорошим подходом, но к какому объекту вы бы привязались / с какого? Я прочитал всю документацию KVO / KVC / Bindings, но я никогда особо не осознавал важность направленности привязки, когда свойство должно быть одинаковым в любом случае. Есть ли какое-то общее правило?

  2. Отправлять сообщения от мутаторов - я бы отправил ячейке сообщение от мутатора элемента управления:

    - (void)setSharedProp:(BOOL)sharedProp
    {
        if ( sharedProp == _sharedProp )
            return;
    
        _sharedProp = sharedProp;
    
        [self.cell setSharedProp:sharedProp];
    }
    

    Тогда я бы сделал то же самое в реализации ячейки:

    - (void)setSharedProp:(BOOL)sharedProp
    {
        if ( sharedProp == _sharedProp )
            return;
    
        _sharedProp = sharedProp;
    
        [self.controlView setSharedProp:sharedProp];
    }
    

    Это тоже кажется довольно разумным, но также более подверженным ошибкам. Если один объект отправляет сообщение другому без проверки значения, бесконечный цикл может возникнуть довольно легко, верно? В приведенных выше примерах я добавил проверки по этой причине, но я уверен, что есть способ получше.

  3. Наблюдать и сообщать - я бы заметил изменения свойств в реализации каждого объекта:

    static void * const BSDPropertySyncContext = @"BSDPropertySyncContext";
    
    - (void)observeValueForKeyPath:(NSString *)keyPath 
                          ofObject:(id)object 
                            change:(NSDictionary *)change 
                           context:(void *)context
    {
        if ( context == BSDPropertySyncContext && [keyPath isEqualToString:@"sharedProp"] ) {
    
            BOOL newValue = [[change objectForKey:NSKeyValueChangeNewKey] boolValue];
    
            if ( newValue != self.sharedProp ) {
                [self setSharedProp:newValue];
            }
    
        } else {
            [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        }
    }
    

    Еще раз, это кажется выполнимым, но мне не нравится писать оператор if для каждого отдельного общего свойства. Помимо наблюдений, я также отправлял уведомления, но, поскольку взаимосвязь между ячейками управления примерно такая же «один-к-одному», насколько это возможно (сладкая игра слов), это просто кажется глупым.

Опять же, я знаю, что это немного субъективно, но я бы очень признателен за некоторые рекомендации. Я уже некоторое время изучаю Cocoa / Objective-C, и это беспокоило меня с самого начала. Мне действительно может помочь знание того, как другие обрабатывают синхронизацию свойств между элементами управления и ячейками!

Спасибо!


person Ben Stock    schedule 17.02.2014    source источник


Ответы (1)


Во-первых, обратите внимание, что NSCell в основном существует из-за проблем с производительностью со времен NeXT. Переход от NSCell происходит медленно, и вам, как правило, не следует создавать новые, если вам не нужно взаимодействовать с чем-то, что требует их (например, работа с NSMatrix или если вы делаете что-то, что очень похоже на NSMatrix). Обратите внимание на изменения в NSTableView с 10.7, чтобы преуменьшить значение ячеек, и на то, что NSCollectionViewItem является полнофункциональным контроллером. Теперь у нас есть вычислительная мощность, чтобы просто использовать представления большую часть времени без необходимости NSCell.

Тем не менее, обычно элемент управления просто пересылает сообщения в ячейку. Например:

- (BOOL)sharedProp {
  return self.cell.sharedProp;
}

- (void)setSharedProp:(BOOL)sharedProp {
  [self.cell setSharedProp:sharedProp];
}

Если KVO вызывает беспокойство, вы все равно можете подключить его к keyPathsForValuesAffectingValueForKey: (и его родственникам). Например, вы можете сделать что-то вроде:

- (NSSet *)keyPathsForValuesAffectingSharedProp {
   return [NSSet setWithObject:@"cell.sharedProp"];
}

Это должно позволить вам наблюдать sharedProp и прозрачно перенаправлять изменения на cell.sharedProp.

person Rob Napier    schedule 18.02.2014
comment
Интересный! Я заметил миграцию от клеток, но никогда не знал, что за ними стоит. Мне всегда было интересно, почему класс NSCell вообще существует. Я читал, что это представления без всех накладных расходов, которые связаны с обычным представлением, но поскольку все больше и больше перемещается на графический процессор, я не мог понять, почему они все еще используются. Теперь я знаю! Спасибо за урок. Но у меня есть один вопрос. В вашем примере похоже, что ячейка пересылает сообщение элементу управления, но вы говорите, что это наоборот. Я просто неправильно это читаю? - person Ben Stock; 19.02.2014
comment
Хорошо, прочитав ваш пример еще несколько раз, я думаю, что понимаю, что вы имеете в виду. Единственное место, где ивар действительно существует, - это элемент управления, а не наличие по одному в каждом из объектов. В этом почти слишком много смысла. Наверное, поэтому я об этом не подумал. Ха! Спасибо чувак. - person Ben Stock; 19.02.2014
comment
Я написала наоборот. Обычно в ячейке есть ivar. Одно замечание: типичное использование NSCell - это случаи, когда он повторно используется несколькими представлениями. В таких случаях такой подход не обязательно имеет смысл. Для правильного взаимодействия вызывающему абоненту необходимо иметь представление об архитектуре ячейки. - person Rob Napier; 19.02.2014
comment
Я понял. Я работал над своим классом и благодаря вашему совету полностью его реорганизовал. Теперь все инкапсулировано в один подкласс NSView, и, если вы спросите меня, он кажется намного чище. Независимо от того, правильный ли это путь, мне определенно нравится иметь дело только с одним представлением; это намного более управляемо. Также спасибо, что напомнили мне о методе -keyPathsForValuesAffecting<#Property#>. Вы действительно помогли молодому стрелку, и я ценю это! - person Ben Stock; 20.02.2014