你的位置:首页 > 操作系统

[操作系统]iOS Programming State Restoration 状态存储


iOS Programming State Restoration 状态存储

If iOS ever needs more memory and your application is in the background, Apple might kill it to return memory to the system.

如果iOS 需要更多的memory,你的应用在后台,apple 可能杀死它来得到更多的内存给系统。

This should be transparent to your users; they should always return to the last spot they were within the application.

这可能是对你来说是透明的,他们总是返回他们在应用的最后一个spot。

To achieve this transparency, you must adopt state restoration within your application. State restoration works a lot like another technology you have used to persist data – archiving.

为了得到这种transparency,你必须采用state restoration 在你的应用中。state restoration 工作起来像你用过persist data 的技术-archiving .

When the application is suspended, a snapshot of the view controller hierarchy is saved.

当应用被挂起时,一个view controller hierarchy 的snapshot 被存储。

If the application was killed before the user opens it again, its state will be restored upon launch.

如果应用在用户再次打开之前被杀掉了,它的状态就会被存储到launch。

If the application has not been killed, then everything is still in memory and you have no need to restore any state.)

如果应用还没有被杀掉,所有的东西都在内存中,你不必要存储任何状态。

If an application does not implement state restoration, the user will briefly see the previous application state, but then the screen will change to its fresh-launch state.

如果一个应用没有实现state restoration ,那么用户将短暂的看到之前的应用程序状态,但是然后屏幕将改变到它的新启动的状态。

1 How State Restoration Works

A running app can be thought of as a tree of view controllers and views, with the root view controller as the root of the tree.

一个running app 能被认为是一个view controller ,view 和root view controller 作为树的根的树。

For example, the interface of your HypnoNerd app can be thought of as a tree like this:

If you opt-in to state restoration, before your app is terminated, the system walks this tree asking each node, "What is your name?", "What is your class?", and "Do you have any data you want me to hold on to?" While the app is dead, this description of the tree is stored on the file system.

当你选择的状态存储,在你的app被终结之前,system 遍历这个树,询问每个节点,"你的名字是什么","你的类是什么""你是否有任何数据想让我保持"。当app is dead,tree 的表述被存储到文件系统中。

A node's name, actually called the restoration identifier, is typically the object's class name. The restoration class is typically the class of the object. The data holds the state of the object. For example, the data for a tab view controller includes which tab is currently selected.

一个node的名字,确切的称为对象类的名字,restoration class 一般是这个对象的类。数据保持了这个对象的状态。例如一个tab view controller的数据包括现在选择了哪个tab。

When the app is restarted, the system tries to recreate the tree of view controllers and views from that saved description.

当app 被重新开始时,系统视图recreate 这个view controllers 和view的tree从那个被存储的表述。

For each node:

(1)The system asks the restoration class to create a new view controller for that node.

系统询问restoration 类来创建一个新的view  controller 为那个节点。
(2)The new node is given an array of restoration identifiers: The identifier for the node being created and the identifiers for all of its ancestors. The first identifier in the array is the identifier for the root node. The last is the identifier for the node being recreated.

新的节点给了一组restoration identifiers:为了那个节点被创建的identifier和为它所有的祖先的identifiers. 在array 中的第一个identifier是root node 的identifier.最后一个是那个将要被创建的identifier.

(3)The new node is given its state data. This data comes in the form of an NSCoder

新的节点被给他的状态数据。这个数据来自NSCoder的一种形式。

2 Opting In to State Restoration 选择到state restoration 

State restoration is disabled by default in applications. To enable state restoration, you must opt in within the application delegate.

默认情况下,State restoration 是不被允许的。为了能够state restoration,你必须opt in 用application 的delegate。

Open BNRAppDelegate.m and implement the two delegate methods to enable state saving and restoration.

-  (BOOL)application:(UIApplication *)application
shouldSaveApplicationState:(NSCoder *)coder
{
return YES;

-  (BOOL)application:(UIApplication *)application
shouldRestoreApplicationState:(NSCoder *)coder
{
return YES;

When the application goes into the background, its state will attempt to be saved, and if the app is launching fresh, its save will attempt to be restored. To understand what gets stored, we need to discuss restoration identifiers. 

当应用到后台后,它的state 试图被存储,如果app 启动刷新,它的save将尝试被存储。

3 Restoration Identifiers and Classes

When the application's state is being saved, the window's rootViewController is asked for its restorationIdentifier.

当application 的状态被存储时,window的rootViewController 将被询问它的存储状态。

If it has a restorationIdentifier, it is asked to save (or encode) its state.

如果他有restorationIdentifier,它被询问存储或编码它的状态。

Then it is responsible for processing its child view controllers in the same way, and they, in turn, pass the torch to their child view controllers.

然后他负责处理它的child view controllers 以同样的方式,然后依次的传递给他们的child view controller .

If any view controller in the hierarchy does not have a restorationIdentifier, however, it (and its child view controllers, whether or not they have restorationIdentifiers) will be excluded from the saved state.

如果任何一个view controller 在hierarchy 并没有一个restorationIdentifier,然后,它(或者是它的child view controller 不管它有没有restorationIdentifier)将会从saved state 中排除掉。

the state of the two gray view controllers – and any child view controllers they might have – would not be saved.

如上图所示,两个灰色的view controller 没有被存储状态。

Typically, the restoration identifier for a class is the same name as the class itself. In BNRAppDelegate.m, give the navigation controller a restoration identifier in application:didFinishLaunchingWithOptions:.

一般情况下,一个类的restoration identifiere 与类自身的名字相同。在application:didFinishLaunchingWithOptions中给navigation controller 一个restoration identifier.

// Give the navigation controller a restoration identifier that is
// the same name as the class
navController.restorationIdentifier = NSStringFromClass([navController

class]);

 

Now that the navigation controller has a restoration identifier, it will attempt to save the state of its viewControllers if they have restoration identifiers themselves.

For your two UIViewController subclasses (BNRItemsViewController and BNRDetailViewController), you will assign the restoration identifier within that class's designated initializer.

Additionally, you will set the view controller's restoration class. When the view controller's state is being restored, it will ask this restoration class for an instance of the view controller to restore.

另外,你还设置view controller 的restoration 类。 当view controller 的state 被存储后,它会要求它的restoration class 来那个view controller 的实例被restore .

 

Open BNRItemsViewController.m and update init to assign the restoration identifier and class.

 

self.restorationIdentifier = NSStringFromClass([self class]);

self.restorationClass = [self class];

 

Open BNRDetailViewController.m and give the view controller a restoration identifier and class in initForNewItem:.

 

self.restorationIdentifier = NSStringFromClass([self class]);

self.restorationClass = [self class];

 

Finally, there is one more view controller that is created in Homepwner: the UINavigationController

that is presented modally when a new item is created.

Reopen BNRItemsViewController.m and update addNewItem: to give the UINavigationController a restoration identifier.

 

navController.restorationIdentifier = NSStringFromClass([navController class]);

 

4 State Restoration Life Cycle  state restoration 的生命周期

The method application:willFinishLaunchingWithOptions: gets called before state restoration has begun. You should use this method to set up the window and do anything that should happen before state restoration.

application:willFinishLaunchingWithOptions在state restoration 开始前被调用。你应该用这个方法来设置window以及在状态restoration  开始之前做的事情。

In BNRAppDelegate.m, override this method to initialize and set up the window.

- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen]

bounds]];
self.window.backgroundColor = [UIColor whiteColor];

return YES; }

Next, update application:didFinishLaunchingWithOptions: to set up the view controller hierarchy in case state restoration did not occur (for example, on the first launch of the application). Also, remove the code that is now in application:willFinishLaunchingWithOptions:.

接下来,更新application:didFinishLaunchingWithOptions来设置view controller hierarchy 以防state restoration 咩有发生(例如,在application首次启动时)。并且移除已经在application:willFinishLaunchingWithOptions方法中的代码。

 

// If state restoration did not occur,

// set up the view controller hierarchy

if (!self.window.rootViewController) {

BNRItemsViewController *itemsViewController

= [[BNRItemsViewController alloc] init];

 

5  Restoring View Controllers

Since the two view controllers have a restoration class, the restoration class will be asked to create new instances of their respective view controller.

因为两个view controllers 有一个restoration  class,restoration class 将被要求创建他们分别对应的view controller的新的实例。

In BNRItemsViewController.h, have the view controller conform to the UIViewControllerRestoration protocol.

 

@interface BNRItemsViewController : UITableViewController <UIViewControllerRestoration>

@end

Then, in BNRItemsViewController.m, implement the one required method for this protocol, which will

return a new view controller instance.

实现一个为这个协议必要的方法,它将返回一个新的view controller实例。

+ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray

*)path   coder:(NSCoder *)coder

{
return [[self alloc] init];

}

 

Now do the same for BNRDetailViewController. Open BNRDetailViewController.h and have it

conform to the UIViewControllerRestoration protocol.

@interface BNRDetailViewController : UIViewController

<UIViewControllerRestoration>

 

Implementing the protocol's required method for BNRDetailViewController is a bit trickier because you need to know whether to pass YES or NO to initForNewItem:.

                                        Restoration path

The restoration identifier path is an array of restoration identifiers that represents this view controller and its ancestors at the time the view controller's state was saved.

restoration identifier path 是一列restoration  identifiers ,代表了这个view controller 和它的祖先在view controller 的state 被保存时。

Armed with this knowledge, you know that the count of the restoration identifier path array will be 2 if you are viewing an existing BNRItem and 3 if you are creating a new BNRItem.

有这个知识,你知道restoration identifier path array 的计数是2如果你查看一个已经存在的BNRItem,如果你创建一个新的BNRItem,计数会是3.

Open BNRDetailViewController.m and implement the one required method of the UIViewControllerRestoration protocol.

+ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray

*)path   coder:(NSCoder *)coder

{
BOOL isNew = NO;
if ([path count] == 3) {

isNew = YES; }

return [[self alloc] initForNewItem:isNew];

 }

 

Instead, if a view controller that is getting restored does not have a restoration class, the application delegate is asked to provide the view controller.

如果一个将要被存储的view controller  没有restoration  class ,appliction  delegate 被要求提供那个view controller .

Open BNRAppDelegate.m and implement this method.

- (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents

coder:(NSCoder *)coder

{
// Create a new navigation controller
UIViewController *vc = [[UINavigationController alloc] init];

// The last object in the path array is the restoration
// identifier for this view controller
vc.restorationIdentifier = [identifierComponents lastObject];

// If there is only 1 identifier component, then // this is the root view controller
if ([identifierComponents count] == 1) {

self.window.rootViewController = vc; }

return vc;

}

6 Encoding Relevant Data 编码相关的数据

To persist other information, a UIViewController is given a chance to encode any relevant data in a process very similar to archiving.

为了持久化其他的信息,一个UIViewController 被给了一个机会来encode 任何相关的数据,与archiving 非常相似。

In fact, both encoding processes use an NSCoder object to do the work. You will use this to save out any necessary information in the view controller subclasses.

事实上,两个encoding processes 都用NSCoder 对象来工作。你可以用这个来存储在view controller  子类中的任何信息。

In BNRDetailViewController.m, encode the itemKey for the currently displayed BNRItem.

- (void)encodeRestorableStateWithCoder:(NSCoder *)coder

{
[coder encodeObject:self.item.itemKey

forKey:@"item.itemKey"];

[super encodeRestorableStateWithCoder:coder]; }

 

Then implement the decoding method to search through the BNRItemStore for the appropriate BNRItem.

 

- (void)decodeRestorableStateWithCoder:(NSCoder *)coder

{
NSString *itemKey =

[coder decodeObjectForKey:@"item.itemKey"];

for (BNRItem *item in [[BNRItemStore sharedStore] allItems]) {

if ([itemKey isEqualToString:item.itemKey]) {

self.item = item;

break; }

}

[super decodeRestorableStateWithCoder:coder]; }

 

There is still one problem: the text fields and labels are being populated with the values of the BNRItem. If the user has typed in some other value, those values will be lost upon state restoration.

有一个问题:text field 和label 是根据BNRItem 的值展现的。如果用户输入了其他的值,这些值会在state restoration 时消失。

You will fix this by saving the current text field values into the BNRItem.

你修复这个通过保存现有的text field values 到BNRItem。

Since view controller encoding occurs after the application has entered the background, you will also need to save the store again.

因为view controller encoding 发生在应用进入background 以后,你需要保存store again.

In BNRDetailViewController.m update the encode method.

 

// Save changes into item

self.item.itemName = self.nameField.text;

self.item.serialNumber = self.serialNumberField.text;

self.item.valueInDollars = [self.valueField.text intValue];

// Have store save changes to disk

[[BNRItemStore sharedStore] saveChanges];

 

7 Saving View States 保存 view states

There are a number of problems with the BNRItemsViewController:

(1)The UITableView is not being restored to its existing scroll position, nor is the currently selected row (if the user has drilled down into an item) being restored.

(2)The view controller does not save whether or not it is in editing mode, and so it is always restored with the default value (which is not in editing mode).

 

In addition to the view controllers having a restoration identifier, certain UIView subclasses can have a restoration identifier to save certain information about the view.

除了view controllers 有一个restoration  identifier,确定的UIView subclasses 能有一个restoration identifier来保存关于view的指定的信息。

Specifically, these subclasses can save some of their information: UICollectionView, UIImageView, UIScrollView, UITableView, UITextField, UITextView, and UIWebView.

一般的,这些子类能保存他们的一些信息:UICollectionView, UIImageView, UIScrollView, UITableView, UITextField, UITextView, and UIWebView.

The documentation for each of those classes states what information is preserved.

每个类的文件表述了他们要被保留的信息。

For UITableView, the useful piece of information saved is the content offset of the table view (the scroll position).

例如,UITableView,保存的有用的信息是table view 的content offset.(the scroll position)

In BNRItemsViewController.m, give the UITableView a restoration identifier.

 

self.tableView.restorationIdentifier = @"BNRItemsViewControllerTableView";

Let's turn our attention to the editing mode of the view controller so its state persists.

In BNRItemsViewController.m, implement the encode and decode methods.

 

- (void)encodeRestorableStateWithCoder:(NSCoder *)coder

{
[coder encodeBool:self.isEditing forKey:@"TableViewIsEditing"];

[super encodeRestorableStateWithCoder:coder]; }

- (void)decodeRestorableStateWithCoder:(NSCoder *)coder

{
self.editing = [coder decodeBoolForKey:@"TableViewIsEditing"];

[super decodeRestorableStateWithCoder:coder]; }

UITableView will save information about the selected UITableViewCell row, but you need to help it out a little.

UITableView 将保存信息关于被选中的UITableViewCell row,但是你需要一点帮助。

In BNRItemsViewController.m, have the view controller conform to the UIDataSourceModelAssociation protocol in the class extension.

@interface BNRItemsViewController ()
<UIPopoverControllerDelegate, UIDataSourceModelAssociation>

The UIDataSourceModelAssociation protocol helps state restoration locate the appropriate model objects for your application.

UIDataSourceModelAssociation帮助state restoration 定位到你的应用恰当的model objects.

When the application state is being saved, state restoration will ask for a unique identifier for the model object associated with the selected row or rows (a BNRItem, for example).

当应用state 被存储时,state restoration 将询问与选中的行或多行的model objects的unique identifier.

When the application is relaunched, state restoration will provide the identifier and ask for an index path for that model object.

当application重新启动时,state restoration 将提供identifier,并询问model object 的index path.

Model objects may change positions in the UITableView on relaunch, but as long as your mapping is correct, the correct rows will be selected.

model objects 可能改变在UITableView中的位置当重新启动时,但是只要你的mapping 是正确的,正确的行就会被选中。

In BNRItemsViewController.m, implement the method to provide state restoration with a unique identifier for the selected BNRItem. You will use the itemKey property as the unique identifier.

- (NSString *)modelIdentifierForElementAtIndexPath:(NSIndexPath *)path

inView:(UIView *)view {

NSString *identifier = nil;

if (path && view) {
// Return an identifier of the given NSIndexPath,
// in case next time the data source changes

BNRItem *item = [[BNRItemStore sharedStore] allItems][path.row];

identifier = item.itemKey;

}

return identifier;

}

Then implement the inverse method that returns an NSIndexPath for a given identifier.

- (NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString

*)identifier

inView:(UIView *)view{

NSIndexPath *indexPath = nil;

if (identifier && view) {
NSArray *items = [[BNRItemStore sharedStore] allItems]; for (BNRItem *item in items) {

if ([identifier isEqualToString:item.itemKey]) {
int row = [items indexOfObjectIdenticalTo:item];

indexPath = [NSIndexPath indexPathForRow:row inSection:0];

break;

}

}

}

return indexPath;

}

 

8  Controlling Snapshots 

Sometimes you may want to have some control over what is displayed to your users on the next launch

有时,你可能想控制 在下次启动时,要展现什么给你的用户。

if your application displays sensitive information (such as a banking application showing account numbers and balances), you may want to hide this information so that it is not shown to unauthorized eyes.

有时你的应用展现一些敏感信息,你想隐藏这些信息这样它不会展现给未授权的人。

As another example, Apple blurs the camera contents when the Camera app goes into the background, not for security reasons, but to make it easier for users to notice the Camera app in the multitasking display (instead of getting distracted by whatever the camera was looking at when the app went into the background).

Modifying the snapshot is easy: you update the view before the snapshot is taken to have it display what you want the snapshot to be.

修正snapshot 很容易:你更新view在snapshot 被去除来展现你想让snapshot 成为什么之前。

n addition to the application delegate callbacks, the application also posts notifications when its state is transitioning.

application 也推送信息当状态改变的时候。

Observing the appropriate notifications in your view controllers will give you an opportunity to update the user interface.

observing 恰当的notifications在你的view controllers将给你一个机会更新user interface.

Specifically, you will want to observe the UIApplicationWillResignActiveNotification to obscure anything necessary, and UIApplicationDidBecomeActiveNotification to get things ready for the user to see again.

 

NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; [nc addObserver:self

selector:@selector(applicationResigningActive:) name:UIApplicationWillResignActiveNotification

object:nil];

[nc addObserver:self

selector:@selector(applicationBecameActive:) name:UIApplicationDidBecomeActiveNotification

object:nil];

 

-  (void)applicationResigningActive:(NSNotification *)note
{
// Prepare app for snapshot

-  (void)applicationBecameActive:(NSNotification *)note
{
// Undo any changes before user returns to app 

}

Finally, if your application implements state restoration but for some reason it does not make sense to use a snapshot the next time it launches, you can tell the application to ignore the snapshot:

最后,如果你的application 实现了state restoration 但是由于一些原因,它变得毫无意义在下次启动时用一个snapshot,你可以告诉application 忽略那个snapshot.

// This method should be called during the code that preserves the state

[[UIApplication mainApplication] ignoreSnapshotOnNextApplicationLaunch];