《高性能iOS應(yīng)用開發(fā)》是一本質(zhì)量很高的 iOS 書籍秩霍,我從此書中系統(tǒng)的學(xué)到了很多東西篙悯。這篇博客是《高性能iOS應(yīng)用開發(fā)》一書第三部分“iOS性能”的讀書筆記,因?yàn)槲覍?APP 性能比較感興趣铃绒,所以就先從第三部分“iOS性能”開始了鸽照。
1. 應(yīng)用啟動
iOS 應(yīng)用在啟動時會調(diào)用 UIApplicationMain
方法,并傳入 UIApplicationDelegate
類的引用颠悬。委托接收應(yīng)用范圍的事件矮燎,并且有明確的生命周期,application:didFinishLaunchingWithOptions:
方法表明應(yīng)用已經(jīng)啟動赔癌。
應(yīng)用的窗口有一個 rootViewController诞外,對應(yīng)的 UIViewController 對象同樣具有明確的生命周期。UIViewController 的方法viewDidAppear:
執(zhí)行時灾票,說明啟動已完成峡谊。
APP 啟動過程中,應(yīng)盡量減少不必要的操作刊苍,從而縮短應(yīng)用的啟動時長既们,實(shí)現(xiàn)更好的用戶體驗(yàn)。應(yīng)用有四種啟動類型班缰。
1.1 首次啟動
安裝應(yīng)用后的首次啟動贤壁。此時沒有之前的狀態(tài)悼枢,也沒有本地緩存埠忘。這意味著將會出現(xiàn)以下兩種情況中的一種:沒有需要加載的內(nèi)容(因此加載時間會縮短),或者需要從服務(wù)器上下載初始數(shù)據(jù)(可能需要很長的加載時間)。在應(yīng)用首次啟動時莹妒,你可以選擇提供引導(dǎo)圖來總結(jié)應(yīng)用的功能和用法名船。
首次啟動時,應(yīng)用通常會執(zhí)行多個任務(wù):
- 加載應(yīng)用的默認(rèn)項(xiàng)(NSUserDefaults旨怠、捆綁的配置等)
- 檢查私有 / 測試版本
- 初始化應(yīng)用標(biāo)識符渠驼,包括但不限于對匿名用戶使用的供應(yīng)商標(biāo)識符(Identifier for
Vendor,IDFV)鉴腻、廣告標(biāo)識符(Identifier for Advertiser迷扇,IDFA)等 - 初始化崩潰報(bào)告系統(tǒng)
- 建立 A/B 測試
- 建立分析方法
- 使用操作或 GCD 建立網(wǎng)絡(luò)
- 建立 UI 基礎(chǔ)設(shè)施(導(dǎo)航、主題爽哎、初始 UI)
- 顯示登錄提示或從服務(wù)器加載最新內(nèi)容及其他更新
- 建立內(nèi)存緩存(如圖片緩存)
上述列舉的內(nèi)容只是應(yīng)用在首次啟動時可能執(zhí)行的任務(wù)蜓席。其中一些還會在后續(xù)啟動中執(zhí)行。問題是课锌,任務(wù)數(shù)量的快速增加必然會導(dǎo)致應(yīng)用的啟動速度變慢厨内。
那么怎么避免這樣的問題呢?可以遵循下述具體步驟渺贤,拆解任務(wù)列表雏胃,從而獲得更高的性能。
- 確定在展示 UI 前必須執(zhí)行的任務(wù)志鞍。如果應(yīng)用是第一次啟動瞭亮,那么沒有必要加載任何用戶偏好,如主題固棚、刷新間隔街州、緩存大小等。此時是沒有任何自定義值的玻孟。初始緩存肆意增長也是沒問題的唆缴,因?yàn)樗脑鲩L不會超過最終的限制值。崩潰報(bào)告系統(tǒng)應(yīng)該第一個被初始化黍翎。
- 按順序執(zhí)行任務(wù)面徽。排序是非常重要的,因?yàn)槿蝿?wù)之間可能具有相互依賴性匣掸,同時趟紊,排序還可以節(jié)省用戶的寶貴時間。
- 將任務(wù)拆分為兩類:一類是必須在主線程中執(zhí)行的任務(wù)碰酝,另一類是可以在其他線程中執(zhí)行的任務(wù) 霎匈,然后分別執(zhí)行。還可以進(jìn)一步將在非主線程中執(zhí)行的任務(wù)分為可以并發(fā)執(zhí)行的和不能并發(fā)執(zhí)行的送爸。
- 其他任務(wù)可以在加載 UI 后執(zhí)行或異步執(zhí)行铛嘱。延遲其他子系統(tǒng)(如記錄儀和分析方法)的初始化暖释。在應(yīng)用的后續(xù)階段將一些操作(例如,寫日志消息或跟蹤事件)放入隊(duì)列中墨吓,直到子系統(tǒng)完全完成初始化球匕。
1.2 冷啟動
應(yīng)用后續(xù)的啟動。在啟動期間帖烘,可能需要恢復(fù)原來的狀態(tài)亮曹,例如,游戲中達(dá)到的最高等級秘症、消息應(yīng)用中的聊天記錄照卦、新聞應(yīng)用中上一次同步的文章、已登錄用戶的證書乡摹,或者 僅僅是用戶已經(jīng)使用過的引導(dǎo)圖標(biāo)記符窄瘟。
冷啟動中一個較為重要的任務(wù)是,載入之前的狀態(tài)趟卸。在應(yīng)用中蹄葱,顯示給用戶(登錄后)的第一個畫面是 feed 流。如果用戶在以前的啟動中登錄過锄列,并且數(shù)據(jù)已經(jīng)同步图云,那我們就會考慮加載之前已經(jīng)緩存的 feed 流。
為了實(shí)現(xiàn)向用戶展示 feed 流的任務(wù)邻邮,必須向服務(wù)器請求最近的更新竣况,同時還要從本地緩存加載數(shù)據(jù)。這些行為是不用思考就知道的筒严。但是丹泉,以下幾點(diǎn)卻是不容忽視的。
- 展示有用且有意義的 UI 所需要的最少信息數(shù)目(min)鸭蛙。
- 記錄從本地緩存加載 M 條信息花費(fèi)的時間(記作 tl)摹恨。
- 記錄從服務(wù)器獲取最新的 M 條信息花費(fèi)的時間(記作 tr)。
- 為了獲得更快的速度娶视,任何時刻在內(nèi)存中存儲的最大信息數(shù)目(max)晒哄,特別是在快速滑動和滾動時。
1.3 熱(重)啟動
這是指當(dāng)應(yīng)用處于后臺肪获,但并未被掛起或關(guān)閉時寝凌,用戶切換至應(yīng)用而觸發(fā)的啟動。在這種情況下孝赫,當(dāng)用戶通過點(diǎn)擊應(yīng)用圖標(biāo)或深層鏈接返回應(yīng)用時较木,不會觸發(fā)啟動時的回調(diào),而是直接用 applicationDidBecomeActive:
(或 application:openURL:source:annotation:
)回調(diào)青柄。
通常來說伐债,這種情況和繼續(xù)執(zhí)行沒什么區(qū)別预侯,只是視圖控制器可能需要處理一些額外的事件。
熱啟動是指切換到一個已經(jīng)運(yùn)行了的應(yīng)用泳赋。兩個原因可能會使應(yīng)用變成非激活狀態(tài):一是用戶向下拉拽狀態(tài)欄,二是用戶點(diǎn)擊 home 鍵或切換至其他應(yīng)用喇喉。
熱啟動有兩種情境:
- 用戶點(diǎn)擊圖標(biāo)
- 應(yīng)用接收到深層鏈接
1.3.1 應(yīng)用重啟
當(dāng)用戶點(diǎn)擊應(yīng)用圖標(biāo)時祖今,一般不需要執(zhí)行其他特殊的操作。
應(yīng)用處于安全狀態(tài)拣技,或者運(yùn)行很多動畫時千诬,可以監(jiān)測背景和前景通知。在第一種情況下膏斤, 應(yīng)用每次進(jìn)入前景狀態(tài)時徐绑,都會展示登錄界面;在后一種情況下,動畫或者游戲狀態(tài)會被暫停莫辨,需要恢復(fù)傲茄。
1.3.2 深層鏈接
當(dāng)應(yīng)用接收到 application:openURL:sourceApplication:annotation:
回調(diào)時,期望能跳轉(zhuǎn)
到應(yīng)用的特定頁面沮榜,實(shí)現(xiàn)用戶想要完成的操作盘榨。但此時的目標(biāo)應(yīng)用可能已經(jīng)發(fā)生變化,處于某一特定狀態(tài)了遵绰。
如果深層鏈接需要從服務(wù)器獲取數(shù)據(jù)往史,那么可以先展示與深層鏈接相關(guān)的原始頁面憎夷,或者先展示一個進(jìn)度條,等從服務(wù)器獲取到了最新數(shù)據(jù)山憨,再執(zhí)行刷新操作。
1.4 升級后的啟動
應(yīng)用升級以后的啟動弥喉。通常而言郁竟,升級后的啟動與冷啟動沒有差別。但是由境,不同的啟動叫法表明了本地存儲發(fā)生變化的時刻是不同的枪孩,這些變化包括模式、內(nèi)容藻肄、之前版本掛起的同步操作蔑舞,以及內(nèi)部的 API/ 默認(rèn)依賴。
應(yīng)用升級后的首次啟動將遵循下列情形之一:
- 無本地緩存或應(yīng)用完全放棄緩存;
- 本地緩存可用嘹屯,可以直接使用或需要切換至升級版本攻询。
如果無本地緩存或應(yīng)用決定放棄緩存(例如,數(shù)據(jù)不可用或從服務(wù)器同步獲取更快)州弟,則不需要進(jìn)行特殊處理钧栖。本地?cái)?shù)據(jù)發(fā)生改變時通知用戶低零。以下的最佳實(shí)踐可以讓用戶有更好的體驗(yàn)。
- 如果本地緩存可用拯杠,通知用戶該情況掏婶。如果沒有遷移到本地緩存的必要,則無需通知用戶潭陪,因?yàn)楸镜鼐彺娴氖褂檬请[式的雄妥。
- 如果必須花幾分鐘對數(shù)據(jù)進(jìn)行遷移,那么向用戶展示一個可以推遲該操作的選項(xiàng)依溯。
- 如果從服務(wù)器檢索數(shù)據(jù)更快老厌、更容易,因而必須放棄本地緩存的使用黎炉,那么這種情況下需要通知用戶枝秤。
2. 用戶界面
當(dāng)與 UI 進(jìn)行交互時,大部分用戶才注意到性能問題慷嗜。如果某個應(yīng)用在數(shù)據(jù)同步和刷新上耗時較長淀弹,或用戶交互不夠穩(wěn)定,那么應(yīng)用會被認(rèn)為是遲鈍的庆械。
功耗垦页、網(wǎng)絡(luò)使用率、本地存儲等因素對用戶來說是不可見的干奢。因此痊焊,雖然這些因素是解決性能問題的要素,但 UI 卻是應(yīng)用的門面忿峻,如果 UI 反應(yīng)遲鈍薄啥,則必然會直接影響用戶的反饋。
還有一些無法控制的外部因素逛尚,如下垄惧。
- 網(wǎng)絡(luò)
- 弱網(wǎng)環(huán)境會增加同步所需的時間。
- 硬件
- 硬件越好绰寞,其提供的性能越高到逊。與舊型號的 iPhone 相比,搭載新系統(tǒng)的新 iPhone 執(zhí)行 速度更快滤钱。
- 存儲
- 應(yīng)用可以在存儲容量不同的設(shè)備上運(yùn)行觉壶,存儲容量小至 16GB,大到 128GB件缸,它們限制了應(yīng)用在本地離線緩存數(shù)據(jù)的規(guī)模铜靶。
2.1 視圖控制器
在應(yīng)用開發(fā)的最初階段,視圖控制器都較為精簡他炊,狀態(tài)較好争剿。隨著時間的推移已艰,這些視圖控制器慢慢變成了所有業(yè)務(wù)邏輯的垃圾場,代碼量也增長至幾千行蚕苇。雖然邏輯的“總量”是不可避免的哩掺,但將代碼重構(gòu)成短小、可復(fù)用的方法是很好的主意涩笤。這樣不僅能解除耦合嚼吞,還可以發(fā)現(xiàn)無用的、重復(fù)的代碼辆它。
下面列舉了創(chuàng)建視圖控制器時需要遵循的一些較為基本的最佳實(shí)踐誊薄。
- 保持視圖控制器輕量履恩。在 MVC 結(jié)構(gòu)的應(yīng)用中锰茉,控制器只是紐帶,而不是存放所有業(yè)務(wù)邏輯的地方切心。它甚至不屬于模型飒筑。業(yè)務(wù)邏輯應(yīng)該屬于服務(wù)層或業(yè)務(wù)邏輯組件。將它放在那里绽昏。
- 不要在視圖控制器中編寫動畫邏輯协屡。動畫可以在獨(dú)立的動畫類中實(shí)現(xiàn),該類接受視圖作為參數(shù)傳入全谤,這些視圖就是用來運(yùn)行動畫的視圖肤晓。然后,視圖控制器會將動畫添加至視圖或轉(zhuǎn)場效果上认然。
- 使用數(shù)據(jù)源和委托協(xié)議补憾,將代碼按照數(shù)據(jù)檢索、數(shù)據(jù)更新和其他的業(yè)務(wù)邏輯進(jìn)行分離卷员。 視圖控制器只能用來選擇正確的視圖盈匾,并將它們連接到供應(yīng)源。
- 視圖控制器響應(yīng)來自視圖的事件毕骡,如按鈕點(diǎn)擊事件或列表單元格的選擇事件削饵,然后將它們連接至數(shù)據(jù)接收器。
- 視圖控制器響應(yīng)來自操作系統(tǒng)的 UI 相關(guān)事件未巫,如方向變化或低內(nèi)存警告窿撬。這可能會觸發(fā)視圖的重新布局。
- 不要在視圖控制器中使用代碼手工布局 UI叙凡,也不要在視圖控制器中實(shí)現(xiàn)全部的 UI尤仍、視圖創(chuàng)建和視圖布局邏輯等操作。
- 比較好的方式是狭姨,創(chuàng)建一個實(shí)現(xiàn)了公共設(shè)置的基類視圖控制器宰啦,其他視圖控制器從這里繼承就好苏遥。
- 在各視圖控制器之間,使用 category 創(chuàng)建可復(fù)用的代碼赡模。如果父視圖控制器不能滿足使用(例如田炭,在應(yīng)用中需要不同種類的視圖控制器),那就創(chuàng)建 category漓柑,并在 category 中加上自定義的方法或?qū)傩浴?/li>
2.1.1 視圖加載
視圖初始化時會涉及兩個方法——loadView 和 viewDidLoad教硫。
如果通過覆寫 loadView 方法創(chuàng)建了自定義 UI,你需要牢記以下幾點(diǎn)辆布。
- 將 view 屬性設(shè)置到視圖層級的根上瞬矩。
- 確保視圖正被其他的視圖控制器所共享。
- 不要調(diào)用 [super loadView]锋玲。
在執(zhí)行過程中景用,應(yīng)該盡量縮短在 viewDidLoad 方法上花費(fèi)的時間。具體來講惭蹂,將要被渲染的數(shù)據(jù)應(yīng)該是已經(jīng)可用的伞插,或是在其他線程進(jìn)行加載的。在 viewDidLoad 的完成中發(fā)生的任何延遲盾碗,都將導(dǎo)致與視圖控制器相關(guān)的 UI 展示發(fā)生延遲媚污。用戶會卡在應(yīng)用啟動或前一個視圖控制器中。
2.1.2 視圖層級
展示出來的 UI 是由嵌套在樹形結(jié)構(gòu)中的各層次視圖組成的廷雅,它們的位置受自動布局或其他編排方式的約束耗美。視圖結(jié)構(gòu)和渲染包括以下步驟。
- (1) 構(gòu)造子視圖航缀。
- (2) 計(jì)算并提供約束商架。
- (3) 為子視圖遞歸地執(zhí)行步驟 1 和步驟 2。
- (4) 遞歸渲染谬盐。
視圖層次越復(fù)雜甸私,構(gòu)建和渲染視圖消耗的時間也就越長,因此要盡量減少視圖層級飞傀。
2.1.3 視圖可見性
視圖控制器提供了四個生命周期方法皇型,以接收有關(guān)視圖可視性的通知。
- viewWillAppear: 當(dāng)視圖層級已經(jīng)準(zhǔn)備好砸烦,且視圖即將被放入視圖窗口時弃鸦,此方法會被調(diào)用。在即將展示視圖控制器或之前入棧(modal 或者其他)的視圖控制器彈出時幢痘,這種情況就會發(fā)生唬格。在這個時刻,過渡動畫還未開始,視圖對終端用戶也是不可見的购岗。不要啟動任何視圖動畫汰聋,因?yàn)闆]有任何作用。
- viewDidAppear: 當(dāng)視圖在視圖窗口展示出來喊积,且過渡動畫完成后烹困,此方法會被調(diào)用。因?yàn)閯赢嫊馁M(fèi)約 300 毫秒乾吻,所以髓梅,對比 viewWillAppear: 和 viewDidLoad:,viewDidAppear: 和 viewWillAppear: 之間的時間差可能會比較大绎签。啟動或恢復(fù)任何想要呈現(xiàn)給用戶的視圖動畫枯饿。
- viewWillDisappear: 該方法表示視圖將要從屏幕上隱藏起來。這可能是因?yàn)槠渌晥D控制器想要接管屏幕诡必, 或該視圖控制器將要出棧奢方。
- viewDidDisappear: 當(dāng)上一個 / 下一個視圖控制器的過渡動畫完成時,此方法會被調(diào)用擒权。正如 viewDidAppear:袱巨,viewWillDisappear: 事件也會有約 300 毫秒的差值阁谆。
以下列舉了一些高效使用生命周期事件的最佳實(shí)踐碳抄。
- 無需多說,不要重寫 loadView场绿。
- 將 viewDidLoad 作為最后的檢查點(diǎn)剖效,查看來自數(shù)據(jù)源的數(shù)據(jù)是否可用。如果可用焰盗,則更新 UI 元素璧尸。
- 如果每次都需要展示最新的信息,那么就使用 viewWillAppear: 更新 UI 元素熬拒。
- 在 viewDidAppear: 中開始動畫爷光。如果有視頻等流式內(nèi)容,那么就可以開始播放了澎粟。訂 閱應(yīng)用事件來檢測動畫 / 視頻或其他持續(xù)更新視頻的處理是應(yīng)該繼續(xù)還是停止蛀序。不推薦在該方法中用最新的數(shù)據(jù)更新 UI。如果你這樣做了活烙,最終的效果是徐裸,在過渡動 畫完成之后,用戶會過渡至舊的 UI啸盏,然后產(chǎn)生更新重贺。這個體驗(yàn)不是很友好。
- 使用 viewWillDisappear: 來暫停或停止動畫气笙。同樣次企,不要做其他多余的操作。
- 使用 viewDidDisappear:銷毀內(nèi)存中的復(fù)雜數(shù)據(jù)結(jié)構(gòu)潜圃。也可以在這里注銷與視圖控制器綁定的數(shù)據(jù)源通知抒巢,以及與動畫、數(shù)據(jù)源秉犹、UI 更新有 關(guān)的應(yīng)用事件通知中心蛉谜。
2.2 視圖
優(yōu)化視圖方面最具挑戰(zhàn)性的部分是,很少有普適于所有視圖的技術(shù)崇堵。每個視圖都有其獨(dú)特
的用途型诚,且大部分的優(yōu)化技術(shù)都與特定的視圖和暴露出的 API 有關(guān)。
- 基本準(zhǔn)則:
- 盡量減少在主線程中所做的工作鸳劳。任何額外代碼的執(zhí)行都意味著更高的丟幀概率狰贯。過多的丟幀會導(dǎo)致不流暢。
- 避免較大的 nibs 或故事板赏廓。故事板很強(qiáng)大涵紊,但整個 XML 在真正使用之前必須被加載(I/O) 和解析(XML 處理)。應(yīng)該最小化故事板中的單元數(shù)目幔摸。
- 避免在視圖層次結(jié)構(gòu)中多層嵌套摸柄。盡量保持扁平化。
- 盡可能延遲加載視圖并進(jìn)行重用既忆。更多的視圖不僅會導(dǎo)致加載時間變長驱负,還會使渲染時間變長,這些會影響內(nèi)存和 CPU 的使用患雇。
- 對于復(fù)雜的 UI 而言跃脊,最好使用自定義繪圖。這樣只會觸發(fā)一個視圖進(jìn)行繪制苛吱,而不是多個子視圖酪术,同時也避免了調(diào)用代價較高的 layoutSubviews 和 drawRect: 方法。此外翠储,要避免使用具有通用目的及功能豐富的組件而帶來的消耗绘雁,你可以使用那些直接實(shí)現(xiàn)了繪制方法的視圖來代替。
2.2.1 UILabel
這可能是 iOS 上最常用的視圖了彰亥。它雖然看起來簡單咧七,但是渲染代價卻不容小覷。下列是涉及的一些復(fù)雜步驟任斋。
- 使用字體继阻、字體類型以及要被渲染的文本時耻涛,計(jì)算需要的像素?cái)?shù)目。這是一個消耗較大的過程瘟檩,應(yīng)盡可能少地去做抹缕。
- 檢查要被渲染的寬度。
- 檢查 numberOfLines墨辛,計(jì)算將要展示的行數(shù)卓研。
- sizeToFit 是否被調(diào)用?如果是,則計(jì)算高度睹簇。
- 如果 sizeToFit 沒有被調(diào)用奏赘,檢查當(dāng)前的內(nèi)容能否在給定的高度下展示出來。
- 如果 frame 不夠太惠,使用 lineBreakMode 確定隱藏或截?cái)嗟奈恢谩?/li>
- 最后磨淌,使用字體、類型及顏色來渲染最終展示的文本凿渊。
具體說明每個 UILabel 是一件工作量很大的事情梁只。使用較少的標(biāo)簽,更容易管理效果埃脏,使用較多的標(biāo)簽搪锣,你就需要多留意這些標(biāo)簽的創(chuàng)建、配置和重用彩掐。
2.2.2 UIButton
渲染按鈕的方式有以下四種:
- 使用自定義文本的默認(rèn)渲染
- 全尺寸資源的按鈕
- 可變大小的資源
- 使用 CALayer 和貝塞爾路徑自定義繪制
2.2.3 UIImageView
在渲染代價較大的各種 UI 元素中构舟,圖像首屈一指。在使用 UIImage 和 UIImageView 時佩谷,遵循以下的最佳實(shí)踐可以提升性能旁壮。
- 對于已知的圖像监嗜,使用 imageNamed: 方法加載圖像谐檀。它可以確保內(nèi)容只被加載至內(nèi)存一次, 還可以確保在多個 UIImage 對象間改變用途裁奇。
- 在使用 imageNamed: 方法加載包圖片時桐猬,使用資源包。如果應(yīng)用有一堆圖標(biāo)刽肠,且每個圖 標(biāo)都較小時溃肪,這種方式極其有用∫粑澹可以隨意地創(chuàng)建相關(guān)圖像(即通常被一起使用的圖片) 的多個目錄惫撰。
- 對于其他圖像,使用高性能的圖像緩存庫躺涝。AFNetworking 和 SDWebImage 都是可選的強(qiáng)大庫厨钻。當(dāng)使用內(nèi)存中的圖片時,確保正確配置了內(nèi)存的使用參數(shù)。不要使用硬編碼夯膀。讓它能夠自適應(yīng)——使用合理的 RAM 百分比可以較好地進(jìn)行配置诗充。
- 載入的圖像與即將渲染的 UIImageView 大小相同。如果被解析的圖像尺寸與 UIImageView 相同诱建,那么你會得到極高的性能蝴蜓,因?yàn)檎{(diào)整圖像大小是一個耗費(fèi)較大的操作,如果該圖像被包含在 UIScrollView 中俺猿,則耗費(fèi)會更大茎匠。如果圖像來自網(wǎng)絡(luò)下載,那么盡量下載和視圖大小匹配的圖像押袍。如果行不通汽抚,適當(dāng)?shù)貙D片進(jìn)行預(yù)處理,調(diào)整其大小伯病。
- 如果需要使用一些類似于模糊或色調(diào)的效果造烁,那么可以創(chuàng)建一份圖像內(nèi)容的副本,在副本上施加效果午笛,然后使用最終的位圖創(chuàng)建所需的 UIImage惭蟋。如此一來,這些附加的效果只會被使用一次药磺,如果有需要告组,原始圖像還可以用于其他顯示。
- 無論使用何種技術(shù)加載圖像癌佩,在非主線程中執(zhí)行木缝,最好在一個專用的隊(duì)列中執(zhí)行。尤其要在非主線程中解壓 JPG/PNG 圖像围辙。
- 最后同樣重要的是我碟,確定是否真的需要圖像。如果要展示一個評分欄姚建,最好使用直接繪制的自定義視圖矫俺,而不是使用多個圖像,通過調(diào)整透明或覆蓋來實(shí)現(xiàn)掸冤。
2.2.4 UITableView
無論是在新聞應(yīng)用厘托、郵件應(yīng)用、照片流稿湿,還是其他的應(yīng)用中铅匹,UITableView 都是最常用于顯示數(shù)據(jù)的視圖。UITableView 提供了一個展示信息條的極好選擇饺藤,這些信息條既可以是同一類別包斑,也可以是不同類別考杉。
UITableView 綁定了兩個協(xié)議。
-
UITableViewDataSource
- 必須將 dataSource 屬性設(shè)置到數(shù)據(jù)源上舰始。顧名思義崇棠,數(shù)據(jù)源是指將要填充至列表單元格中的數(shù)據(jù)源。
-
UITableViewDelegate
- 必須將 delegate 屬性設(shè)置到委托上丸卷,當(dāng)用戶與列表或單元格交互時枕稀,此處的委托必須能接收到回調(diào)。
下列是使用 UITableView 時需要牢記的一些最佳實(shí)踐谜嫉。
- 在數(shù)據(jù)源的 tableView:cellForRowAtIndexPath: 方法中萎坷,使用 tableView:dequeueReusa bleCellWithIdentifier: 或 tableView:dequeueReusableCellWithIdentifier:forIndexPa th: 進(jìn)行單元格的重用,而不是每次都創(chuàng)建新的單元格沐兰。
- 盡可能避免動態(tài)高度的單元格哆档。誠然,已經(jīng)確定的高度代表著只需很少的計(jì)算量住闯。如果內(nèi)容是動態(tài)配置的瓜浸,那么不僅需要計(jì)算高度,而且每次視圖要被渲染時比原,單元格的內(nèi)容也需要刷新和重新布局插佛。這是一個很大的性能損失。
- 如果你真的需要動態(tài)高度的單元格量窘,那么定義一個規(guī)則來標(biāo)記單元格是臟的雇寇。如果某個單元格是臟的,計(jì)算它的高度并緩存蚌铜。在委托的 tableView:heightForRowAtIndexPath: 回調(diào)中繼續(xù)返回緩存的高度锨侯,直到單元格不再被標(biāo)記為臟。如果要被渲染的模型是不可變的冬殃,一個可用的簡單規(guī)則是囚痴,檢查當(dāng)前被渲染的模型是否 和相應(yīng)的 indexPath 的值一樣。如果一樣造壮,則使用同樣的值渲染渡讼,無需進(jìn)一步的處理。如果不一樣耳璧,則重新計(jì)算值,并將新的對象(模型)附加至該單元格展箱。
- 當(dāng)用自定義視圖重用單元格時旨枯,要避免通過調(diào)用 layoutIfNeeded 每次都對其進(jìn)行布局。即使一個單元格的高度是固定的混驰,也有可能出現(xiàn)這樣的情況:在單元格中的獨(dú)立元素可能會被設(shè)置成不同的高度攀隔,例如皂贩,UILabel 支持多行內(nèi)容,UIImageView 可以裝入不同大小的圖像昆汹。
- 避免透明的單元格子視圖明刷。創(chuàng)建 UITableViewCell 時,盡量引入不透明元素满粗。半透明或透明元素(alpha 低于 1.0 的視圖)很好看辈末,但會有性能損失。
- 在快速滾動時考慮使用界面外殼(可以參考這個Skeleton)映皆。當(dāng)用戶快速滾動列表視圖時挤聘,雖然使用了 所有的優(yōu)化,但視圖的重用和渲染仍然需要超過 16 毫秒捅彻,還有可能出現(xiàn)偶發(fā)的丟幀現(xiàn) 象组去,從而導(dǎo)致不流暢的體驗(yàn)。
- 避免漸變步淹、圖像縮放以及任何屏幕外的繪制从隆。這些效果對 CPU 以及圖形處理單元(GPU) 來說都是消耗。
2.2.5 UIWebView
UIWebView 是用于渲染未知或動態(tài)內(nèi)容的最常見視圖缭裆。
雖然有些應(yīng)用可能全部都是原生的广料,但還是有需要使用 UIWebView 的場景,以下是一些常見場景幼驶。
- 任何應(yīng)用中的用戶登錄艾杏。Spotify、Mint 和 LinkedIn 這樣的應(yīng)用使用原生 UI 渲染登錄表單盅藻。但這有一定的限制购桑。
- 在任何應(yīng)用中顯示隱私政策或使用條款。因?yàn)檫@些會隨著時間變化氏淑,并且需要大量的格式化(文本樣式勃蜘、編號列表、其他內(nèi)容的交叉引用)假残,使用原生視圖不是較好的選擇缭贡。
- 新聞或文章閱讀器,因?yàn)榇蟛糠值奈恼露际菫?Web 創(chuàng)建的辉懒,幾乎都是 HTML阳惹。
- 郵件應(yīng)用。例如眶俩,初始郵件是 HTML 形式莹汤,當(dāng)呈現(xiàn)消息或跟帖,以及撰寫回復(fù)時颠印。
使用 UIWebView 時纲岭,請將以下幾個最佳實(shí)踐牢記在心抹竹。(需要注意的是,關(guān)于 UIWebView 能做的事情非常少止潮,并非都是關(guān)注性能的;相反窃判,此處的重點(diǎn)是以最恰當(dāng)?shù)姆绞秸故?HTML 內(nèi)容。)
- UIWebView 可能比較笨重且遲鈍喇闸,所以盡可能復(fù)用 web view袄琳。同時,UIWebView 也因內(nèi)存泄漏而知名仅偎。因此跨蟹,每個應(yīng)用的實(shí)例都應(yīng)該足夠好。
- 附加一個自定義的 UIWebViewDelegate橘沥。實(shí)現(xiàn) webView:shouldStartLoadWithRequest: navigationType: 方法窗轩。要留意 URL scheme。如果是 http 或 https 以外的東西座咆,需要注意: 應(yīng)用應(yīng)該知道如何處理這種情況痢艺,或警告用戶該網(wǎng)站正試圖脫離應(yīng)用。
- 你可以通過 stringByEvaluatingJavaScriptFromString: 方法創(chuàng)建一個橋來連接應(yīng)用和 JavaScript介陶,從而在當(dāng)前已經(jīng)加載的 web 頁面執(zhí)行 JavaScript堤舒。如果想要調(diào)用原生應(yīng)用的方法,你可以參考之前的處理方法哺呜,使用自定義的 URL scheme舌缤。
- 實(shí)現(xiàn)委托的 webView:didFailLoadWithError: 方法,以保持對所有可能出現(xiàn)的錯誤的緊密追蹤某残。
- 實(shí)現(xiàn) webView:didFailLoadWithError: 方法來處理特定的錯誤国撵。
2.3 自動布局
通過 Auto Layout,可以描述一個元素距另一元素的距離(水平或垂直)玻墅、其大小(寬度或高度)介牙,或其與另一元素的對齊方式(水平或垂直)。
關(guān)于 Auto Layout 的性能問題澳厢,書中只介紹了 Auto Layout 在視圖數(shù)量很多時环础,消耗會比 Frame 布局大很多,介紹的比較籠統(tǒng),具體還可以參考下這篇博客:從 Auto Layout 的布局算法談性能。
3. 網(wǎng)絡(luò)
在應(yīng)用中使用網(wǎng)絡(luò)是必不可少的尿褪,但減少網(wǎng)絡(luò)延遲的方法卻是有限的,因此框都,你應(yīng)該著手對網(wǎng)絡(luò)條件進(jìn)行最大程度的優(yōu)化,并預(yù)先對不同的場景進(jìn)行規(guī)劃呵晨。
3.1 指標(biāo)和測量
在網(wǎng)絡(luò)中完成的大多數(shù)工作是無法控制的魏保,因此確定衡量的標(biāo)準(zhǔn)非常重要。接下來會列出在性能優(yōu)化相關(guān)的測量中更為重要的一些指標(biāo)摸屠。
3.1.1 DNS查找時間
發(fā)起連接的第一步是 DNS 查找谓罗。如果你的應(yīng)用嚴(yán)重依賴網(wǎng)絡(luò)操作,DNS 的查找時間會使應(yīng)用變慢季二。
為了最大限度地減少 DNS 查詢時間所產(chǎn)生的延遲檩咱,你應(yīng)該遵循以下的最佳實(shí)踐。
- 最小化應(yīng)用使用的專有域名的數(shù)量胯舷。按照路由的一般工作方式刻蚯,多個域名是不可避免的。最好是能做到以下幾點(diǎn):
- 身份管理(登錄桑嘶、注銷炊汹、配置文件)
- 數(shù)據(jù)服務(wù)(API 端點(diǎn))
- CDN(圖片和其他靜態(tài)人工產(chǎn)品)
- 在應(yīng)用啟動時不需要連接所有的域名,可能只需要身份管理和初始畫面所需的數(shù)據(jù)逃顶。對于后續(xù)的子域名讨便,嘗試更早地進(jìn)行 DNS 解析,也被稱為 DNS 預(yù)先下載以政。為實(shí)現(xiàn)此操作霸褒, 你可以參考以下兩點(diǎn)。
- 如果子域名和主機(jī)在控制范圍內(nèi)盈蛮,可以配置一個預(yù)設(shè)的 URL废菱,不返回任何數(shù)據(jù),只返回 HTTP 204 的狀態(tài)碼抖誉,然后提前對該 URL 發(fā)起連接殊轴。
- 第二個方法是使用 gethostbyname 執(zhí)行一個明確的 DNS 查找。然而寸五,針對不同的協(xié)議梳凛, 主機(jī)可能會解析至不同的 IP,例如梳杏,HTTP 請求可能會解析至一個地址韧拒,而 HTTPS 會解析至另一個地址。雖然不是很常見十性,但第 7 層的路由可以根據(jù)實(shí)際的請求解析 IP 地址叛溢,例如,圖像是一個地址劲适,視頻是另外一個地址楷掉。鑒于這些因素,在連接之前解析 DNS 經(jīng)常是無用的霞势,對主機(jī)進(jìn)行偽連接會更有效烹植。
3.1.2 SSL握手時間
為了安全起見斑鸦,可以假設(shè)應(yīng)用中所有的連接均是通過 TLS/SSL 的(使用 HTTPS)。HTTPS 在連接開始時草雕,先進(jìn)行 SSL 握手巷屿,SSL 握手主要是驗(yàn)證服務(wù)器證書,同時共享用于通信的隨機(jī)密鑰墩虹。這一操作聽起來簡單嘱巾,但是卻有很多步驟,還會耗費(fèi)較多時間诫钓。
你應(yīng)該遵循以下的最佳實(shí)踐旬昭。
- 最大程度地減少應(yīng)用發(fā)起的連接數(shù)。因此菌湃,也需要減少應(yīng)用連接的獨(dú)有域名的數(shù)量问拘。
- 請求結(jié)束后不要關(guān)閉 HTTP/S 連接。為所有的HTTPS請求添加頭Connection: keep-alive慢味。這確保了同樣的連接在下一次 請求時可以復(fù)用场梆。
- 使用域分片。如此一來纯路,雖然連接的是不同的主機(jī)名或油,你也可以使用同一個 socket,只 要它們解析為相同的 IP驰唬,可以使用相同的證書(例如顶岸,在通配符域)就行了。
3.1.3 網(wǎng)絡(luò)類型
一般情況下叫编,iPhone 和 iPad 可以使用 WiFi辖佣、4G、3G 等網(wǎng)絡(luò)連接到互聯(lián)網(wǎng)搓逾。
遵循以下的最佳實(shí)踐卷谈。
- 設(shè)計(jì)時考慮不同的網(wǎng)絡(luò)可用性。在移動網(wǎng)絡(luò)中霞篡,唯一不變的是世蔗,網(wǎng)絡(luò)可用性是多變的。 對于流媒體朗兵,最好選擇 HTTP 實(shí)時流或任何可用的自適應(yīng)比特率流媒體技術(shù)污淋,這些技術(shù)可以在某一時刻針對可用帶寬進(jìn)行動態(tài)切換,切換至當(dāng)前帶寬的最佳流質(zhì)量余掖,從而提供流暢的視頻播放寸爆。對于非流媒體內(nèi)容,你需要實(shí)現(xiàn)一些策略,確定在單次拉取時應(yīng)該下載多少數(shù)據(jù)赁豆,并且數(shù)據(jù)量必須是自適應(yīng)的仅醇。例如,你可能不希望在最新一次更新時歌憨,一次拉取所有的 200 封新郵件着憨。你可以先下載前 50 封郵件墩衙,再逐步下載更多郵件务嫡。同樣,在低速網(wǎng)絡(luò)時漆改,不要打開視頻自動播放功能心铃,這可能會花費(fèi)用戶很多錢。
- 出現(xiàn)失敗時挫剑,在隨機(jī)的去扣、以指數(shù)增長的延遲后進(jìn)行重試。例如樊破,第一次失敗后愉棱,應(yīng)用可能會在 1 秒后重試。第二次失敗時哲戚,應(yīng)用在 2 秒后重試奔滑, 接著是 4 秒的延遲。不要忘記對每個會話設(shè)置最多的自動重試次數(shù)顺少。
- 設(shè)立強(qiáng)制刷新之間的最短時間朋其。當(dāng)用戶明確要求刷新時,不要立即發(fā)出請求脆炎。相反梅猿,檢查是否已經(jīng)存在一個請求,或當(dāng)前請求與上次請求的時間間隔是否小于閾值秒裕。如果滿足上述條件袱蚓,則不要發(fā)送此次請求。
- 使用可到達(dá)性庫發(fā)現(xiàn)網(wǎng)絡(luò)狀態(tài)的變化几蜻。
- 不要緩存網(wǎng)絡(luò)狀態(tài)喇潘。不論是通過觸發(fā)請求時的回調(diào)來獲取狀態(tài),還是在發(fā)送請求之前顯式地檢查狀態(tài)入蛆,要始終使用網(wǎng)絡(luò)敏感度高的任務(wù)的最新值响蓉。
- 基于網(wǎng)絡(luò)類型下載內(nèi)容。如果想要展示一個圖像哨毁,不用總是下載原始的枫甲、高質(zhì)量的圖像。應(yīng)該始終下載和設(shè)備適配的圖像——iPhone 4S 所需的圖像尺寸和第三代 iPad 所需的差別很大。
- 樂觀地預(yù)先下載想幻。在 WiFi 網(wǎng)絡(luò)中預(yù)先下載用戶在后續(xù)時刻需要的內(nèi)容粱栖。隨后就可以使用緩存內(nèi)容了。最好分次下載內(nèi)容脏毯,在使用之后關(guān)掉網(wǎng)絡(luò)連接闹究,這有助于節(jié)省電量。
- 如果適用食店,當(dāng)網(wǎng)絡(luò)可用時渣淤,支持同步的離線存儲。通常情況下吉嫩,網(wǎng)絡(luò)緩存就足夠了价认。但如果需要更多的結(jié)構(gòu)化數(shù)據(jù),使用本地文件或 Core Data 會是一個較好的選擇自娩。對游戲來說用踩,緩存最近一級的詳細(xì)信息。對郵件應(yīng)用來說忙迁,存儲一些帶有附件的最新電子郵件是一個不錯的選擇脐彩。
3.1.4 延遲
延遲是指從服務(wù)器請求資源時,在網(wǎng)絡(luò)傳輸上花費(fèi)的額外時間姊扔。設(shè)置用于測量網(wǎng)絡(luò)延遲的系統(tǒng)是很重要的惠奸。
網(wǎng)絡(luò)延遲可以通過使用請求過程中花費(fèi)的總時間減去服務(wù)器上花費(fèi)的時間(計(jì)算和服務(wù)響 應(yīng))來測量:
Round-Trip Time = (Timestamp of Response - Timestamp of Request)
Network Latency = Round-Trip Time - Time Spent on Server
花費(fèi)在服務(wù)器上的時間可以由服務(wù)器來計(jì)算。對客戶端而言旱眯,往返的時間是準(zhǔn)確可用的晨川。服務(wù)器可以將花費(fèi)的時間放在響應(yīng)的自定義頭部,然后客戶端就可以用來計(jì)算延遲了删豺。
如果你有數(shù)據(jù)來分析任何模式下的延遲共虑,還需跟蹤下列數(shù)據(jù)。
-
連接超時
- 跟蹤連接超時的次數(shù)是非常重要的呀页。根據(jù)網(wǎng)絡(luò)質(zhì)量(較薄弱的基礎(chǔ)設(shè)施或較低的容量)妈拌,該指標(biāo)會提供詳細(xì)的地理區(qū)域分類,網(wǎng)絡(luò)質(zhì)量將反過來幫助規(guī)劃同步時間的傳輸蓬蝶。例如尘分,同步會在短時間間隔傳輸,比如幾分鐘丸氛,而不用在某一個特定時間跨時區(qū)同步培愁。
-
響應(yīng)超時
- 捕捉連接成功但響應(yīng)超時的數(shù)量。這有助于根據(jù)地理位置和日期缓窜、年份的時間來規(guī)劃數(shù)據(jù)中心的容量定续。
-
載荷大小
- 請求以及響應(yīng)的大小完全可以在服務(wù)器端進(jìn)行測量谍咆。使用此數(shù)據(jù)可以識別任何可能降 低網(wǎng)絡(luò)操作速度的峰值,并確定一些可用選項(xiàng):通過選擇合適的序列化格式(JSON私股、 CSV摹察、Protobuf 等)減少數(shù)據(jù)占位,或者分割數(shù)據(jù)并使用增量同步(例如倡鲸,通過使用小的批量大小或在多個塊中發(fā)送部分?jǐn)?shù)據(jù))供嚎。
3.2 應(yīng)用部署
隨著對這些指標(biāo)的統(tǒng)計(jì),你可以更好地規(guī)劃應(yīng)用的部署峭状。這不僅包括服務(wù)器克滴、服務(wù)器的位置和容量,還包括客戶端宁炫,以及如何在給定的場景下獲得最好的偿曙。
3.2.1 服務(wù)器
在查看網(wǎng)絡(luò)延遲的地域分布時,我們可以使用這個信息為數(shù)據(jù)中心選擇適當(dāng)?shù)奈恢酶岢病H绻褂猛泄艿臄?shù)據(jù)中心提供商,不妨選擇有多個地理位置的罩阵,如 Amazon AWS 或 Rackspace Cloud竿秆。如果你有自己的數(shù)據(jù)中心,那么應(yīng)該確保它們在地理上是分散的稿壁。
無需多想幽钢,服務(wù)器應(yīng)該安裝在多個位置,這樣你可以更好地服務(wù)本地內(nèi)容傅是。
以下是一些應(yīng)該遵循的最佳實(shí)踐匪燕。
- 使用多個數(shù)據(jù)中心,讓服務(wù)器在地理上分散開來喧笔,更貼近用戶帽驯。
- 使用 CDN 提供靜態(tài)內(nèi)容,如圖像书闸、JavaScript尼变、CSS、字體等浆劲。
- 使用接近的邊緣服務(wù)器來提供動態(tài)內(nèi)容嫌术。
- 避免使用多個域名(DNS 查詢時間可能會很長,這會降低用戶體驗(yàn))牌借。
3.2.2 請求
為了恰當(dāng)?shù)卦O(shè)置網(wǎng)絡(luò)度气,正確地配置 HTTP/S 請求很重要。你應(yīng)該遵循以下的最佳實(shí)踐膨报。
- 不要為每一個操作單元都進(jìn)行一次請求磷籍,使用批量請求哲虾。即使必須實(shí)現(xiàn)多個后端子系統(tǒng)來完成,但是合并批量請求會帶來較大的性能提升择示,所以還是值得的束凑。
- 使用持續(xù)的 HTTP 連接,該連接也被稱為 HTTP 長連接栅盲。它們有助于最大限度地減少 TCP 和 SSL 握手的消耗汪诉,同時也減少了網(wǎng)絡(luò)擁塞。
- 在任何可以的情況下都使用 HTTP/2谈秫。通過單一的連接扒寄, HTTP/2 支持 HTTP 請求的真正復(fù)用;如果請求解析為一個 IP 地址,那么 HTTP/2 會將跨越了多個子域的請求聚集到一起;HTTP/2 還支持報(bào)頭壓縮等拟烫。使用 HTTP/2 的好處是巨大的该编。最好的是,就消息結(jié)構(gòu)而言硕淑,該協(xié)議仍舊保持不變课竣,依然包括頭部和主體。
- 使用 HTTP 緩存頭設(shè)置正確的緩存級別置媳。對于想要下載的標(biāo)準(zhǔn)圖像(如主題背景或表情)于樟, 內(nèi)容的有效期可以設(shè)置為較長的時間。這不僅保證了網(wǎng)絡(luò)庫在本地緩存它們拇囊,還保證了其他設(shè)備可以從在本地進(jìn)行了緩存的中介服務(wù)器(ISP 服務(wù)器或代理)中受益迂曲。影響 HTTP 緩存的響應(yīng)頭是 Last-Modified、Expires寥袭、ETag 和 Cache-Control路捧。
3.2.3 數(shù)據(jù)格式
選擇正確的數(shù)據(jù)格式和選擇網(wǎng)絡(luò)參數(shù)一樣重要。一些選擇可能會使應(yīng)用的性能產(chǎn)生很大的不同传黄,比如對無損圖像壓縮使用 PNG 還是 WEBP杰扫。
如果你的應(yīng)用是以數(shù)據(jù)為導(dǎo)向的,那么選擇適合其傳輸?shù)恼_格式很關(guān)鍵尝江。其他協(xié)議支持的功能也可以提供幫助涉波。
在選擇數(shù)據(jù)格式時,你應(yīng)該遵循以下的最佳實(shí)踐炭序。
- 使用數(shù)據(jù)壓縮啤覆。當(dāng)傳送 JSON 或 XML 這樣的文本內(nèi)容時,這一點(diǎn)尤為重要惭聂。 NSURLRequest 會自動給頭部添加 Accept-Encoding:gzip窗声、deflate,這樣你就無需自己動手了辜纲。但這也意味著服務(wù)器應(yīng)該承認(rèn)頭部笨觅,并使用適當(dāng)?shù)膫鬏斁幋a發(fā)送數(shù)據(jù)拦耐。
- 選擇正確的數(shù)據(jù)格式。不用多想见剩,JSON 和 XML 這樣冗長杀糯、人類可讀的格式是資源密集型的——序列化、傳輸苍苞、反序列化會比使用自定義制作的固翰、二進(jìn)制的、機(jī)器友好的格式更耗費(fèi)時間羹呵。此處不討論媒體壓縮(即圖像壓縮和視頻編解碼器)骂际,而是著眼于文本數(shù)據(jù)格式。
3.3 工具
Charles 是一個非常強(qiáng)大的網(wǎng)絡(luò)調(diào)試代理冈欢。使用比較簡單歉铝,這里不做過多介紹,如有需要可參考網(wǎng)上博客凑耻。
推薦一些之前收集的性能優(yōu)化的博客:
- iOS網(wǎng)絡(luò)深度優(yōu)化
- iOS網(wǎng)絡(luò)緩存掃盲篇--使用兩行代碼就能完成80%的緩存需求
- 攜程App的網(wǎng)絡(luò)性能優(yōu)化實(shí)踐
- 移動 APP 網(wǎng)絡(luò)優(yōu)化概述
- 深度優(yōu)化iOS網(wǎng)絡(luò)模塊
- iOS網(wǎng)絡(luò)請求優(yōu)化
- 移動端網(wǎng)絡(luò)常見問題及優(yōu)化對策
- 無線性能優(yōu)化:域名收斂
- App的網(wǎng)絡(luò)測試中性能優(yōu)化方案
- iOS網(wǎng)絡(luò)模塊優(yōu)化(失敗重發(fā)太示、緩存請求有網(wǎng)發(fā)送)
- 58 同城 iOS 客戶端網(wǎng)絡(luò)框架的演進(jìn)之路
- 阿里無線11.11:手機(jī)淘寶 521 性能優(yōu)化項(xiàng)目揭秘
4. 數(shù)據(jù)共享
有時你會需要與其他應(yīng)用共享數(shù)據(jù),或訪問設(shè)備上其他應(yīng)用的共享數(shù)據(jù)拳话。共享數(shù)據(jù)的場景包括以下幾個先匪。
- 與其他應(yīng)用集成(例如,讓用戶使用微信的登錄信息登錄你的應(yīng)用)弃衍。
- 發(fā)布一系列互補(bǔ)的應(yīng)用。
- 將用戶數(shù)據(jù)從統(tǒng)一的應(yīng)用移動到有多個特定用途的應(yīng)用坚俗,檢測其是否存在镜盯,并在需要時傳遞控制。
- 在可用的最佳查看器中打開文檔猖败。
4.1 深層鏈接
在移動應(yīng)用的上下文中速缆,深層鏈接包括使用統(tǒng)一的資源標(biāo)識符(uniform resourceidentifier,
URI)恩闻,其鏈接到移動應(yīng)用內(nèi)的特定位置艺糜,而不是簡單地啟動應(yīng)用。
深層鏈接為應(yīng)用之間的共享數(shù)據(jù)提供了解耦的方案幢尚。與訪問網(wǎng)站時的 HTTP 網(wǎng)址類似破停,iOS 中的深層鏈接通過所謂的自定義 URL scheme 來提供。你可以配置自己的應(yīng)用尉剩,讓它響應(yīng)唯一的 scheme真慢,操作系統(tǒng)會確保無論何時使用該 scheme,都由你的應(yīng)用進(jìn)行處理理茎。應(yīng)用可以響應(yīng)任何數(shù)量的 scheme黑界。
不論是訪問共享數(shù)據(jù)管嬉,還是對外共享數(shù)據(jù),深層鏈接可能是最常用的選項(xiàng)朗鸠,同時蚯撩,優(yōu)化創(chuàng)建和解析的時間也很重要。以下列表涵蓋了可以遵循的一些最佳實(shí)踐烛占,從而讓應(yīng)用實(shí)現(xiàn)最優(yōu)性能胎挎。
- 最好使用較短的 URL,因?yàn)樗鼈兊臉?gòu)建速度和解析速度都比較快扰楼。
- 避免基于正則表達(dá)式的模式呀癣。
- 優(yōu)先選擇基于查詢的 URL 進(jìn)行標(biāo)準(zhǔn)解析。用基于字符的分隔符解析比使用正則表達(dá)式解析更快弦赖。
- 在你的 URL 中支持深層鏈接回調(diào)项栏,以幫助用戶完成意圖。一個較好的方法是支持三個選項(xiàng):success蹬竖、failure 和 cancel沼沈。
- URL 最好使用深層鏈接,以幫助用戶定義一個需要多個應(yīng)用協(xié)調(diào)的工作流币厕。
- 不要在 URL 中放置任何敏感數(shù)據(jù)列另。具體來說,不要使用任何身份驗(yàn)證令牌旦装。這些令牌可能會被未知的應(yīng)用劫持页衙。
- 不要信任任何傳入的數(shù)據(jù)。始終驗(yàn)證 URL阴绢。作為附加的措施店乐,可以讓應(yīng)用在傳遞 URL 前對數(shù)據(jù)進(jìn)行簽名,并在處理前驗(yàn)證簽名呻袭,這可能會是個不錯的主意眨八。但是,為了安全地進(jìn)行左电,私鑰必須保存在服務(wù)器上廉侧,如此一來,就必須要有網(wǎng)絡(luò)連接篓足。
- 使用 sourceApplication 來標(biāo)識源段誊。有一個應(yīng)用白名單非常有用,你可以始終信任這些數(shù) 據(jù)纷纫。sourceApplication 的使用與簽名驗(yàn)證不正交枕扫。這可以是 URL 開始處理前的第一步。
4.2 剪貼板
官方文檔對剪貼板的描述如下辱魁。
剪貼板是用于在應(yīng)用之內(nèi)或之間交換數(shù)據(jù)的安全且標(biāo)準(zhǔn)化的機(jī)制烟瞧。許多操作取決 于剪貼板诗鸭,特別是復(fù)制—剪切—粘貼。......但你也可以在其他情況下使用剪貼板参滴,例如强岸,在應(yīng)用之間共享數(shù)據(jù)時。
可通過 UIPasteBoard 類使用剪貼板砾赔,該類可以訪問共享存儲庫蝌箍,寫對象和讀對象在共享存儲庫中進(jìn)行數(shù)據(jù)交換。寫對象也被稱為剪貼板所有者暴心,將數(shù)據(jù)存儲在剪貼板實(shí)例上妓盲。讀對象訪問剪貼板,將數(shù)據(jù)復(fù)制到其地址空間中专普。
與深層鏈接相比悯衬,剪貼板具有以下優(yōu)點(diǎn)。
- 它具有支持復(fù)雜數(shù)據(jù)(如圖像)的能力檀夹。
- 它支持在多種形式中表示數(shù)據(jù)筋粗,這些形式可以基于目標(biāo)應(yīng)用的功能來選擇。例如炸渡,消息應(yīng)用可以使用純文本格式娜亿,郵件應(yīng)用可以使用來自同一剪貼板項(xiàng)目的富文本格式。
- 即使應(yīng)用關(guān)閉后蚌堵,剪貼板內(nèi)容仍然會保留买决。
使用剪貼板時,你應(yīng)該遵循以下的最佳實(shí)踐吼畏。
- 剪貼板本質(zhì)上是由剪貼板服務(wù)進(jìn)行調(diào)解的進(jìn)程間通信策州。IPC 的所有安全規(guī)則都適用(例如,不發(fā)送任何安全數(shù)據(jù)宫仗、不信任任何傳入數(shù)據(jù))。
- 因?yàn)椴荒芸刂颇膫€應(yīng)用會訪問剪貼板旁仿,所以使用時總是不安全的藕夫,除非數(shù)據(jù)被加密。
- 不要在剪貼板中使用大量數(shù)據(jù)枯冈。雖然剪貼板支持交換圖像以及多種格式毅贮,但請記住,每個條目不僅消耗內(nèi)存尘奏,也需要額外的時間來讀寫滩褥。
- 當(dāng)應(yīng)用將使用 UIApplicationDidEnterBackgroundNotification 通知或 UIApplicationWillResignActiveNotification 通知進(jìn)入后臺時,清除剪貼板炫加。更好的做法是瑰煎,你可以實(shí)現(xiàn) UIApplicationDelegate 相應(yīng)的回調(diào)方法铺然。通過將 items 設(shè)置為 nil,你可以清除剪貼板酒甸,如下所示: myPasteboard.items = nil;
- 為了防止任何類型的復(fù)制 / 粘貼魄健,繼承 UITextView,并在 canPerformAction 的 copy: 動作中返回 NO插勤。
5. 安全
應(yīng)用可能會在未知的執(zhí)行環(huán)境中運(yùn)行沽瘦,并通過未知的傳輸網(wǎng)絡(luò)交換數(shù)據(jù),因此农尖,應(yīng)始終將安全性作為首要任務(wù)之一析恋,以便保護(hù)用戶及應(yīng)用的敏感數(shù)據(jù)。
不論是通過代碼的執(zhí)行(例如盛卡,從 1024 位 DSA 密鑰的加密密鑰轉(zhuǎn)為 2048 位 RSA 的加密密鑰)還是通過用戶干預(yù)(例如助隧,引入雙因素認(rèn)證或應(yīng)用 PIN),任何附加的安全層都會導(dǎo)致應(yīng)用變慢窟扑。因此喇颁,在保證用戶完成意圖的前提下,你需要對添加的安全措施(會導(dǎo)致延遲)進(jìn)行權(quán)衡嚎货。
5.1 應(yīng)用訪問
5.1.1 匿名訪問
應(yīng)用可能需要驗(yàn)證橘霎,也可能不需要驗(yàn)證。
有兩個選項(xiàng)可用于識別設(shè)備:供應(yīng)商的標(biāo)識符(Identifier for Vendor殖属,IDFV)和廣告商的 標(biāo)識符(Identifier for Advertiser姐叁,IDFA)。
IDFV 是設(shè)備上每個應(yīng)用的持久唯一的標(biāo)識符洗显,用于向應(yīng)用的供應(yīng)商標(biāo)識設(shè)備外潜。應(yīng)用包 ID 的一部分用于生成 IDFV,因此挠唆,即使應(yīng)用來自同一家公司处窥, IDFV 也可能不同。
IDFA 是可重置的標(biāo)識符玄组,在設(shè)備上的所有應(yīng)用中是唯一的滔驾。正因?yàn)樵诒姸鄳?yīng)用中是唯一的,所以它才是真正唯一的 ID俄讹。但是哆致,IDFA 可以被用戶重置。此外患膛,蘋果公司對它的使用設(shè)置了限制摊阀,你必須保證在提交應(yīng)用到 iTunes Connect 審核時使用它。此 ID 只應(yīng)由廣告投放系統(tǒng)使用。
5.1.2 認(rèn)證訪問
當(dāng)需要識別用戶時胞此,你需要認(rèn)證訪問臣咖。這并不意味著認(rèn)證必須在你的應(yīng)用中完成。以下是一些可用的認(rèn)證選項(xiàng)豌鹤。
-
應(yīng)用密碼
- 它也被稱為應(yīng)用 PIN亡哄,無論是否存在登錄至應(yīng)用的一組憑據(jù),應(yīng)用 PIN 都是你想要添加到應(yīng)用的本地憑據(jù)布疙。實(shí)際上蚊惯,它就是只存儲在設(shè)備本地的密碼。
-
游戲中心
- 此選項(xiàng)僅適用于游戲灵临。用 GameKit 連接游戲中心截型,后者會負(fù)責(zé)使用憑據(jù)對用戶進(jìn)行驗(yàn)證。游戲中心可以訪問用戶資料儒溉、個人記錄等宦焦,但僅共享唯一標(biāo)識用戶所需的內(nèi)容(即用戶 ID)。
5.2 網(wǎng)絡(luò)安全
前面已經(jīng)對網(wǎng)絡(luò)進(jìn)行了深入的討論顿涣。這里將討論在與遠(yuǎn)程設(shè)備通信中與安全有關(guān)的最佳實(shí)踐波闹,該遠(yuǎn)程設(shè)備可以是服務(wù)器,也可以是點(diǎn)對點(diǎn)設(shè)備涛碑。
5.2.1 使用HTTPS
假設(shè)你將 HTTP 作為底層消息傳遞協(xié)議(TCP 是傳輸層協(xié)議)精堕,那么你必須通過 TLS/SSL 使用它。這也就是說蒲障,你應(yīng)該一直使用 HTTPS歹篓。但是,使用 HTTPS 有幾個問題揉阎。如果這些潛在風(fēng)險(xiǎn)未得到解決庄撮,則 HTTPS 可能會受到影響。
1.CRIME攻擊
不要使用 SSL/TLS 壓縮毙籽。如果你現(xiàn)在在使用洞斯,請?jiān)诶^續(xù)之前立即關(guān)閉它。這會讓你處于較大的風(fēng)險(xiǎn)當(dāng)中坑赡。使用 TLS 壓縮(gzip巡扇、deflate 或其他格式),任何請求都會受到 CRIME(Compression Ratio Info-leak Made Easy垮衷,壓縮率使信息很容易泄露)攻擊。要想緩解風(fēng)險(xiǎn)乖坠,可以關(guān)閉 TLS 壓縮搀突,并給每個響應(yīng)發(fā)送反 CRIME cookie,較為簡單的方式是發(fā)送一個唯 一的隨機(jī)序列 cookie熊泵。
2.BREACH攻擊
如果使用請求/響應(yīng)正文壓縮(Transfer-Encoding = gzip或deflate)仰迁,你的通信會受到BREACH(Browser Reconnaissance and Exfiltration via Adaptive Compression of Hypertext甸昏,通過自適應(yīng)超文本壓縮的瀏覽器偵聽和滲透)攻擊,這種攻擊類型于 2012 年 9 月首次發(fā)現(xiàn)徐许。當(dāng)滿足以下標(biāo)準(zhǔn)時施蜜,就會存在風(fēng)險(xiǎn)。
- 應(yīng)用使用 HTTP 壓縮雌隅。
- 響應(yīng)反映了用戶輸入翻默。
- 響應(yīng)反映了隱私。
沒有單一的方法可以降低這種風(fēng)險(xiǎn)恰起。The Breach Attack網(wǎng)站按有效性列出了以下方法修械。
- 禁用 HTTP 壓縮。這種方法增加了傳輸?shù)臄?shù)據(jù)量检盼,可能不會作為實(shí)際的解決方案肯污。
- 從用戶輸入分離出隱私。將授權(quán)碼放在遠(yuǎn)離請求正文的地方吨枉。
- 對每個請求進(jìn)行隨機(jī)化加密蹦渣。但是,由于每個請求的加密是隨機(jī)的貌亭,因此柬唯,多個并行請求可能無法實(shí)現(xiàn)了。
- 修飾隱私属提。不要以原始格式發(fā)送隱私权逗。
- 使用 CSRF 保護(hù)易受攻擊的 HTML 頁面。在移動原生應(yīng)用上冤议,除非使用移動 Web斟薇,否
則不需要 CSRF。 - 隱藏長度恕酸。一個較好的方法是在 HTTP 響應(yīng)中使用分塊傳輸編碼堪滨。
- 對請求限速(這應(yīng)該作為最后的方法)。
5.2.2 使用證書鎖定
HTTPS 不是萬靈藥——采取 HTTPS 不會神奇地確保所有的通信都是安全的蕊温。HTTPS 的基礎(chǔ)是對公鑰的信任矿咕,該公鑰用于加密初始消息(在 SSL 握手期間)岸啡。中間人(man-in-the- middle,MITM)攻擊會捕獲用于加密消息的密鑰。
不讓請求變成無效的唯一方法就是信任的圆,該信任由網(wǎng)絡(luò)庫放置在接收到的證書之中。證書只是簽名的公鑰秦忿。因此内颗,如果網(wǎng)絡(luò)庫信任簽名者,那它也會信任主機(jī)提供的公鑰。黑客提供的假的根證書成為了讓所有安全措施崩潰的罪魁禍?zhǔn)住?/p>
這個問題的解決方案就是所謂的證書鎖定前计。這種方案的工作原理是胞谭,通過只信任一個或幾個能夠作為應(yīng)用根證書的證書,應(yīng)用創(chuàng)建一個自定義的信任級別男杈。這允許應(yīng)用僅信任來自白名單的證書丈屹, 確保設(shè)備上永不安裝那些允許網(wǎng)絡(luò)監(jiān)視的未知證書。
5.3 本地存儲
與通過網(wǎng)絡(luò)交換的數(shù)據(jù)類似伶棒,存儲在設(shè)備上的數(shù)據(jù)是不能防止被篡改的旺垒,而且如果不小心處理的話,入侵者是可以讀取或修改數(shù)據(jù)的苞冯。以下是需要注意的幾個要點(diǎn)袖牙,以及為了保護(hù)本地存儲空間需要遵循的最佳實(shí)踐。
-
本地存儲不安全
- 在越獄設(shè)備上非常容易訪問本地存儲舅锄。
-
加密本地存儲
- 本地存儲可以利用操作系統(tǒng)提供的數(shù)據(jù)保護(hù)能力進(jìn)行加密鞭达。
5.4 數(shù)據(jù)共享
共享數(shù)據(jù)和處理傳入數(shù)據(jù)時遵循的簡單基本規(guī)則是:不要信任對方。
當(dāng)接收數(shù)據(jù)時皇忿,總是進(jìn)行驗(yàn)證畴蹭。應(yīng)用對數(shù)據(jù)的唯一假設(shè)應(yīng)該是,它可能是無效且錯誤的鳍烁。為了提高安全性叨襟,要求數(shù)據(jù)進(jìn)行簽名。
同樣幔荒,因?yàn)椴恢滥膫€應(yīng)用會處理數(shù)據(jù)糊闽,所以永遠(yuǎn)不要發(fā)送敏感數(shù)據(jù)。如果你確實(shí)需要共享敏感數(shù)據(jù)爹梁,那么提供令牌右犹,然后要求其他應(yīng)用從你的應(yīng)用(或服務(wù)器)請求數(shù)據(jù)。
5.5 安全和應(yīng)用性能
額外添加的加密或安全措施會計(jì)入總內(nèi)存的消耗之中姚垃,同時還會增加處理時間念链。你沒有辦法在所有維度上進(jìn)行優(yōu)化,只能做一些權(quán)衡积糯。
有時掂墓,并非必須使用 2048 位的 RSA 密鑰,1024 位的 DSA 密鑰也許就已經(jīng)足夠了看成。其他時候君编,Rijndael 這樣的對稱加密算法就足以保護(hù)數(shù)據(jù)的安全了。
從鑰匙串檢索初始值可能會導(dǎo)致加載時間延長川慌。你在使用時應(yīng)該小心謹(jǐn)慎啦粹。
證書鎖定有其自己的成本偿荷,有可能會減慢所有的網(wǎng)絡(luò)操作。
創(chuàng)建和驗(yàn)證數(shù)據(jù)簽名需要計(jì)算內(nèi)容哈希唠椭,這意味著會產(chǎn)生額外的內(nèi)容傳遞。根據(jù)內(nèi)容的大小忍饰,這可能需要較多時間贪嫂,更不用說計(jì)算和驗(yàn)證數(shù)字簽名所需的額外時間了。
所有這些步驟會快速疊加起來艾蓝。也許你有了世界上最保險(xiǎn)和最安全的應(yīng)用力崇,但如果僅加載程序就需要 30 分鐘,估計(jì)也沒有人想使用它赢织。對于這一點(diǎn)亮靴,即使 5 秒鐘都可能對用戶體驗(yàn)產(chǎn)生負(fù)面影響,甚者永遠(yuǎn)失去用戶于置,尤其在其他應(yīng)用可以滿足同樣需求的情況下茧吊。
相關(guān)文章:高性能iOS應(yīng)用開發(fā) - 核心優(yōu)化