Ленивая загрузка в Objective -C - я должен позвонить сеттеру изнутри Getter?

StackOverflow https://stackoverflow.com/questions/3712275

Вопрос

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

Метод № 1

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

Метод № 2

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

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

Это было полезно?

Решение

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

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

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

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

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

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


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

#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 В список вариантов наблюдения он очень сильно видит.

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

Другие советы

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top