8

Это небольшая деталь, но каждый раз, когда я ленивую загрузку, я нахожусь на ней. Оба метода приемлемы? Или лучше? Предположим, что переменная имеет свойство сохранения.Lazy Loading in Objective-C - Должен ли я вызвать сеттер из приемника?

Способ № 1

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

Способ № 2

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

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

ответ

17

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

С # 1 вы избегаете настройки, и, таким образом, что-либо, наблюдающее изменение, не увидит изменения. «Наблюдая», я специально ссылаюсь на наблюдение за ключевыми значениями (включая привязки Cocoa, которые используют 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 к списку вариантов наблюдения, это очень зависает.

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

+0

Это правильный ответ, на мой взгляд. –

+1

На самом деле нет изменений, которые следует соблюдать при ленивой инициализации объекта, поэтому этот ответ ** неверен. – Sven

+0

А? Является ли оно лениво инициализированным или нет, не имеет значения. Если я установил KVO-наблюдение «объекта» и поместил 'self.theObject = ...' в приемник, то это наблюдение KV ** будет срабатывать **. «Ленивая инициализация» - это всего лишь название шаблона; ни компилятор, ни среда выполнения ничего не знают об этом. – bbum

0

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

+0

Нет, они не являются. # 2 неверно! – Sven

1

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

+0

Это может быть то же самое с точки зрения управления памятью. Но сеттеры делают больше, чем просто заботятся об управлении памятью, есть и KVO, о которых нужно думать. И это делает метод № 2 неправильным! – Sven

3

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

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

+0

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

+0

Другими словами, оба метода ошибочны .... – bbum

+1

Нет, настройка привязки всегда будет называться получателем. Невозможно заставить наблюдателя войти в противоречивое состояние. – Sven