Ленивая загрузка в Objective-C - я должен вызвать сеттер из геттера? - PullRequest
8 голосов
/ 14 сентября 2010

Это небольшая деталь, но каждый раз, когда я лениво загружаю что-то, я зацикливаюсь на этом.Являются ли оба эти метода приемлемыми?Или лучше?Предположим, что переменная имеет свойство retain.

Метод # 1

(AnObject *)theObject{
    if (theObject == nil){
        theObject = [[AnObject createAnAutoreleasedObject] retain];
    }
    return theObject;
}

Метод # 2

(AnObject *)theObject{
    if (theObject == nil){
        self.theObject = [AnObject createAnAutoreleasedObject];
    }
    return theObject;
}

Во-первых, я не уверен, что это нормально дляполучить доступ к другой функции доступа в рамках средства доступа (хотя, не вижу почему).Но кажется, что установка переменной класса без прохождения через установщик может быть в равной степени плохой, если установщик делает что-то особенное (или если свойство изменяется на что-то кроме сохранения, а получатель не проверяется).

Ответы [ 4 ]

17 голосов
/ 14 сентября 2010

И то, и другое на самом деле довольно хрупко и совсем не идентично, в зависимости от того, что делают клиенты класса. Сделать их идентичными достаточно легко - см. Ниже - но сделать их менее хрупкими сложнее. Такова цена ленивой инициализации (и поэтому я обычно стараюсь избегать ленивой инициализации таким образом, предпочитая рассматривать инициализацию подсистем как часть общего управления состоянием приложения).

С # 1 вы избегаете установки и, следовательно, все, что наблюдает за изменением, не увидит изменения. Под «наблюдением» я конкретно имею в виду наблюдение значения ключа (включая привязки Какао, которые используют KVO для автоматического обновления пользовательского интерфейса).

С помощью # 2 вы будете запускать уведомление об изменении, обновляя пользовательский интерфейс, а в остальном точно так же, как если бы был вызван сеттер.

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

Если вы собираетесь использовать любой из этих методов, тщательно обдумайте последствия. Один из них может оставить приложение в несогласованном состоянии, так как об изменении состояния свойства не было сообщено, а другой потенциально может зайти в тупик.

Лучше избегать этой проблемы полностью. Смотри ниже.

<Ч />

Рассмотрим (сборка мусора, стандартный инструмент командной строки Какао:

#import <Foundation/Foundation.h>

@interface Foo : NSObject
{
    NSString *bar;
}
@property(nonatomic, retain) NSString *bar;
@end
@implementation Foo
- (NSString *) bar
{
    if (!bar) {
        NSLog(@"[%@ %@] lazy setting", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
        [self willChangeValueForKey: @"bar"];
        bar = @"lazy value";
        [self didChangeValueForKey: @"bar"];
    }
    return bar;
}

- (void) setBar: (NSString *) aString
{
    NSLog(@"[%@ %@] setting value %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), aString);
    bar = aString;
}
@end

@interface Bar:NSObject
@end
@implementation Bar
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
{
    NSLog(@"[%@ %@] %@ changed\n\tchange:%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), keyPath, change);
}
@end

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    Foo *foo = [Foo new];
    Bar *observer = [Bar new];
    CFRetain(observer);
    [foo addObserver:observer forKeyPath:@"bar"
             options: NSKeyValueObservingOptionPrior | NSKeyValueObservingOptionNew
             context:NULL];
    foo.bar;
    foo.bar = @"baz";
    CFRelease(observer);

    [pool drain];
    return 0;
}

Это не зависает. Извергает:

2010-09-15 12:29:18.377 foobar[27795:903] [Foo bar] lazy setting
2010-09-15 12:29:18.396 foobar[27795:903] [Bar observeValueForKeyPath:ofObject:change:context:] bar changed
    change:{
    kind = 1;
    notificationIsPrior = 1;
}
2010-09-15 12:29:18.397 foobar[27795:903] [Bar observeValueForKeyPath:ofObject:change:context:] bar changed
    change:{
    kind = 1;
    new = "lazy value";
}
2010-09-15 12:29:18.400 foobar[27795:903] [Bar observeValueForKeyPath:ofObject:change:context:] bar changed
    change:{
    kind = 1;
    notificationIsPrior = 1;
}
2010-09-15 12:29:18.400 foobar[27795:903] [Foo setBar:] setting value baz
2010-09-15 12:29:18.401 foobar[27795:903] [Bar observeValueForKeyPath:ofObject:change:context:] bar changed
    change:{
    kind = 1;
    new = baz;
}

Если вы добавите NSKeyValueObservingOptionOld в список опций для наблюдения, он очень сильно зависнет.

Возвращаясь к комментарию, который я сделал ранее; лучшее решение - не выполнять ленивую инициализацию как часть вашего метода получения / установки . Это слишком мелкозернистый. Вы намного лучше управляете своим состоянием графов объектов на более высоком уровне и, как часть этого, у вас есть переход состояния, который в основном имеет вид "Йо! Я собираюсь использовать эту подсистему сейчас! Разогреть этого плохого парня! " что делает ленивую инициализацию.

3 голосов
/ 15 сентября 2010

Эти методы никогда не бывают идентичными. Первый правильный, а второй неправильный ! Получатель может никогда не вызвать will/didChangeValueForKey: и, следовательно, также не установщик. Это приведет к бесконечной рекурсии, если это свойство соблюдается.

И, кроме того, нет изменений состояния, чтобы наблюдать, когда элемент инициализируется. Вы просите у своего объекта theObject и получаете его. Когда это создается, это деталь реализации и не имеет отношения к внешнему миру.

1 голос
/ 14 сентября 2010

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

0 голосов
/ 14 сентября 2010

Оба из них в основном идентичны, вам решать, какой из них лучше всего подходит для вашего случая.Вы уже действительно описали все за и против использования синтаксиса свойств.

...