1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 6328字,需瀏覽 13分鐘

         ·

        2021-06-27 02:07

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

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


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

        應(yīng)用的啟動體驗是你帶給用戶的第一印象。在等待應(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 是蘋果負責(zé)加載應(yīng)用的程序。它的運行過程與你編寫的代碼相同,會在啟動的時候加載所有依賴框架,包括系統(tǒng)框架。

        Dyld 的任務(wù)之一是重定位二進制元數(shù)據(jù)中的指針,這些元數(shù)據(jù)描述了源代碼中的類型。動態(tài)運行時功能需要這些元數(shù)據(jù),但這些元數(shù)據(jù)也會導(dǎo)致二進制文件膨脹。以下是某個已編譯的應(yīng)用二進制文件中包含的 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)用二進制文件中,因此任何人從商店下載到的數(shù)據(jù)都是完全相同的。然而,由于地址空間布局隨機化(Address Space Layout Randomization,簡稱 ASLR),因此每次啟動應(yīng)用時,這些數(shù)據(jù)在內(nèi)存中的位置都會不同(并非總是從 0 開始)。這是一項安全功能,目的是為了防止他人猜測某個特定功能在內(nèi)存中的位置。

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

        所有這些指針的設(shè)置都會導(dǎo)致應(yīng)用的啟動時間增加,因此減少指針設(shè)置可以縮減應(yīng)用二進制文件的大小,加快啟動速度。下面,我們來看一看這些指針設(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ù)。這是因為所有 Swift 類型都包含蘋果平臺的 Objective-C 元數(shù)據(jù)。我們來看一看下面這個聲明:

        final class TestClass { }

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

        圖:應(yīng)用二進制文件中的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īng)用,重定位對實際啟動時間的影響也會有不同。我測量了一臺舊 iPhone 5S 上的實際情況。

        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

        我們可以看到,每進行 2000 次重定位操作,啟動時間就會增加大約 1 毫秒。但這些時間不會直接累加到啟動時間,因為某些操作可以并行完成,但是這些操作的確有一個下限,當(dāng)重定位超過 40 萬個時,應(yīng)用的啟動時間就已經(jīng)接近了蘋果建議的 400 毫秒的一半。


        示例


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

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

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


        該怎么辦?


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

        • 組合與繼承

        假設(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ù),但是同樣的功能可以通過值類型實現(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 沒有使用類別,而是使用了擴展,但你仍然可以通過聲明使用了 Objective-C 函數(shù)的擴展來生成類別二進制元數(shù)據(jù)。聲明方式如下:

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

        這兩個函數(shù)都包含在二進制元數(shù)據(jù)中,但是由于它們是在擴展中聲明的,因此可以通過 TestClass 的合成類別引用。將這些函數(shù)移到原始類聲明中,可以避免二進制文件包含額外的類別元數(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()}
        • 代碼生成

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

        上述這些示例只是由于二進制文件規(guī)模擴大,而導(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)盤了,歡迎下載!

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

        在看點這里好文分享給更多人↓↓

        瀏覽 102
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            无套内谢少妇高潮毛片 | 肏屄在线看 | 先锋影音男人资源 | 特级特黄A片一级一片亲下面 | 日韩欧美高清视频 | 军婚吸乳1v1 | 嫩草视频欧美一级A片 | 久青草电影 | 日韩一级电影院 | 日本无码视频在线 |