<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          iOS 應(yīng)用啟動慢的原因找到了!

          共 6328字,需瀏覽 13分鐘

           ·

          2021-06-27 02:07

          ????關(guān)注后回復(fù) “進(jìn)群” ,拉你進(jìn)程序員交流群????

          一款應(yīng)用首先帶給用戶的就是啟動體驗(yàn),時間越短則體驗(yàn)越好,蘋果更是建議應(yīng)用第一個加載時間不宜超過 400 毫秒,可是據(jù)說 Swift 引用類型對應(yīng)用的大小及啟動速度有影響,這具體是怎么回事?


          作者 | Noah Martin   譯者 | 彎月
          出品 | CSDN(ID:CSDNnews)

          應(yīng)用的啟動體驗(yàn)是你帶給用戶的第一印象。在等待應(yīng)用啟動的過程中,每一毫秒對他們來說都很寶貴,他們完全可以將這些時間花在別處。如果你的應(yīng)用很吸引用戶,他們在一天內(nèi)使用了很多次你的應(yīng)用,那么他們肯定會一遍又一遍耐心地等待應(yīng)用啟動。蘋果建議第一個畫面的加載不應(yīng)該超過 400 毫秒。這樣可以確保在 Springboard 的應(yīng)用啟動動畫結(jié)束前,你的應(yīng)用就做好準(zhǔn)備可以使用了。

          由于只有 400 毫秒的時間,所以開發(fā)人員必須非常小心,應(yīng)盡力避免意外增加應(yīng)用的啟動時間。然而,應(yīng)用的啟動過程非常復(fù)雜,有很多可變因素,因此我們很難準(zhǔn)確地把握究竟哪些方面影響到了啟動的速度。在構(gòu)建自己的應(yīng)用期間,我深入研究了應(yīng)用大小與啟動時間的關(guān)系。在本文中,我會揭開應(yīng)用啟動過程中較為神秘的一些方面,并向你展示 Swift 引用類型對應(yīng)用的大小以及啟動速度有何種影響。

          Dyld


          應(yīng)用啟動的時候,Dyld 會加載 Macho-O 可執(zhí)行文件。Dyld 是蘋果負(fù)責(zé)加載應(yīng)用的程序。它的運(yùn)行過程與你編寫的代碼相同,會在啟動的時候加載所有依賴框架,包括系統(tǒng)框架。

          Dyld 的任務(wù)之一是重定位二進(jìn)制元數(shù)據(jù)中的指針,這些元數(shù)據(jù)描述了源代碼中的類型。動態(tài)運(yùn)行時功能需要這些元數(shù)據(jù),但這些元數(shù)據(jù)也會導(dǎo)致二進(jìn)制文件膨脹。以下是某個已編譯的應(yīng)用二進(jìn)制文件中包含的 Obj-C 類的布局:

          struct ObjcClass {  let isa: UInt64  let superclass: UInt64  let cache: UInt64  let mask: UInt32  let occupied: UInt32  let taggedData: UInt64}

          每個 UInt64 都是一段元數(shù)據(jù)的地址。由于它包含在應(yīng)用二進(jìn)制文件中,因此任何人從商店下載到的數(shù)據(jù)都是完全相同的。然而,由于地址空間布局隨機(jī)化(Address Space Layout Randomization,簡稱 ASLR),因此每次啟動應(yīng)用時,這些數(shù)據(jù)在內(nèi)存中的位置都會不同(并非總是從 0 開始)。這是一項(xiàng)安全功能,目的是為了防止他人猜測某個特定功能在內(nèi)存中的位置。

          ASLR 的問題在于,它會導(dǎo)致應(yīng)用的二進(jìn)制文件中硬編碼的地址出錯,實(shí)際的起始地址有隨機(jī)的偏移量。Dyld 的任務(wù)就是重定位所有指針,糾正起始位置??蓤?zhí)行文件中的每個指針,以及所有依賴框架(包括遞歸依賴),都要經(jīng)過這樣的處理。此外,Dyld 還需要設(shè)置其他可能會影響啟動時間的元數(shù)據(jù),比如綁定,但是在本文中,我們只討論重定位。

          所有這些指針的設(shè)置都會導(dǎo)致應(yīng)用的啟動時間增加,因此減少指針設(shè)置可以縮減應(yīng)用二進(jìn)制文件的大小,加快啟動速度。下面,我們來看一看這些指針設(shè)置源自何方,以及可能產(chǎn)生的影響。


          Swift 和 Obj-C


          上述,我們看到重定位的時間是由應(yīng)用的 Obj-C 元數(shù)據(jù)引起的,但為什么 Swift 應(yīng)用中會包含這些元數(shù)據(jù)呢?Swift 具有 @objc 屬性,它可以讓 Objective-C 代碼看到 Swift 中的聲明,但是即使 Obj-C 代碼看不到 Swift 類型,也會生成元數(shù)據(jù)。這是因?yàn)樗?Swift 類型都包含蘋果平臺的 Objective-C 元數(shù)據(jù)。我們來看一看下面這個聲明:

          final class TestClass { }

          這是純 Swift 代碼,并沒有繼承 NSObject,也沒有使用 @objc。但是,它仍然會在二進(jìn)制文件中生成一個 Obj-C 類元數(shù)據(jù),而且還會產(chǎn)生 9 個需要重定位的指針!為了證明這一點(diǎn),下面我們使用 Hopper 工具檢查二進(jìn)制文件,并查看“純 Swift”類的 objc_class 條目:

          圖:應(yīng)用二進(jìn)制文件中的Obj-C元數(shù)據(jù)

          將環(huán)境變量 DYLD_PRINT_STATISTICS_DETAILS 設(shè)置成 1,就可以看到啟動應(yīng)用時需要重定位的指針數(shù)量。在應(yīng)用啟動完成后,控制臺中就會輸出重定位的總數(shù)。我們甚至可以準(zhǔn)確地找出這 9 個指針的位置。

          并非所有 Swift 類型都會添加相同數(shù)量的重定位。如果通過重載超類或遵循 Obj-C 協(xié)議的方式,將方法公開給 Obj-C,則添加的重定位更多。另外,Swift 類上的每個屬性都將在 Objective-C 元數(shù)據(jù)中生成一個 ivar。


          測量


          根據(jù)設(shè)備類型以及運(yùn)行的應(yīng)用,重定位對實(shí)際啟動時間的影響也會有不同。我測量了一臺舊 iPhone 5S 上的實(shí)際情況。

          iOS 的啟動大致可分為:熱啟動和冷啟動。熱啟動指的是,系統(tǒng)已經(jīng)啟動過了應(yīng)用,并緩存了一些 Dyld 設(shè)置信息。由于我測試的首次啟動是冷啟動,因此速度略微慢一些。

          類數(shù)量

          重定位

          重定位時間(ms)

          0

          17715

          8.71

          1000

          26726

          9.23

          10000

          107726

          43.31

          20000

          197721

          104.23

          40000

          377724

          195.26

          我們可以看到,每進(jìn)行 2000 次重定位操作,啟動時間就會增加大約 1 毫秒。但這些時間不會直接累加到啟動時間,因?yàn)槟承┎僮骺梢圆⑿型瓿桑沁@些操作的確有一個下限,當(dāng)重定位超過 40 萬個時,應(yīng)用的啟動時間就已經(jīng)接近了蘋果建議的 400 毫秒的一半。


          示例


          我測量了幾款流行的應(yīng)用中重定位操作的發(fā)生次數(shù),并借以了解這些操作在實(shí)踐中的普遍程度。

          % xcrun dyldinfo -rebase TikTok.app/TikTok | wc -l2066598

          抖音有 200 多萬個重定位,這導(dǎo)致它的啟動時間超過了一秒鐘!抖音使用了 Objective-C,但是我也測試了一些大型的 Swift 應(yīng)用,它們使用了單體二進(jìn)制體系結(jié)構(gòu),其中的重定位次數(shù)大約在 68.5 萬~180 萬次之間。


          該怎么辦?


          盡管每個類都會增加重定位操作,但我并沒有建議將每個 Swift 類都換成 struct。大型 struct 也會增加二進(jìn)制文件的大小,而且在某些情況下,你需要的只是引用而已。與其他提升性能的手段一樣,你應(yīng)該避免過早優(yōu)化,而且首先應(yīng)該從測量開始。在發(fā)現(xiàn)問題之后,你可以尋找應(yīng)用中需要改進(jìn)的地方。以下是一些常見的情況:

          • 組合與繼承

          假設(shè)有如下這樣的一個數(shù)據(jù)層:

          class Section: Decodable {  let name: String  let id: Int}
          final class TextRow: Section { let title: String let subtitle: String
          private enum CodingKeys: CodingKey { case title case subtitle }
          required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) title = try container.decode(String.self, forKey: .title) subtitle = try container.decode(String.self, forKey: .subtitle) try super.init(from: decoder) }}
          final class ImageRow: Section { let imageURL: URL let accessibilityLabel: String
          private enum CodingKeys: CodingKey { case imageURL case accessibilityLabel }
          required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) imageURL = try container.decode(URL.self, forKey: .imageURL) accessibilityLabel = try container.decode(String.self, forKey: .accessibilityLabel) try super.init(from: decoder) }}

          這段代碼會產(chǎn)生大量元數(shù)據(jù),但是同樣的功能可以通過值類型實(shí)現(xiàn)(更適合在數(shù)據(jù)層中使用),并最終減少 22% 的重定位。你需要用組合替換掉對象繼承,例如具有關(guān)聯(lián)值的枚舉,或泛型等。

          struct Section<SectionType: Decodable>: Decodable {  let name: String  let id: Int  let type: SectionType}
          struct TextRow: Decodable { let title: String let subtitle: String}
          struct ImageRow: Decodable { let imageURL: URL let accessibilityLabel: String}
          • Swift 中的類別

          即使 Swift 沒有使用類別,而是使用了擴(kuò)展,但你仍然可以通過聲明使用了 Objective-C 函數(shù)的擴(kuò)展來生成類別二進(jìn)制元數(shù)據(jù)。聲明方式如下:

          extension TestClass {  @objc  func foo() { }
          override func bar() { }}

          這兩個函數(shù)都包含在二進(jìn)制元數(shù)據(jù)中,但是由于它們是在擴(kuò)展中聲明的,因此可以通過 TestClass 的合成類別引用。將這些函數(shù)移到原始類聲明中,可以避免二進(jìn)制文件包含額外的類別元數(shù)據(jù)。

          此外,你還可以使用基于閉包的回調(diào)(例如 iOS 14 引入的回調(diào))完全避免 @objc。

          • 許多屬性

          Swift 類中的每個屬性都會添加 3~6 個重定位,具體取決于該類是否為 final 類。如果有很多擁有 20 多個屬性的大型類,那么這個數(shù)字就非常驚人了。例如:

          final class TestClass {  var property1: Int = 0  var property2: Int = 0  ...  var property20: Int = 0}

          將其轉(zhuǎn)換為 struct,可以減少 60% 的 rebase!

          final class TestClass {  struct Content {    var property1: Int = 0    var property2: Int = 0    ...    var property20: Int = 0  }
          var content: Content = .init()}
          • 代碼生成

          回報率最高的提升方法之一就是改進(jìn)代碼生成。代碼生成的一種流行的用法是在多個代碼庫中建立共享的數(shù)據(jù)模型。如果你在多種類型上進(jìn)行此操作,則需注意它們會增加多少 Obj-C 元數(shù)據(jù)。然而,即便是值類型,也會增加代碼量以及重定位的開銷。最佳解決方案是盡可能減少生成的類型數(shù)量,或者用生成的函數(shù)替換自定義類型。

          上述這些示例只是由于二進(jìn)制文件規(guī)模擴(kuò)大,而導(dǎo)致啟動時間增加的幾種情況。還有其他導(dǎo)致啟動時間增加的原因,比如從磁盤加載到內(nèi)存的代碼量越大,啟動時間就會越長。

          原文鏈接:https://medium.com/codestory/why-swift-reference-types-are-bad-for-app-startup-time-90fbb25237fc

          聲明:本文為 CSDN 翻譯,轉(zhuǎn)載請注明來源。

          -End-

          最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

          點(diǎn)擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

          在看點(diǎn)這里好文分享給更多人↓↓

          瀏覽 102
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲在线a | 日日干日韩 | 翔田千里菊门无码 | 婷婷五月天丁香网 | 日本黄色视频一区 |