Question

This is a small detail but everytime I lazy load something I get caught up on it. Are both of these methods acceptable? Is either better? Assume that the variable has the retain property.

Method #1

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

Method #2

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

First, I'm not sure if it's OK to access another accessor function within an accessor (don't see why not, though). But it seems like setting the class variable without going through the setter could be equally bad if the setter does something special (or if the property is changed to something besides retain and the getter isn't checked).

Was it helpful?

Solution

Both are actually quite fragile and not at all identical, depending on what clients of the class are doing. Making them identical is easy enough -- see below -- but making it less fragile is harder. Such is the price of lazy initialization (and why I generally try to avoid lazy initialization in this fashion, preferring to treat initialization of subsystems as a part of overall application state management).

With #1, you are avoiding the setter and, thus, anything observing the change won't see the change. By "observing", I'm specifically referring to key-value observation (including Cocoa Bindings, which uses KVO to update the UI automatically).

With #2, you will trigger the change notification, updating the UI and otherwise exactly as if the setter was called.

In both cases, you have a potential for infinite recursion if the initialization of the object calls the getter. That includes if any observer asks for the old value as a part of the change notification. Don't do that.

If you are going to use either method, consider carefully the consequences. One has the potential to leave the app in an inconsistent state because a state change of a property did not notify and the other has the potential for deadlock.

Better to avoid the issue entirely. See below.


Consider (garbage collection on, standard Cocoa command line tool:

#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;
}

This does not hang. It spews:

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;
}

If you were to add NSKeyValueObservingOptionOld to the list of options for observation, it very much does hang.

Getting back to a comment I made earlier; the best solution is to not do lazy initialization as a part of your getter/setter. It is too fine grained. You are far better off managing your object graph state at a higher level and, as a part of that, have a state transition that is basically of the "Yo! I'm going to use this subsystem now! Warm that bad boy up!" that does the lazy initialization.

OTHER TIPS

Those methods are never identical. The first one is right, while the second one is wrong! A getter may never call will/didChangeValueForKey: and therefore also not the setter. This will lead to infinite recursion if that property is observed.

And besides, there is no state change to observe when the member is initialized. You ask your object for the theObject and you get it. When this gets created is an implementation detail and no concern to the outside world.

If you know the property setter method is a standard retaining setter, they're identical. If not, you need to decide whether the setter's other behavior should be invoked during that operation. If you don't know, it's safest to use the setter, since its behavior may be important. Don't sweat it.

Both of those are basically identical, its really just up to you to choose which one is best for your case. You already really described the pros/cons about using the property syntax.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top