Maintaining state between launches is an important job of any iPhone application. Unfortunately it's not trivial when your app has many views having dependencies among each other. And coming of iPhone OS 4.0 will not make your life easier since applications can be (and will be) terminated anyway.
So, when your application is relatively simple, saving its state is simple as well. You can do it this way:
- in the applicationWillTerminate: method you save your state into NSUserDefaults
- in viewDidLoad: or applicationDidFinishLaunching: (whichever is more convenient for you) you read the state from user defaults and apply it (say, set current tab of a UITabBarController)
After your app gets bigger these methods start to grow and become very messed, especially if you have a navigation controller with potentially deep navigation stack. UIViewController implements NSKeyedArchiver, however after I experimented with it, I realized that it codes/encodes pretty weirdly and moreover anyway there's no way to restore dependencies between view controllers (if you, say, passed an object from Nth view controller to (N+1)th and expect to have the same instance in both controllers).
Thus I want to describe the approach to saving application state we used in Yandex.Maps application.
First, we don't use NSUserDefaults in order to separate state from application preferences. We store the state in a separate file. Then, we declared three protocols very similar to NSCoding:
@protocol YMCoder
- (void)encodeObject:(id)object forKey:(NSString*)key;
- (void)encodeController:(id)vc forKey:(NSString*)key;
@end
@protocol YMDecoder
- (id)decodeObjectForKey:(NSString*)key;
- (void)decodeController:(id)vc forKey:(NSString*)key;
@end
@protocol YMPersistantController
- (void)decodeStateWithDecoder:(id)decoder;
- (void)encodeStateWithCoder:(id)coder;
@end
Now each controller (not necessarily UIViewController) implements YMPersistantController like this:
- (void)encodeStateWithCoder:(id)coder {
[coder encodeObject:[NSNumber numberWithBool:self.searchActive] forKey:@"searchActive"];
[coder encodeController:self.searchController forKey:@"searchController"];
}
- (void)decodeStateWithDecoder:(id)decoder {
self.searchActive = [[decoder decodeObjectForKey:@"searchActive"] boolValue];
[decoder decodeController:self.searchController forKey:@"searchController"];
if (self.searchActive) {
[self activateSearch];
}
}
The controller doesn't care about when this methods are called (since they can be called a bit later after launch to visually decrease app launch time) or about naming conflicts (it can be guaranteed while coding/decoding). All it cares about is only its own state and dependent controllers.
Finally, we implemented a kind of NSKeyedArchiver, which takes a root controller, recursively encodes it from NSData or decodes an NSData given. Encoding/decoding is called from applicationWillTerminate:/applicationDidFinishLaunching: respectively.
This is it. Actually, I see nothing tricky or special in this approach, but it was the key to adding persistence to Maps application painlessly because existing code was already complex and hard to redesign.
In the end I want to point at completely different solution I saw in three20 code. Shortly speaking, the idea is that your view controllers have URLs like webpages. You store the URL like /themap/search/resultdetails?query=coffee&resultid=42 in user defaults and then restore from it. I find this approach pretty interesting, so you're thinking about persistence in your app take a look at three20's code, it may fit you better.
0 comments:
Post a Comment