[TOC=5]
* * * * *
>原文鏈接 :[Preserving and Restoring State](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/PreservingandRestoringState.html#//apple_ref/doc/uid/TP40007457-CH28-SW1)
視圖控制器在狀態保存和恢復過程中起著重要的作用。 在掛起之前狀態保存記錄您的應用程序的配置,以便在后續應用程序啟動時恢復配置。 將應用程序返回到以前的配置為用戶節省了時間,并提供了更好的用戶體驗。
保存和恢復過程大部分是自動的,但是你需要告訴iOS你的應用程序的哪些部分可以保存。
保留 app 的視圖控制器的步驟如下:
* (必需)將恢復標識符分配給要保留其配置的視圖控制器; 請參閱[ Tagging View Controllers for Preservation](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/PreservingandRestoringState.html#//apple_ref/doc/uid/TP40007457-CH28-SW2)。
* (必需)告訴iOS如何在啟動時創建或定位新的視圖控制器對象; 請參閱[ Tagging View Controllers for Preservation](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/PreservingandRestoringState.html#//apple_ref/doc/uid/TP40007457-CH28-SW5)。
* (可選)對于每個視圖控制器,存儲返回該視圖控制器到其原始配置所需的任何特定配置數據;請參閱[ Encoding and Decoding Your View Controller’s State](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/PreservingandRestoringState.html#//apple_ref/doc/uid/TP40007457-CH28-SW6)。
有關保存和恢復過程的概述,請參閱[ App Programming Guide for iOS](https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40007072) 。
### 為了保存標記視圖控制器
UIKit 只保留您告訴它保存的視圖控制器。 每個視圖控制器都有一個 `restoreIdentifier` 屬性,其默認值為 `nil` 。 將該屬性設置為有效的字符串將告訴 UIKi t應該保留視圖控制器及其視圖。 您可以以編程方式或在故事板文件中分配恢復標識符。
分配恢復標識符時,請記住,視圖控制器層次結構中的所有父視圖控制器也必須具有恢復標識符。 在保存過程中,UIKit從窗口的根視圖控制器開始,并遍歷視圖控制器層次結構。 如果該層次結構中的視圖控制器沒有恢復標識符,則視圖控制器及其所有子視圖控制器和呈現的視圖控制器將被忽略。
#### 選擇有效的恢復標識符
UIKit 使用您的恢復標識符字符串來重新創建視圖控制器,因此選擇易于識別的字符串。如果 UIKit 不能自動創建一個視圖控制器,它要求你創建它,為你提供視圖控制器和所有父視圖控制器的恢復標識符。此標識符鏈表示視圖控制器的恢復路徑,以及如何確定請求的視圖控制器。恢復路徑從根視圖控制器開始,包括所有視圖控制器,直至包括所請求的視圖控制器。
恢復標識符通常只是視圖控制器的類名稱。 如果您在許多地方使用相同的類,則可能需要分配更有意義的值。 例如,您可以根據視圖控制器管理的數據分配一個字符串。
每個視圖控制器的恢復路徑必須是唯一的。如果容器視圖控制器有兩個子元素,那么容器必須為每個子節點指定一個惟一的恢復標識符。在 UIKit 中,一些容器視圖控制器會自動消除其子視圖控制器的歧義,允許您為每個子節點使用相同的恢復標識符。
例如,UINavigationController 根據它在導航堆棧中的位置給每個子元素添加信息。有關給定視圖控制器的行為的更多信息,請參見相應的類引用。
有關如何使用恢復標識符和恢復路徑來創建視圖控制器的更多信息,請參閱[ Restoring View Controllers at Launch Time](https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/PreservingandRestoringState.html#//apple_ref/doc/uid/TP40007457-CH28-SW5)。
#### 排除視圖控制器組
要從還原過程中排除整個視圖控制器組,請將父視圖控制器的還原標識符設置為 `nil` 。 圖7-1顯示了將視圖控制器層次結構中的恢復標識符設置為 `nil` 的影響。 缺少保存數據可以防止視圖控制器稍后被恢復。
###### 圖 7-1 從自動保存過程中排除視圖控制器

排除一個或多個視圖控制器在隨后的恢復過程中不會刪除它們全部。在啟動時,您的應用程序的默認設置中的任何一個視圖控制器仍然被創建,如圖 7-2 所示 。

從自動保存過程中排除視圖控制器不會阻止您手動保存視圖控制器。 在恢復歸檔中保存對視圖控制器的引用可保留視圖控制器及其狀態信息。 例如,如果圖 7-1 中的應用程序委托保存了導航控制器的三個子項,則它們的狀態將被保留。 在還原期間,app delegate 可以重新創建這些視圖控制器并將其推送到導航控制器的堆棧中。
#### 保留視圖控制器的視圖
有些視圖具有與視圖相關的附加狀態信息,而不是與父視圖控制器相關。例如,滾動視圖具有可能想要保存的滾動位置。當視圖控制器負責提供滾動視圖的內容時,滾動視圖本身負責保存它的視覺狀態。
要保存視圖的狀態,請執行以下操作:
* 將一個有效字符串分配給視圖的 `restorationIdentifier` 屬性。
* 使用具有有效恢復標識符的視圖控制器中的視圖。
* 表視圖和視圖集合,分配一個數據來源,遵守 `UIDataSourceModelAssociation` 協議。
將一個恢復標識符分配給一個視圖告訴UIKit它應該將視圖的狀態寫入保存存檔。稍后恢復視圖控制器時,UIKit 還會恢復任何具有恢復標識符的視圖的狀態。
### 在啟動時恢復視圖控制器
在啟動時,UIKit 會嘗試將您的應用恢復到之前的狀態。 那時,UIKit 會要求您的應用程序創建(或定位)包含您保存的用戶界面的視圖控制器對象。 嘗試定位視圖控制器時,UIKit 按以下順序搜索:
1. **如果視圖控制器有恢復類,UIKit會要求該類提供視圖控制器**。 UIKit調用恢復相關類的 `viewControllerWithRestorationIdentifierPath:coder:` 方法來檢索視圖控制器。 如果該方法返回 `nil` ,則假定應用程序不想重新創建視圖控制器,并且 UIKit 停止查找它。
2. **如果視圖控制器沒有恢復類,則 UIKit 會要求 app delegate 提供視圖控制器**。 UIKit 調用 app delegate 的 `application:viewControllerWithRestorationIdentifierPath:coder:` 方法來查找沒有恢復類的視圖控制器。 如果該方法返回 nil,則UIKit將嘗試隱式查找視圖控制器。
3. **如果具有正確還原路徑的視圖控制器已經存在,則UIKit將使用該對象**。 如果您的應用程序在啟動時創建視圖控制器(以編程方式或通過從故事板加載視圖控制器),并且這些視圖控制器具有恢復標識符,則UIKit會根據其恢復路徑隱式查找它們。
4. **如果視圖控制器最初是從一個故事板文件加載的,UIKit 會使用保存的故事板信息來定位和創建它**。UIKit在恢復存檔中保存關于視圖控制器的故事板的信息。在恢復時,UIKit 使用這些信息來定位相同的故事板文件,如果視圖控制器沒有被其他方法發現,則實例化相應的視圖控制器。
為視圖控制器分配一個恢復類可以防止 UIKit 隱式地搜索該視圖控制器。 使用恢復類可以更好地控制是否真的要創建視圖控制器。 例如,如果您的類確定不應該重新創建視圖控制器,則您的 `viewControllerWithRestorationIdentifierPath:coder:` 方法可以返回 `nil` 。 當沒有恢復類時,UIKit 會盡其所能找到或創建視圖控制器并將其恢復。
當使用恢復類時,您的 `viewControllerWithRestorationIdentifierPath:coder:` 方法應該創建一個新的類實例,執行最小化的初始化,并返回結果對象。 清單 7-1 顯示了一個如何使用這個方法從故事板加載視圖控制器的例子。 由于視圖控制器最初是從故事板加載的,因此此方法使用 `UIStateRestorationViewControllerStoryboardKey` 鍵從檔案中獲取故事板。 請注意,此方法不會嘗試配置視圖控制器的數據字段。當視圖控制器的狀態被解碼時,這個步驟就會發生。
###### 清單7-1在恢復期間創建一個新的視圖控制器
~~~
+ (UIViewController*) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents
coder:(NSCoder *)coder
{
MyViewController* vc;
UIStoryboard* sb = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey];
if (sb) {
vc = (PushViewController*)[sb instantiateViewControllerWithIdentifier:@"MyViewController"];
vc.restorationIdentifier = [identifierComponents lastObject];
vc.restorationClass = [MyViewController class];
}
return vc;
}
~~~
重新分配恢復標識符和恢復類是手動重新創建視圖控制器時采用的良好習慣。 恢復恢復標識符的最簡單方法是獲取 `identifierComponents` 數組中的最后一項,并將其分配給您的視圖控制器。
對于在啟動時從app的主故事板文件中創建的對象,不要創建每個對象的新實例。讓 UIKit 隱式查找這些對象,或使用應用程序的 `viewControllerWithRestorationIdentifierPath:coder:` 方法來查找現有對象。
### 編碼和解碼您的視圖控制器的狀態
對于要保存的每個對象,UIKit 都會調用對象的 `encodeRestorableStateWithCoder:` 方法,使其有機會保存其狀態。 在恢復過程中,UIKit 調用匹配的 `decodeRestorableStateWithCoder:` 方法來解碼該狀態并將其應用于對象。 這些方法的實現是可選的,但對于視圖控制器來說,建議使用。 您可以使用它們來保存和恢復以下類型的信息:
* * 對正在顯示的任何數據的引用(不是數據本身)
* 對于容器視圖控制器來說,它的子視圖控制器的引用
* 關于當前選擇的信息
* 對于具有用戶可配置視圖的視圖控制器,關于該視圖的當前配置的信息。
在您的編碼和解碼方法中,您可以對編碼器支持的對象和任何數據類型進行編碼。 對于除視圖和視圖控制器以外的所有對象,對象必須采用 NSCoding 協議,并使用該協議的方法來寫入其狀態。 對于視圖和視圖控制器,編碼器不使用 NSCoding 協議來保存對象的狀態。相反,編碼器保存對象的恢復標識符并將其添加到可保存對象的列表中,這將導致對象的 `encodeRestorableStateWithCoder:` 方法被調用。
視圖控制器的 `encodeRestorableStateWithCoder:` 和 `decodeRestorableStateWithCoder:` 方法必須在其實現的某個時刻實現父類調用。 父類調用讓父類有機會保存和恢復任何附加信息。 清單 7-2 顯示了這些方法的一個示例實現,它們保存用于標識指定視圖控制器的數字值。
###### 清單 7-2 編碼和解碼視圖控制器的狀態
~~~
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
[super encodeRestorableStateWithCoder:coder];
[coder encodeInt:self.number forKey:MyViewControllerNumber];
}
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder
{
[super decodeRestorableStateWithCoder:coder];
self.number = [coder decodeIntForKey:MyViewControllerNumber];
}
~~~
編碼器對象在編碼和解碼過程中不共享。 每個具有可保存狀態的對象都會收到自己的編碼器對象。 使用唯一的編碼器意味著您不必擔心您的密鑰之間的命名空間沖突。但是,請不要使用鍵名稱。 UIKit使用這些鍵來存儲關于視圖控制器狀態的附加信息。
* UIApplicationStateRestorationBundleVersionKey,
* UIApplicationStateRestorationUserInterfaceIdiomKey
* UIStateRestorationViewControllerStoryboard
有關實現視圖控制器的編碼和解碼方法的更多信息,請參閱 [UIViewController Class Reference](https://developer.apple.com/documentation/uikit/uiviewcontroller) 。
### 保存和恢復視圖控制器的提示
在視圖控制器中添加對狀態保存和恢復的支持時,請考慮以下準則:
* **記住,您可能不希望保留所有視圖控制器**。在某些情況下,保存視圖控制器可能沒有意義。例如,如果應用程序顯示了更改,您可能希望取消操作,并將應用程序恢復到前一個屏幕。在這種情況下,您將不保存請求新密碼信息的視圖控制器。
* **在恢復過程中避免更換視圖控制器類**。狀態保存系統對它保存的視圖控制器的類進行編碼。在恢復期間,如果您的應用程序返回一個對象,該對象的類與原始對象不匹配(或不是一個子類),系統就不會請求視圖控制器解碼任何狀態信息。因此,將舊的視圖控制器替換為完全不同的控制器并不能恢復對象的全部狀態。
* **狀態保護系統希望您能夠按照預期的方式使用視圖控制器**。 恢復過程依賴于視圖控制器的包含關系來重建您的界面。 如果您沒有正確使用容器視圖控制器,保存系統將找不到您的視圖控制器。 例如,除非在相應的視圖控制器之間存在包含關系,否則不要將視圖控制器的視圖嵌入到不同的視圖中。