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.