iOS9-by-Tutorials-學習筆記四:APP-瘦身
iOS9-by-Tutorials-學習筆記四:APP-瘦身
這篇文章在書中的標題是App Thinning,這裡我給翻譯成了App 瘦身。
本文然然進行了一些文法的修改,很開心她為我修改這些東西。她說我轉折只會用但是,被她這麼一說想想還真是只是會用但是,嘿嘿。
iPhone經過這幾年的發展,已經發生了很大的變化,例如螢幕變得更加多樣,尺寸更多,記憶體變得更大,CPU的架構也在變化。伴隨著iPhone的變化,iOS也在變化,例如AutoLayout、size classes、split view controller等。這些技術及裝置的變化給我在開發的過程中也造成了許多的問題,不僅如此蘋果通過不斷推出新的技術,努力在協助我們使用同一套代碼開發適應多個裝置的Universal的App。另一方面Universal App雖然在開發的過程中,方便了我們開發人員,可是對於使用者來說就不那麼友好了,由於需要適配多種裝置,所以裡包含所有裝置的代碼,但真正的在啟動並執行時候,我們並不需要那麼多相關的代碼及資源。
例如下面的一張圖,是一個App運行在iPhone 6+上,使用的各個資源相關的情況:
中對勾標出來的是在iPhone 6+上真實啟動並執行時候使用到的相關的資源及代碼,對比有對勾的部分,更多的是沒有被對勾標出來的部分。可以想象我們下載了一個App(前提這個App是Universal的),然後至少一半的代碼及資源是我們不需要的,白白佔用著我們的空間。這樣對使用者體驗也不好。為瞭解決這個問題蘋果在iOS 9給出了新的解決方案:
App Slicing 當你提交你的iOS 9 打包檔案到App Store的時候,蘋果編譯你的資源和可執行檔,然後為每個裝置產生一個特定的可執行檔。最終,裝置只會下載適應與其特性的,並且它使用到的內容。這些特性包含顯卡效能(原文單詞:graphics capabilities)、記憶體層級、CPU架構、size classes、螢幕 scaling等。 On Demand Resouces 應用程式的資源只有在需要使用的時候才會下載,並且如果其他資源需要空間這些資源可以被移除。 Bitcode 在你提交App到App Store的時候,Bitcode可以作為中間產物一起提交。包含bitcode配置的程式將會在App store上被編譯和連結。bitcode允許蘋果在後期重新最佳化我們程式的二進位檔案,而不需要我們重新提交一個新的版本到App store上。
這三個技術加起來,統一稱為App Thinning。
Getting started
開啟本章節的初始項目,然後選在iPad Air 2運行,這時候運行效果如下:
伴隨著模擬器啟動起來的還開啟了一個Finder視窗:
這個Finder視窗能夠開啟,是因為在程式中添加了一個指令碼,每次啟動並執行時候都會執行,指令碼所在地方如下:
echo "App Size in KB: `du -sk \"${CONFIGURATION_BUILD_DIR}/${EXECUTABLE_NAME}.app\"`"if [ "${CONFIGURATION}" = "Debug" ]; thenopen ${CONFIGURATION_BUILD_DIR}fi
在Finder的Old CA Maps點擊右鍵,選擇顯示包內容,如下:
中標註的說明如下:
1. Assets.car是Assets.xcassets被Xcode進行編譯後的檔案。
2. Old CA Maps是真實運行在裝置上的可執行檔。
3. Santa Cruz PNGs 這個是圖片檔案,但是沒有被編譯到Assets.car檔案中,這是因為它並沒有放到Assets.xcassets中,而是放到了工程的頂層檔案中。
4. SD_Map.bundle 這個就是地圖圖片檔案,但是將近120MB。
Measuring your work
本章介紹一些App瘦身相關的東西,所以我們必須能夠測量App是否減少了。工程裡面已經內建了一個指令碼(上面代碼裡面有),能夠在build的過程中輸出App的大小。查看的位置如下:
Slicing up app slicing
App slicing包含兩部分內容:可執行檔分區(Executable slicing)和資源分區(resource slicing)。
Executable slicing 指的是在裝置下載App的時候會根據裝置的相關資訊只是下載對應該裝置的相關的可執行檔,並不會包含其他裝置及架構的可執行檔,達到App安裝包的縮小。並且這個功能並不需要我們做太多,App Store預設支援的。
預設情況下提交到App Store的包是包含所有的內容的,這些都在設定檔裡面,App Store會自動建立對應於每個類型的可執行檔。這個在iOS9+上支援。
Being smart with resources
Resource slicing 需要我們一小部分簡單的工作就能實現。如果使用Resource slicing,則要保證我們的資源都被Asset Catalogs管理。在Xcode 7中,能夠標記資源被使用裝置的 Memory 和 Graphics ,如下:
Your first fix
在開始的時候介紹過Santa Cruz PNGs這個檔案因為被放到Main bundle中,所以不能被編譯進入到Assets.car,進而也不能使用Resource slicing。下面看一下我們怎麼修改,使其能夠使用:
選擇New Image Set後,將新加入的set命名為Santa Cruz,緊接著做如下操作:
糾正一下 左邊的內容應該是刪除,包括在Finder內也應該刪除<喎?http://www.bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjwvYmxvY2txdW90ZT4NCjxwPsi7uvPU2rK7zay1xMnosbjJz9TL0NBBcHCjrNfuuvO3os/WQXNzZXQuY2FyzsS8/rXEtPPQobKisrvSu9bCoaPV4rj2ysfS8s6q1NqwstewtcTKsbryo6y74bj5vt3J6LG4sLLXsLbU06a1xNfK1LShozwvcD4NCjxoNSBpZD0="lazily-downloading-content">Lazily (down)loading content
蘋果提供On-Demand Resources技術,簡稱ODR。ODR允許你將資源儲存在蘋果的伺服器上,然後在你App使用的時候再去下載。NSBundleResourceRequest是處理ODR的類,使用這個類能夠通過tag下載對應的資源。images, data, OpenGL shaders, SpriteKit Particles, Watchkit Complications等都可以使用ODR。
Wire things up to use tags
下面我們修改代碼,實現資源的下載,修改MapChromeViewController.swift對應方法如下:
private func downloadAndDisplayMapOverlay() {// displayOverlayFromBundle(NSBundle.mainBundle()) guard let bundleTitle = mapOverlayData?.bundleTitle else { return } let bundleResource = NSBundleResourceRequest(tags: [bundleTitle]) bundleResource.beginAccessingResourcesWithCompletionHandler { [weak self] (error) -> Void in NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in if error == nil { self?.displayOverlayFromBundle(bundleResource.bundle) } }) } }
這時候我們運行代碼,可能會在控制台輸出錯誤,這是因為我們對應的bundle並沒有tag,我們需要給bundle添加tag:
然後我們重新編譯運行我們的程式,然後按照上面的查看編譯啟動並執行程式的大小,發現小了許多。對比之前的編譯產生的檔案,發現運行檔案裡面不包含bundle了。
如果你的App在App Store上可能這個資源檔下載的很慢。但是在開發的過程中,Xcode會利用本網作為伺服器,然後在裝置上能夠下載到,所以在開發的過程中如果電腦關了,那ODR也就不能使用了。
Make it download faster
在我們使用ODR的過程中,如果bundle比較大,可能再下載的過程中就會比較耗時,並且在下載過程中使用者不知道,這樣使用者體驗就不好。我們可以再Resource下載的過程中給使用者一些提示,修改下面的代碼:
// add 為新添加的 ProgressView是程式已經添加上的private func downloadAndDisplayMapOverlay() {// displayOverlayFromBundle(NSBundle.mainBundle()) guard let bundleTitle = mapOverlayData?.bundleTitle else { return } let bundleResource = NSBundleResourceRequest(tags: [bundleTitle]) bundleResource.loadingPriority = NSBundleResourceRequestLoadingPriorityUrgent //add loadingProgressView.observedProgress = bundleResource.progress // add loadingProgressView.hidden = false // add UIApplication.sharedApplication().networkActivityIndicatorVisible = true // add bundleResource.beginAccessingResourcesWithCompletionHandler { [weak self] (error) -> Void in NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in self?.loadingProgressView.hidden = true // add UIApplication.sharedApplication().networkActivityIndicatorVisible = false // add if error == nil { self?.displayOverlayFromBundle(bundleResource.bundle) } }) }}
如果使用者已經下載過某個bundle,下次在使用的時候就不會再去下載了。
The many flavors of tagging
雖然添加了ProgressView,在體驗是好了一點,但是需要注意測試的時候是使用的本地的網路,所以比較快,但是如果要是提交到App Store上,那可能下載就是比較慢了,如果再配上使用者沒有WiFi那可能就沒法用了,所以我們還需要做其他的一些調整。
Initial install tags
使用Initial install tags,我們可以設定哪些bundle會在我們App初始化安裝的時候就會被下載。 下面下介紹一下ODR三種下載的時機吧:
* Initial Install Tags 在ipa下載的時候一同下載
* Prefetched Tag Order 在程式下載完成後,下載對應的資源,然後按順序排列。
* Prefetched Tag Order 按需下載
下面是配置的地方:
Purging content
應用程式在使用的過程中通過ODR下載了對應的bundle,但是有時候我們需要清理一些已經下載過的並且不使用的bundle。在介紹怎麼刪除之前先看一下怎麼查看下載的ODR:
Set a resource to be purged
在MapChromeViewController.swift添加如下代碼:
// new add 是新加的代碼 var overlayBundleResource: NSBundleResourceRequest? // new add private func downloadAndDisplayMapOverlay() {// displayOverlayFromBundle(NSBundle.mainBundle()) guard let bundleTitle = mapOverlayData?.bundleTitle else { return } let bundleResource = NSBundleResourceRequest(tags: [bundleTitle]) overlayBundleResource = bundleResource // new add bundleResource.loadingPriority = NSBundleResourceRequestLoadingPriorityUrgent //add loadingProgressView.observedProgress = bundleResource.progress // add loadingProgressView.hidden = false // add UIApplication.sharedApplication().networkActivityIndicatorVisible = true // add bundleResource.beginAccessingResourcesWithCompletionHandler { [weak self] (error) -> Void in NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in self?.loadingProgressView.hidden = true // add UIApplication.sharedApplication().networkActivityIndicatorVisible = false // add if error == nil { self?.displayOverlayFromBundle(bundleResource.bundle) } }) } } // new add override func viewDidDisappear(animated: Bool) { super.viewDidDisappear(animated) // 告訴系統結束了對資源的訪問 overlayBundleResource?.endAccessingResources() }
上面的代碼,我做測試的時候不清楚會在什麼時候會刪除,我也類比了記憶體警告,如果誰清楚,還請告訴我,謝謝。
堅持了好幾天中午寫完了,這篇筆記,一篇筆記13張,好累。