本文由唧唧歪歪翻譯自Apple文檔 On-Demand Resources Guide
該文檔的上部分包含:按需加載資源基礎以及創建和編輯tag兩部分
(三)管理按需加載資源
下載和管理按需加載資源是由操作系統完成的。app使用NSBundleResourceRequest來:
請求獲取按需加載資源。
通知操作系統某些資源不再使用。
更新下載的優先級。
追蹤下載的進度。
檢查存儲空間不足的通知。
當已下載的某些資源不再使用時,可以用NSBundle中的方法來設置保存優先級。
按需加載資源使用下面的4個方法來管理。
app分配並初始化一個NSBundleResourceRequest對象。需要管理的tag必須在初始化時指定,不能更改。
app請求獲取一個tag下的資源。如果這些資源需要下載,可以更新下載的優先級,追蹤下載進度。如果發生錯誤了,可以采取適當的措施。錯誤包括無效的tag、沒有網絡連接、無權使用蜂窩移動數據、沒有足夠的空間等等。
app使用這些資源。這些資源的使用方式和該類型的其他資源一樣。
app結束獲取這些資源,並通知系統不再使用這些資源。
在tag下載到設備後的任何時間都可以設置tag的保存優先級。
注意:每個NSBundleResourceRequest對象都只能用於一個請求訪問/結束訪問循環。
請求訪問
app必須在使用tag的資源之前先請求訪問這些tag。請求訪問的第一步是為tag創建一個NSBundleResourceRequest對象。一個tag可以由多個NSBundleResourceRequest對象來管理。
每個NSBundleResourceRequest實例管理同一個bundle下的加了tag的資源。使用下面的兩個方法來在初始化時設置被管理的tag和bundle:
如果資源都在app的main bundle中,使用 initWithTags:。
如果資源都在同一個自定義bundle中,使用 initWithTags:bundle: 。
注意:bundle可以設置為main bundle。
列表4-1展示了一個初始化資源管理器的一個例子,所有加tag的資源都在main bundle中。
列表4-1 初始化一個NSBundleResourceRequest實例
// Initialize an NSBundleResourceRequest with the desired tags NSSet *tags = [NSSet setWithArray: @[@"birds", @"bridge", @"city"]]; // All the resources are in the main bundle so use the shorter initialization method resourceRequest = [[NSBundleResourceRequest alloc] initWithTags:tags];
注意:tag和bundle只能在初始化時設置。
請求訪問資源
在初始化NSBundleResourceRequest實例之後,就是請求訪問了。當請求的所有tag下的所有資源都在本地存儲中時,操作系統會持有這些資源,並使用回調通知app這些資源已經可以使用了。更多信息參見第三步按需加載資源的生命周期。
有兩個方法來請求訪問。當資源已在設備上時,這兩個方法都可以允許訪問。不同的是當資源不在設備上時會做什麼。
beginAccessingResourcesWithCompletionHandler: 會從 App Store下載這些資源。
conditionallyBeginAccessingResourcesWithCompletionHandler: 不會下載資源。
兩個方法都會在回調block中返回結果。所有的資源都必須已經在設備上才能使用。列表4-2展示了方法beginAccessingResourcesWithCompletionHandler:。
列表4-2 使用beginAccessingResourcesWithCompletionHandler:
// Request access to the tags for this resource request [resourceRequest beginAccessingResourcesWithCompletionHandler: ^(NSError * __nullable error) { // Check if there is an error if (error) { // There is a problem, update app state (should inform user if appropriate) self.resourcesLoaded = NO; return; } // the resources associated with the the tags are loaded, start using them self.resourcesAvailable = YES; } ];
注意:在允許訪問之後,不要使用同一個NSBundleResourceRequest實例再次請求訪問。
檢查tag是否已在設備上
有時當tag不在設備上時,你並不想開始下載它們。例如,當設備使用低帶寬網絡,並且高質量的圖片和聲音不在設備上時,你可以使用低質量資源。
當tag已在設備上,conditionallyBeginAccessingResourcesWithCompletionHandler:會允許訪問。如果tag不在設備上,app需要調用beginAccessingResourcesWithCompletionHandler:來下載它們。列表4-3展示了一個檢查tag是否在設備上的例子。
注意:如果conditionallyBeginAccessingResourcesWithCompletionHandler:返回YES,就不要調用beginAccessingResourcesWithCompletionHandler:了。
列表4-3 使用conditionallyBeginAccessingResourcesWithCompletionHandler:
// Request access to tags that may already be on the device [resourceRequest conditionallyBeginAccessingResourcesWithCompletionHandler: ^(BOOL resourcesAvailable) { // Check if the resources are available if (resourcesAvailable) { // the resources associated with the the tags are loaded, start using them self.highQualityResourcesAvailable = YES; } else { // The resources are not on the device and need to be loaded // Queue up a call to custom method for loading the tags using // beginAccessingResourcesWithCompletionHandler: dispatch_async(dispatch_get_main_queue(), ^{ [self loadLowerQualityTags]; } } } ];
何時請求tag
因為從App Store下載tag會花一些時間,你可以在需要使用tag之前請求tag。下載時間取決於總共要下載的大小,網絡連接的速度,以及操作系統能分配多少資源來處理下載。
在沒有帶寬限制的理想情況,在300Mbps的 802.11n或者LTE網絡(299.6Mbps)上,下載一個64MB的tag至少要用1.7秒。但實際情況是連接到因特網的速度要遠低於300Mbps。
下載優先級
資源請求有一個默認的優先級,這可以隨時更改,包括下載時。低優先級使用更少的操作系統資源,為其他任務騰出資源。這也會降低下載速度。低優先級有利於最大化app執行效率。列表4-4展示了一個更改請求優先級的例子。
列表4-4 更改下載優先級
// The priority is a between 0.0 and 1.0 self.resourceRequest.loadingPriority = 0.1;
提高下載優先級會使用更多的操作系統資源,相應地提高下載速度,降低app效率。如果下載很緊急,app可以將下載優先級設置為NSBundleResourceRequestLoadingPriorityUrgent。這會告訴操作系統盡可能多地分配資源來處理下載。一個使用場景就是用戶在下載完成之前什麼也做不了。列表4-5展示了一個當用戶需要等待時,設置緊急優先級的例子
列表4-5 提高請求的優先級
// Raise the priority based on the urgency if (self.userWaiting) { // The user is waiting, request the maximum download time self.resourceRequest.loadingPriority = NSBundleResourceRequestLoadingPriorityUrgent; } else { // Set a higher priority self.resourceRequest.loadingPriority = 0.8; }
上面的代碼直接使用了一個固定浮點數設置優先級。你也可以根據app的效率來更新下載優先級。
追蹤下載進度
在下載開始之後,請求會開始更新progress,這是一個NSProgress類型的屬性。app通過對progress.fractionCompleted進行KVO來追蹤下載進度。這需要開始和結束觀察,以及添加當值改變時執行的代碼。列表4-6展示了如何開始和結束觀察進度。列表4-7展示了當值改變時執行的代碼。
列表4-6 開始和結束追蹤下載進度
// Start observing fractionCompleted for the progress [self.resourceRequest.progress addObserver:self forKeyPath:@"fractionCompleted" options:NSKeyValueObservingOptionNew context:NULL]; // Stop observing fractionCompleted for the progress [self.resourceRequest.progress removeObserver:self forKeyPath:@"fractionCompleted"];
列表4-7 當fractionCompleted的值改變時執行的代碼
// -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { // Check for the progress object and key if ((object == self.resourceRequest.progress) && ([keyPath isEqualToString:@"fractionCompleted])) { double progressSoFar = self.resourceRequest.progress.fractionCompleted; // do something with the value } }
追蹤下載的兩個重要用途是:
調整下載優先級。如果下載時間過長可以提高優先級,如果時間充裕可以降低優先級。
為用戶提供下載進度反饋。可以使用一個簡單的進度條來反饋fractionCompleted的值。
暫停和取消下載
正在進行的下載可以被暫停、恢復、取消。著通過progress屬性,以及NSProgress提供的方法來完成。更多信息參見NSProgress類參考。
列表4-8 暫停、恢復、取消當前的下載
// Pause the current download [self.resourceRequest.progress pause]; // Resume the current download [self.resourceRequest.progress resume]; // Cancel the current download [self.resourceRequest.progress cancel];
結束訪問
當app不再使用資源時,結束訪問能讓操作系統可以回收存儲空間。這也就是按需加載資源基礎中按需加載資源的生命周期的第4步。有2種方法結束訪問:
給請求發送endAccessingResources,如列表4-9所示。
釋放這個請求。
列表4-9 結束對tag的訪問
// End access by calling this method or deallocating the NSBUndleResourceRequest instance [self.resourceRequest endAccessingResources];
在endAccessingResources調用之後,這個請求就不能再用於請求訪問了。如果app還需要訪問同一個tag,需要再重新創建一個NSBundleResourceRequest實例。
設置保留優先級
某些tag中的資源可能比其他的更重要。例如,應用內購買或者基本功能的資源就會被更頻繁地用到。app可以為這些tag設置一個高保留優先級。當操作系統開始清理tag時,會從最低保留優先級開始。
可以使用NSBundle的方法來設置和檢查保留優先級。
列表4-10 為tag檢查和設置保留優先級
// Check the preservation priority for the llama in-app purchase module double currentPriority = [[NSBundle mainBundle] preservationPriorityForTag:@"iap-llamas"]; // Set the priority to the maximum of 1.0 (the default is 0.0) // The call to set the priority takes a set of tags NSSet *tags = [NSSet setWithArray: @[@"iap-llamas"]]; [[NSBundle mainBundle] setPreservationPriority:1.0 forTags:tags];
低存儲空間警告
當操作系統沒有辦法為當前正在請求的資源釋放出足夠的空間時,系統會發出一個通知。你的app應該停止訪問所有不再使用的tag,如上面結束訪問描述的。如果操作系統不能釋放出足夠空間,app會被終止。
例如,在一個有多個關卡的游戲中,用戶正在第4關,app請求第3、5、6關的tag。當低存儲空間警告發生時,app可以釋放第3、6關的tag。列表4-11展示了注冊低存儲空間通知的代碼。列表4-12展示了釋放不需要tag的方法。
列表4-11 注冊NSBundleResourceRequestLowDiskSpaceNotification通知
// End access by calling this method or deallocating the NSBUndleResourceRequest instance [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(lowDiskSpace:) name:NSBundleResourceRequestLowDiskSpaceNotification object:nil];
注冊通知一般由app delegate或者主view來完成。
列表4-12 低存儲空間通知的處理
// Notification handler for low disk space warning -(void)lowDiskSpace:(NSNotification*)theNotification { // Free the lower priority resource requests for (NSBundleResourceRequest *atRequest in self.lowPriorityRequests) { // End accessing the resources [atRequest endAccessingResources]; } // clear lowPriorityRequests preventing multiple calls to endAccesingResource [self.lowPriorityRequests removeAllObjects]; }
lowPriorityRequests 不是由操作系統提供的。它是一個需要由app創建和維持的mutable set 。
(四)調試
在你開發的過程中可能會遇到幾類問題。不同類別的問題需要使用不同的工具來調試。主要有以下幾類問題:
網絡連接:慢速或無網絡連接產生的問題。
本地存儲空間:本地存儲空間不足無法下載。
意外狀態:tag處於意外狀態。例如,一個tag顯示正在使用中,但app的所有模塊都已經結束訪問了。
意外狀態
調試意外狀態最有用的工具就是Xcode中的磁盤儀表了。磁盤儀表中會顯示tag的當前狀態,如下圖所示。
磁盤儀表會顯示每個tag的大小和狀態。大小是針對當前設備裁切後的。
tag的狀態
表5-1描述了磁盤儀表中tag可能處於的狀態。
表5-1 tag的狀態
使用磁盤儀表
在模擬器或真機上運行app都可以使用磁盤儀表。
打開磁盤儀表。
選擇View > Navigators > Show Debug Navigator。
使用Scheme彈框選擇一個target和設備。
選擇Product > Run來啟動app。app會在選擇的設備上啟動,並連接調試器。
在Debug Navigator中的列表中點擊磁盤儀表。磁盤儀表會在workspace窗口的內容區顯示。
向下滾到直到顯示資源分類。你可以調整內容的大小來顯示全部tag。
附錄A:資源類型
下表列出了可以加tag的資源類型。
表A-1 資源類型
數據文件可以包括除了可執行的Swift、Objective-C、C、或者C++二進制包以外的任何類型數據。由腳本語言生成的文件可以用作資源。
附錄B:資源大小限制
內存大小
在App Store 提交時和app運行時,資源的使用的內存大小是有限制的。
表B-1 資源大小
Slicing。表示這個大小是在App裁切之前還是之後。
app二進制包。表示裁切後的下載到設備上的安裝包大小。
Initial install tags。裁切後標為初始安裝tag的全部大小。
Initial install and prefetched tags。裁切後標為初始安裝和預獲取tag的全部大小。
In use on-demand resources。裁切後app在任何時刻使用中的tag的大小。只要有一個NSBundleResourceRequest 對象訪問tag,tag就算是在使用中。
Hosted on-demand resources。由App Store托管的未裁切的大小。
該文檔的上部分包含:按需加載資源基礎以及創建和編輯tag兩部分