Today we go over the various ways that you can monitor properties in your classes for changes, and act on those changes. There are three ways that I am going to demonstrate.
- Manually - Notify a specific class of changes to specific properties manually.
- Monitoring statically via KVO - Notifies specific classes of changes made to specific properties automatically.
- Monitoring dynamically via KVO - Notifies any class of changes made to any property, without having to specify which properties by using the Objective-C runtime library. Yay C functions!
Object monitoring
There are instancs were monitoring property changes to an object can be very useful. You might want to monitor changes to various object properties that might need to get wrote to a local database, propogate back to a server, or perhaps you want to immediately write out property changes to file. Monitoring an object for changes allows you to save the changes as they happen, and prevent invoking any save methods or update methods when nothing has changed.
In order to monitor the properties of an object, you need to determine what the extent of the monitoring needs to be. Are you only going to monitor a handful of properties in a single object? Perhaps you have several dozen properties spread out over multiple objects, or you might want to make something flexible so you can re-use it on other projects if needed.
I should note that two of the three approachs I'm going to discuss use KVO. If you have a lot of objects and need your app to be fairly performant, this might not be the best solution for you. While KVO is very quick, it does come with some additional overhead. The performance hit is negligible on standard apps, or even hard-core apps with very few KVO's; a large quantity (we're talking hundreds) of KVO setups can significantly affect your performance though. A general rule of thumb is not to use these in any kind of app/game loop and keep the code that they perform fairly short. It should be used primarily as a callback, letting something know that things have changed.
Manually
Manually is the most straight forward approach. You create a property, preferably a BOOL
, and set it to YES when a property is changed. In the BOOL properties setter, you would invoke what ever method that is neccessary to commit or process those changes.
It's best to demonstrate with an exampe, so let's say we have a class we are building called PlayerSettings and this class is responsible for saving the players settings in a game. When a setting is changed, we want to automatically save the settings to what ever format you happen to choose. The format isn't important, getting to the save code automatically without manually invoking a [player saveSettings];
method all the time is.
Lets start with the header API, which just exposes the public properties for the player settings class.
#import <Foundation/Foundation.h>
@interface MYPlayerSettings : NSObject
@property (strong, nonatomic) NSString *name;
@property (nonatomic) int age;
@property (nonatomic) double mouseSensitivity;
@property (strong, nonatomic) NSString *currentLevel;
@end
What we did was create four properties that pertain to the player settings. If any of these four properties are changed, we want to instantly save the player settings. Notice that there is no - (void)saveSettings;
method? That's because there will be no need to manually save the settings; we'll do it all automatically. In order to do that though, we need to implement the player settings, so lets get to that.
#import "MYPlayerSettings.h"
@interface MYPlayerSettings ()
@property (nonatomic) BOOL settingsChanged;
@end
@implementation MYPlayerSettings
@end
In order to make things simple, we are going to use a BOOL property called settingsChanged
. This property will be the only thing responsible for saving the player settings. I'll show you how by implementing the properties setter method next.
#import "MYPlayerSettings.h"
@interface MYPlayerSettings ()
@property (nonatomic) BOOL settingsChanged;
@end
@implementation MYPlayerSettings
// Setter method
- (void)setSettingsChanged:(BOOL)settingsChanged {
_settingsChanged = settingsChanged;
if (settingsChanged) [self savePlayerSettings];
_settingsChanged = NO; // We are saved; no longer changed.
}
- (void)savePlayerSettings {
// Do stuff.
}
@end
In our settingsChanged
setter method, we check if the settingsChanged property is being set to YES
. If it is, then we need to save the player settings, so we invoke the [self savePlayerSettings];
method. Note that the savePlayerSettings
method is private; just like I said above, the player settings class will handle saving itself, there is no need to expose the method via the public API.
Next, we need to actually provide a way to set the settingsChanged
property right? We do that by implementing the setter methods for our four properties like so:
- (void)setName:(NSString *)name {
_name = name;
self.settingsChanged = YES;
}
- (void)setAge:(int)age {
_age = age;
self.settingsChanged = YES;
}
- (void)setCurrentLevel:(NSString *)currentLevel {
_currentLevel = currentLevel;
self.settingsChanged = YES;
}
- (void)setMouseSensitivity:(double)mouseSensitivity {
_mouseSensitivity = mouseSensitivity;
self.settingsChanged = YES;
}
Not to bad right? Anytime that your game needs to adjust a setting, the player settings will automatically get saved. Now this does have some drawbacks, such as what happens if you change the player's settings from two different threads or what if you save method is asynchronous? There are some edge cases to consider with this approach, so keep that in mind. For small objects such as this, with quick file I/O operations, this would work just fine.
What if I have a handful of classes, each with a dozen properties that I want to implement this with? Isn't there an easier way? Why yes there is. You can either statically monitor or dynamically monitor an object via KVO, both of which I'll show you next.
Monitoring statically via KVO Part 1:
We will continue to use the PlayerSettings
class for this example, but we will build on it because (hypothetically) you have added additional settings to the player that need to be saved. As mentioned above, it's a royal pain to write out a setter method for every single property, just for the sake of setting settingsChanged
to YES
. Let's do this a bit differently. We will use KVO.
KVO stands for Key-value observing and it provides a mechanism for objects to be notified when changes are made to a object's properties. There are a couple of ways we could do this, one of which modifies the above setter methods to look like such:
#import "MYPlayerSettings.h"
@implementation MYPlayerSettings
- (void)setName:(NSString *)name {
_name = name;
[[NSNotificationCenter defaultCenter] postNotificationName:@"settingChanged" object:self];
}
- (void)setAge:(int)age {
_age = age;
[[NSNotificationCenter defaultCenter] postNotificationName:@"settingChanged" object:self];
}
- (void)setCurrentLevel:(NSString *)currentLevel {
_currentLevel = currentLevel;
[[NSNotificationCenter defaultCenter] postNotificationName:@"settingChanged" object:self];
}
- (void)setMouseSensitivity:(double)mouseSensitivity {
_mouseSensitivity = mouseSensitivity;
[[NSNotificationCenter defaultCenter] postNotificationName:@"settingChanged" object:self];
}
- (void)savePlayerSettings {
// Do stuff.
}
@end
As you can see, we no longer have the @property (nonatomic) BOOL settingsChanged;
property, nor the setter method were we invoke the [self savePlayerSettings];
method. Instead, we have this nifty [[NSNotificationCenter defaultCenter] postNotificationName:object:];
call. What this does is send a message to the specified object (in this case self
) with a notification called settingChanged
. The notification name can be anything, I just happened to use settingChanged because it seems appropriately titled.
Once this happens, NSNotificationCenter will look for any object that is registered to receive the settingChanged
message. This is called observing an object and in this case, the above code will do nothing because we have not added any observers. We will do that by implementing the PlayerSettings
initializer method.
- (id)init {
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(savePlayerSettings)
name:@"settingChanged"
object:self];
}
return self;
}
In the initializer we are adding ourself as on observe to any property within ourself that sends the settingChanged
notification. It's important that the object
argument has self
as the parameter, otherwise our PlayerSettings
class will be registering to receive settingChanged
notifications from any object, which could be bad. For this example, we only want to receive messages from changes made to ourself. Also note that the parameter selector:@selector(savePlayerSettings)
is telling NSNotificationCenter to invoke our [self savePlayerSettings]
method when it receives the settingChanged
notification. Now, unlike our manual approach, we are no longer manually invoking the save method in our private code either!
Now, let's assume someplace in your game you need to instance some player settings and assign some values. You would do so like this:
MYPlayerSettings *settings = [[MYPlayerSettings alloc] init];
settings.name = @"Bob";
settings.age = 22;
settings.currentLevel = @"Last level";
Each time you assign a property a value, the settings
object will be saved automatically. As mentioned before, it works just fine for something small like this, or if you are only saving the changes. In the event that you have a massive amount of properties, or a lot of objects that will have it's entire object saved or transferred, you don't want to have your save code being called after each assignment. What if your save code is sending the data across the network to a server someplace? You are wasting data by just sending the save data across the network multiple times. This needs to be fixed! On top of that, wasn't the point of using KVO to elemenate the need for implementing the setter method on our properties? We're still doing that! Let's take care of that.
Monitoring statically via KVO Part 2:
We will continue to use our previous PlayerSettings.h
file, but we are going to start fresh on our PlayerSettings.m
file. It should look like this:
#import "MYPlayerSettings.h"
@implementation MYPlayerSettings
@end
Nice and empty! Now, as your class grows, it can be a pain in the behind to continously add new setter method for our properties, so that's going to stop. Instead, we will rely on KVO a bit differently by observing actual properties themselves rather than observing a object for a broadcasted message. Our PlayerSettings
will no longer broadcast a settingChanged
message when properties are changed, instead the PlayerSettings
class will be self-aware and know when it has had it's own properties changed. In order to do this, we need to do a couple of things first. We need to implement a init
method, a observeValueForKeyPath:ofObject:change:context:
method and finally our savePlayerSettings
method.
To get started, the PlayerSettings
object needs to know what properties to monitor for changes, so we implement our initializer method and add ourself as an observer to those properties.
- (id)init {
self = [super init];
if (self) {
[self addObserver:self forKeyPath:@"name" options:0 context:NULL];
[self addObserver:self forKeyPath:@"age" options:0 context:NULL];
[self addObserver:self forKeyPath:@"currentLevel" options:0 context:NULL];
[self addObserver:self forKeyPath:@"mouseSensitivity" options:0 context:NULL];
}
return self;
}
That's a pretty easy initializer to implement, we just tell ourself that we want to observe ourself, monitoring each property that we specify. Now, when a property is changed, our PlayerSettings
object will be made aware of it! How does this happen though? If you remember earlier, we specified that we wanted our savePlayerSettings
method to be used right? In the above code, we aren't specifying any method, so what gets invoked? The answer to that lays in the Apple Documentation:
The observer must implement the key-value observing method observeValueForKeyPath:ofObject:change:context:.
Since we are observing ourself, we have to implement the observeValueForKeyPath:ofobject:change:context:
method ourself. When a property is changed, this method will automatically be invoked for us. So that means, within that method, we can invoke our savePlayerSettings
method.
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
[self savePlayerSettings];
}
- (void)savePlayerSettings {
NSLog(@"%@ saved", self.name);
}
Now we have a fully automated save system and it's actually fewer lines of code than before. As you add new properties to the PlayerSettings
class, you just need to go and observe them in the initializer.
We still have the same problem however of saving multiple times when we don't really need to. Take the following code, re-used from above:
MYPlayerSettings *settings = [[MYPlayerSettings alloc] init];
settings.name = @"Bob";
settings.age = 22;
settings.currentLevel = @"Last level";
settings.mouseSensitivity = 5.0;
Due to all four properties being changed, the save code will be called four different times. Is that efficient? Not really, so we need to fix that. I chose to take a similar approach as to what Apple did with their UI animations. We will add two new methods and two different properties. The methods will be called beginUpdates
and endUpdates
and our properties will be two BOOL
values called settingsChanged
and performingBatchChanges
.
First let's create the two properties in our .m file like such:
@interface MYPlayerSettings ()
@property (nonatomic) BOOL settingChanged;
@property (nonatomic) BOOL performingBatchChanges;
@end
When we invoke our observeValueForKeyPath:
method, we will need to perform a check. First, check if we are performingBatchChanges
and if so, do not save the player settings. Since we are doing batch changes, we will save the player setting once we are completed with all of the changes. Since we know that settings have changed though, we need to set settingChanged
to YES
.
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if (self.performingBatchChanges) {
self.settingChanged = YES;
} else {
[self savePlayerSettings];
}
}
In order to perform the batch changes, we need to implement our two new methods, beginUpdates
and endUpdates
. These two methods are really simple and need to be added to your .h file like this:
#import <Foundation/Foundation.h>
@interface MYPlayerSettings : NSObject
@property (strong, nonatomic) NSString *name;
@property (nonatomic) int age;
@property (nonatomic) double mouseSensitivity;
@property (strong, nonatomic) NSString *currentLevel;
- (void)beginUpdates;
- (void)endUpdates;
@end
Next we will add the implementation of these two new methods into our .m file like this:
- (void)beginUpdates {
self.performingBatchChanges = YES;
}
- (void)endUpdates {
self.performingBatchChanges = NO;
if (self.settingChanged) {
[self savePlayerSettings];
}
}
This is pretty straight forward. When a property is changed, our observeValueForKeyPath:
method is invoked. We check if we are performing batch changes (due to the user invoking beginUpdates
prior to changing the property) and we set the settingChanged
property to YES
to indicate that we have changed settings but not saved them. If this is not a batch change, then we just save the settings. Once the user invokes the endUpdates
method, we set our performingBatchChanges
to NO
because we are no longer making batch changes. Lastly, we have to check if the settings were changed and if they were, save them. Why perform this check? It's possible for the user to do the following:
[self.playerSettings beginUpdates];
[self.playerSettings endUpdates];
If they invoked the beginUpdates
and endUpdates
methods without ever making any changes, then we would be needlessly saving the player settings.
So, how do we use this? Pretty simply, if you are changing one or two properties, just use the settings file like normal.
self.playerSettings = [[MYPlayerSettings alloc] init];
self.playerSettings.mouseSensitivity = 5.0;
There's no harm in changing one or two settings, but if you are wanting to change several, then you use your new beginUpdates
and endUpdates
methods.
self.playerSettings = [[MYPlayerSettings alloc] init];
[self.playerSettings beginUpdates];
self.playerSettings.name = @"Bob";
self.playerSettings.age = 22;
self.playerSettings.currentLevel = @"Last level";
self.playerSettings.mouseSensitivity = 5.0;
[self.playerSettings endUpdates];
The player settings will only be saved once now, at the end when endUpdates
is invoked. Saves on I/O or data usage if the content is sent over the network.
Alright, we are now monitoring our object's properties and automatically invoking our save method. What if we want to add another object, like a GameSettings
to our project? It's simple enough that we can re-use the above code and get it up and running, but what if we could just write the code once and never re-write it again; while using across dozens of classes? Monitoring property changes dynamically via KVO can get the job done.
Monitoring dynamically via KVO
This approach digs into the Objective-C runtime and requires use of it's C runtime functions. I assume the reader has some understanding of what ARC does and is famiiar with introspection. As with most dynamic approaches, this one comes with the largest performance hit. You will not see the hit unless you are using this on hundreds of objects (like in a game).
Let's start off by creating a new class called MYGameSettings
and providing it with some properties in the header.
#import <Foundation/Foundation.h>
@interface MYGameSettings : NSObject
@property (strong, nonatomic) NSString *version;
@property (nonatomic) int brightness;
@property (nonatomic) BOOL hardMode;
@end
We will save the implementation for last, as we need to build our dynamic object monitoring class first.
We are going to create a new class called ObjectMonitor
. This object will be used to observe our classes from now on, monitoring what happens to the classes and then acting on the changes that take place. Let's set up our ObjectMonitor.h
header first.
#import <Foundation/Foundation.h>
@interface ObjectMonitor : NSObject
- (id)initWithMonitoringObject:(NSObject *)objectToMonitor respondingWithSelector:(SEL)selector;
@end
We will implement a new initializer that accepts the object we want to monitor, and a selector which identifies a method that we will invoke on the objectToMonitor
. What this will do, is allow ObjectMonitor
to monitor our GameSettings
and PlayerSettings
objects (or any other object you provide it) and each object will tell ObjectMonitor
what method to invoke. When a property on the observed class changes, the ObjectMonitor
will invoke the requested method, within the provided object, in this case our settings classes. You'll see how it all comes together in the end. For now, we need to implement our ObjectMonitor.m
implementation, so let's do that. First, the initializer and our selector property:
#import "ObjectMonitor.h"
#import <objc/objc-runtime.h>
@interface ObjectMonitor ()
@property (nonatomic) SEL selector;
@end
@implementation ObjectMonitor
- (id)initWithMonitoringObject:(NSObject *)objectToMonitor respondingWithSelector:(SEL)selector {
self = [super init];
if (self) {
self.selector = selector;
unsigned int count;
objc_property_t *properties = class_copyPropertyList([objectToMonitor class], &count);
for (size_t i = 0; i < count; ++i) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
[objectToMonitor addObserver:self forKeyPath:key
options:0 context:NULL];
}
free(properties);
}
return self;
}
@end
The first thing we do in our initializer is store a reference to the selector provided to us for future use. A selector is nothing more than a pointer to a method stored in a variable for us to use at a later date. This can cause some issues, which we will discuss and handle in just a bit.
Next, we have this interesting Objective-C Runtime function:
class_copyPropertyList([objectToMonitor class], &count);
Here we are calling the C runtime function class_copyPropertyList
which takes a class as an argument and outputs the number of properties contained within that class. It also returns an array of every property that the class has. So we have the number of properties that the object has stored in the count
variable and an array of every property the object has stored in the properties
variable. What next you ask? We itterate through each property and observe them!
In our for-loop we use another C runtime function called property_getName
which takes a property from our array and determines it's name and returns it for us. That provides us with a fully qualified property name that belongs to objectToMonitor
. We then tell objectToMonitor
that we are going to observe it for any changes made to that property. We then loop through the rest of the array, adding ourself as an observer to each property found in the object.
Finally we invoke the C runtime function free
which releases the properties array from memory. Since C functions are not managed by ARC, we have to manage the memory ourselves.
A side note on observing the objectToMonitor
for property changes. If you wanted, you could expand on this by adding a NSArray argument to this initializer with properties that you want exempt from observation. Then you could adjust your for-loop to be like this:
for (size_t i = 0; i < count; ++i) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
// If this property is in our exempt array, we skip it and move on to the next.
if ([exemptProperties containsObject:key]) {
continue;
}
[objectToMonitor addObserver:self forKeyPath:key
options:0 context:NULL];
}
Instead of providing a list of exemptions, you could just provide a list of properties to observe as well. This could be useful if you have several dozen properties but only want to monitor a handful. In most cases though, manually observing those using static KVO or the manual approach above is probably a better idea.
Alright, we have our initializer wrote and we are now observing any object that instances our ObjectMonitor
! The next thing to do would be to do something when the properties are actually changed. Remember the observeValueForKeyPath
method we implemented in our PlayerSettings
class? Well, our ObjectMonitor
class will implement that now, and our PlayerSettings
and GameSettings
classes won't have to implement this at all.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(@"%@ had %@ changed!", NSStringFromClass([object class]), keyPath);
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[object class] instanceMethodSignatureForSelector:self.selector]];
if (invocation) {
invocation.target = object;
invocation.selector = self.selector;
@try {
[invocation invoke];
}
@catch (NSException *exception) {
NSLog(@"Failed to invoke the method");
}
@finally {
}
} else {
NSLog(@"ERROR: Failed to locate the method for @selector:%@", NSStringFromSelector(self.selector));
}
}
The first thing we do is print to the debugger that we have entered the method due to an object's properties changing. The method arguments provides us with the property name and the object that the property belongs to. The property name is stored in keyPath
and the object that owns the property is object
. Since the method provides us with this information, we can print the name of the property using NSLog and include the class name. Since the object is not a string, we get the name of the class by using NSStringFromClass
.
The next part can make or break your app, so you really want to make sure and set it up properly. We instance a NSInvocation object, which will be used to actually invoke the method provided to us and stored under self.selector
. It's really important that you don't use [object performSelector:self.selector];
because this will not be memory safe and can leak. The runtime needs to know what to do with the result of your method invocation, which could be anything (void
,BOOL
, MYPlayerSettings
). ARC would normally acquire this information from your objects header. With this approach, the ObjectMonitor
class has no idea what method is stored in the selector, preventing the runtime from determining what the result is. This causes a compiler warning to be generated stating that a leak could occure. You could potentially acquire the actual method pointer itself by using IMP imp = [methodForSelector:self.selector];
but there is no guarantee that the correct method signature will be returned. So invoking imp
could crash your app.
So, how do we get around this safely, keeping the runtime happy? We use NSInvocation. This is not a crash free solution but if you code it right and provide proper documentation on how to use the ObjectMonitor
then you can have it work without a hitch.
The [NSInvocation invocationWithMethodSignature: instanceMethodSignatureForSelector:]
checks the properties owner (object
argument) to see if it has the selector we have a reference stored to. If it does, then a valid NSInvocation object is returned. If no method exists, then nil is returned. We check against nil on the very next line, so if no method is returned, we don't try to invoke it and crash out app.
We tell the invocation which object we want to invoke the method on ( invocation.target = object;
) and then we tell it which method to invoke (invocation.selector = self.selector;
). Lastly, we invoke the method using [invocation invoke];
and we wrap this invocation in a @try/@catch
in the event that the invocation fails.
Using this approach, our ObjectMonitor
is fairly safe to use. In the event an invalid method is provided we are protected from crashes. One last item to mention before we move on from our ObjectMonitor
class. The [invocation invoke];
call invokes the method specified with zero arguments. What if you would like to know what property changed? You can do that by adjusting the if statement to look like this:
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[object class] instanceMethodSignatureForSelector:self.selector]];
if (invocation) {
invocation.target = object;
invocation.selector = self.selector;
[invocation setArgument:&keyPath atIndex:2]; // index 0 = self and index 1 = _cmd
@try {
[invocation invoke];
}
@catch (NSException *exception) {
NSLog(@"Failed to invoke the method");
}
@finally {
}
} else {
NSLog(@"ERROR: Failed to locate the method for @selector:%@", NSStringFromSelector(self.selector));
}
It is critical that the atIndex
argument in the setArgument
call is 2 or greater. The Objective-C runtime invokes all methods by send a message to the object. The message is objc_msgSend()
and it requires at least two arguments. The receiver (in this case our object we are monitoring) and a selector. All Objective-C messages have two arguments that are always included. The first argument is passed as the receiver of the second agument (our method selector) and is most often passed as self
. The second argument (selector) is passed as \_cmd
and contains a pointer to our actual object method. So we can start adding arguments at index 2. If you would like to invoke the method we specify and provide the property name that was changed, you can do so. Your object that actually implements the method being invoked must have an argument that accepts what you add to the NSInvocation, otherwise it will fail to invoke. Luckily, since we wrapped it in a @try/@catch
it won't crash your app. It can be annoying to debug though.
So, that's it, we have built our ObjectMonitor
class. Now let's use it. We will return to our PlayerSettings
class and re-write it to make use of this. We add our ObjectMonitor
as a property and then in our PlayerSettings
initializer, we instance it, tell it to observe us and we provide it our savePlayerSettings
method for invocation.
#import "MYPlayerSettings.h"
#import "ObjectMonitor.h"
@interface MYPlayerSettings ()
@property (strong, nonatomic) ObjectMonitor *objectMonitor;
@end
@implementation MYPlayerSettings
-(id)init {
self = [super init];
if (self) {
self.objectMonitor = [[ObjectMonitor alloc]
initWithMonitoringObject:self
respondingWithSelector:@selector(savePlayerSettings)];
}
return self;
}
- (void)savePlayerSettings {
NSLog(@"%@ saved", self.name);
}
@end
And we do the same thing with our GameSettings
class.
#import "MYGameSettings.h"
#import "ObjectMonitor.h"
@interface MYGameSettings ()
@property (strong, nonatomic) ObjectMonitor *monitor;
@end
@implementation MYGameSettings
- (id)init {
self = [super init];
if (self) {
self.monitor = [[ObjectMonitor alloc]
initWithMonitoringObject:self
respondingWithSelector:@selector(saveGameSettings)];
}
return self;
}
- (void)saveGameSettings {
NSLog(@"Game settings saved!");
}
@end
Isn't it nice that we can now add object property monitoring to any class we want with just 2 lines of code? A property declaration and instancing. Now we can use the following code, anywhere in our app:
self.playerSettings = [[MYPlayerSettings alloc] init];
self.playerSettings.name = @"Bob";
self.playerSettings.age = 22;
self.playerSettings.currentLevel = @"Last level";
self.playerSettings.mouseSensitivity = 5.0;
self.gameSettings = [[MYGameSettings alloc] init];
self.gameSettings.brightness = 73;
self.gameSettings.hardMode = NO;
self.gameSettings.version = @"1.0";
Our game settings and player settings will always be saved.
Of course we lost the ability to use our cool beginUpdates
and endUpdates
, but that can easily be reimplemented. You just re-set it back up and in your savem methods, don't actually save if performingBatchUpdates
is YES
.
I hope this document on implementing the ability to dynamically monitor any property on any object proves to be useful to you guys. It only took me about 5 hours to write!
Until next post.