Saturday, March 23, 13

Report 4 Downloads 29 Views
Key-Value Observing With Blocks

Saturday, March 23, 13

What is key-value observing? • Allows you to be notified whenever a

particular property of an object changes

• Used heavily by bindings on OS X • Can also be used to manually observe objects

Saturday, March 23, 13

KVO API •

- (void)addObserver:(id)observer forKeyPath:(NSString*)keyPath options:(NSKeyValueObservingOptions)options context:(void*)context;



- (void)removeObserver:(id)observer forKeyPath: (NSString*)keyPath context:(void*)context;



- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;

Saturday, March 23, 13

KVO vs. NSNotificationCenter • No “KVONotificationCenter” object • No callback selector, all notifications come through -observeValueForKeyPath:...

• Must observe each object individually, i.e. no equivalent of observing nil

Saturday, March 23, 13

static NSString* MyObservationContext = @”MyObservationContext”; - (void)loadView { [super loadView]; [[self representedObject] addObserver:self forKeyPath:@”name” options:0 context:MyObservationContext]; [[self representedObject] addObserver:self forKeyPath:@”icon” options:0 context:MyObservationContext]; } - (void)observeValueForKeyPath:(NSString *)aKeyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (object == [self representedObject] && context == MyObservationContext) { if ([keyPath isEqualToString:@”name”]) [self updateNameTextField]; else if ([keyPath isEqualToString:@”icon”]) [self updateIcon]; } else [super observeValueForKeyPath:aKeyPath ofObject:object change:change context:context]; }

Saturday, March 23, 13

KVO downsides • -observeValueForKeyPath:... gets unruly fast if you’re making multiple observations

• Need to make sure that any observations

that you didn’t register get passed to super

Saturday, March 23, 13

KVO with blocks • •

A more straightforward way of managing observations Simply pass a block that you want to be called when the value changes - (void)loadView { [[self representedObject] addObserverForKeyPath:@”name” task:^(id object, NSDictionary* change) { [self updateNameTextField]; }]; [[self representedObject] addObserverForKeyPath:@”icon” task:^(id object, NSDictionary* change) { [self updateIcon]; }]; }

Saturday, March 23, 13

KVONotificationManager https://github.com/schwa/KVONotification-Manager • •

Simple implementation, adds a category to NSObject Uses an identifier object to differentiate each observation for purposes of removal typedef void (^KVOBlock)(NSString *keyPath, id object, NSDictionary *change, id identifier); - (void)addObserver:(NSObject *)observer handler:(KVOBlock)inHandler forKeyPath:(NSString *)inKeyPath options:(NSKeyValueObservingOptions)inOptions identifier:(id)inIdentifier; - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)inKeyPath identifier:(id)inIdentifier;

Saturday, March 23, 13

KVO+Blocks https://gist.github.com/153676 • More complex implementation • Uses objc_setAssociatedObject() to store observations for each observed object



Allows notifications to be posted to an NSOperationQueue

• •

Adds additional hooks to ensure thread safety

Saturday, March 23, 13

Returns a token object that you must hang on to if you want to remove the observation

AMObserver API typedef void (^AMBlockTask)(id obj, NSDictionary *change); - (AMBlockToken *)addObserverForKeyPath: (NSString *)keyPath onQueue:(NSOperationQueue *)queue task:(AMBlockTask)task; - (void)removeObserverWithBlockToken: (AMBlockToken *)token;

Saturday, March 23, 13

Implementation •

Both use an internal helper class to manage the actual observation (AMObserverTrampoline, CKVOBlockNotificationHelper)



One object is created for each time an observer is added



The helper implements observeValueForKeyPath:... and simply calls through to the callback block

Saturday, March 23, 13

My mashup • Based mostly on KVO+Blocks • Uses passed-in identifier ala

KVONotificationManager instead of returned token (easier for removal, IMO)

• Adds new parameter for KVO observing options

Saturday, March 23, 13

My API - (void)addObserverForKeyPath:(NSString*)inKeyPath withOptions:(NSKeyValueObservingOptions)inOptions identifier:(NSString*)inIdentifier onQueue:(NSOperationQueue*)inQueue task:(void (^)(id obj, NSDictionary *change))task; - (void)addObserverForKeyPath:(NSString *)keyPath task: (void (^)(id obj, NSDictionary *change))task; //simpler cover method - (void)removeObserverForKeyPath:(NSString*)inKeyPath identifier:(NSString*)inIdentifier;

Saturday, March 23, 13

Retain cycles •

Retain cycles formed when referencing self in your callback block



Approach 1: Use __weak attribute __weak id blockSelf = self; [representedObject addObserverForKeyPath:@”name” task:^(id object, NSDictionary* change) { [self updateNameTextField]; //NO: forms retain cycle [blockSelf updateNameTextField]; //YES: __weak vars are not retained }];

Saturday, March 23, 13

Problems with __weak •

Can lead to unexpected (though correct) behavior if the object gets deallocated before the block is called



Reference inside the block will be nil - might work OK, might not



Things gets more hairy if the object is deallocated while the block is executing



Reference will be valid initially, then all of a sudden become nil

Saturday, March 23, 13

__weak and __strong __weak id weakSelf = self; [representedObject addObserverForKeyPath:@”name” task:^(id object, NSDictionary* change) { __strong id strongSelf = weakSelf; [strongSelf updateNameTextField]; }];



Saturday, March 23, 13

This way, strongSelf will either be nil or a valid reference through the entire block

Removing observers •

Like NSNotifications, you must remove your observation when you’re deallocated - (void)dealloc { [[self representedObject] removeObserverForKeyPath:@”name”] identifier:kObservationIdentifier]; [super dealloc]; }

Saturday, March 23, 13

Things to watch out for • Because observations are self contained, it can be easy to lose track of where you make them in your code

• I typically have a -setupObservations

method for most classes, and make all observations there

Saturday, March 23, 13

Da Code

• My version of KVO+Blocks is available on Github at https://gist.github.com/1217658

Saturday, March 23, 13

Summary • Key-value observing is very useful, but the default API is somewhat lacking

• Using blocks for callbacks improves code readability, scalability

Saturday, March 23, 13