<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>

          京東APP訂單業(yè)務(wù)Swift優(yōu)化總結(jié)

          共 7567字,需瀏覽 16分鐘

           ·

          2021-06-01 13:41

          隨著Swift ABI穩(wěn)定,開發(fā)者對Swift的關(guān)注也持續(xù)升溫,一些開源框架甚至已經(jīng)不再提供ObjC版本了,部分蘋果新出的系統(tǒng)庫也是Swift Only。

          在這樣的背景下,京東商城訂單業(yè)務(wù)在不同場景下嘗試更多的使用Swift開發(fā),比如:

          • 京東App部分訂單業(yè)務(wù)頁面

          • 京東App物流小組件

          • “京東工作站”,為公司內(nèi)部提供的集成部分工作環(huán)境與開發(fā)環(huán)境,以及部分工作流的macOS應(yīng)用


          在改造過程中,Swift的高效安全與便捷和一些優(yōu)秀特性給團(tuán)隊留下了深刻的印象。有很多特性是開發(fā)者在寫ObjC時不會太多考慮的。比如,Swift的靜態(tài)派發(fā)方式、值類型的使用、靜態(tài)多態(tài)、Errors+Throws、柯里化與函數(shù)合成以及豐富高階函數(shù)等等,而且相對于OOP,Swift也能更好的支持面向協(xié)議編程、泛型編程以及更抽象函數(shù)式編程,解決了很多ObjC時代開發(fā)者面臨的痛點問題。

          結(jié)合Swift和ObjC的異同點,我們從Swift優(yōu)勢出發(fā),重新審視和優(yōu)化了項目的功能代碼,優(yōu)化點包括但不限于如下幾個方面。


          將部分方法動態(tài)派發(fā)替換為靜態(tài)派發(fā)


          Swift運行速度比ObjC快的原因之一就是其派發(fā)方式:靜態(tài)派發(fā)(值類型)和函數(shù)表派發(fā)(引用類型)。使用靜態(tài)派發(fā)ARM架構(gòu)可直接用bl指令跳轉(zhuǎn)到對應(yīng)函數(shù)地址,調(diào)用效率最高并且有利于編譯器的內(nèi)聯(lián)優(yōu)化。值類型無法繼承父類,編譯時期能確定類型,滿足靜態(tài)派發(fā)的條件。對于引用類型,不同編譯器的設(shè)置也會對派發(fā)方式有影響。比如WMO全模塊編譯下,系統(tǒng)會自動填用隱式final等關(guān)鍵字來修飾沒有被子類繼承的類,從而盡可能多的使用靜態(tài)派發(fā)。

          在我們的項目中,針對所有使用Class的類做了整體檢查。除非必要應(yīng)完全避免繼承NSObject,少用NSObject的子類。對于不需要考慮繼承或者多態(tài)的場景,盡可能的使用final 或者 private等關(guān)鍵字修飾。

          另外需要關(guān)注的是,ObjC也引入了方法的靜態(tài)派發(fā)。在Xcode12中集成的最新LLVM已經(jīng)支持 ObjC 通過對方法指定__attribute__((objc_direct)) 的方式,來將原本的動態(tài)消息派發(fā)改為靜態(tài)派發(fā)。


          檢查所有Class 盡可能替換為結(jié)構(gòu)體或者枚舉


          Swift中的結(jié)構(gòu)體和枚舉是值類型,Class是引用類型。在Swift中使用值類型還是引用類型是開發(fā)者需要思考和評估的。

          在我們開發(fā)的京東物流小組件和基于SwiftUI開發(fā)的macOS應(yīng)用中,我們目前更多的使用了結(jié)構(gòu)體和枚舉。先對比下值類型與引用類型的區(qū)別,值類型(Struct Enum等等):

          • 在棧上創(chuàng)建,創(chuàng)建速度快

          • 內(nèi)存占用小。整體占用的內(nèi)存就是內(nèi)部屬性內(nèi)存對齊后的大小

          • 內(nèi)存回收快,用棧幀控制入棧出棧即可,沒有處理堆內(nèi)存的開銷

          • 不需要引用計數(shù) (結(jié)構(gòu)體中使用引用類型作為屬性除外)

          • 一般是靜態(tài)派發(fā),運行速度快,也方便編譯器優(yōu)化,如內(nèi)聯(lián)等

          • 賦值時深拷貝。系統(tǒng)通過Copy-On-Write,避免不必要的copy,減少拷貝開銷

          • 沒有隱式數(shù)據(jù)共享,具有獨立性不可變性

          • 可通過mutating去修改結(jié)構(gòu)體中的屬性。這樣在保證值類型的獨立性的同時,也能支持對部分屬性的修改。

          • 線程安全,一般來說沒有競態(tài)條件和死鎖(要注意確定值在各個子線程中是被copy過的)

          • 不支持繼承,避免OOP子類過于耦合父類的問題。

          • 可通過協(xié)議和泛型實現(xiàn)抽象。但實現(xiàn)協(xié)議的結(jié)構(gòu)體內(nèi)存大小不同,因此無法直接放入數(shù)組中,為了存儲的一致性,傳參賦值時系統(tǒng)會引入中間層Existential Container。此處如果結(jié)構(gòu)體屬性較多會復(fù)雜一點,但蘋果也會有優(yōu)化(Indirect Storage With Copy-On-Write),較少開銷。總體來說,值類型的多態(tài)是有成本的,系統(tǒng)會盡量優(yōu)化。開發(fā)者要考慮的是:減少動態(tài)多態(tài)把協(xié)議直接當(dāng)做類來使用,需要更多考慮靜態(tài)多態(tài),多結(jié)合泛型約束來使用。




          引用類型(Class Function Closure等等):


          • 引用類型在內(nèi)存使用上沒有值類型高效,在堆上創(chuàng)建并需要有棧指針指向該區(qū)域,增加了堆內(nèi)存分配和回收的開銷

          • 賦值消耗小,一般是淺拷貝復(fù)制指針。但有引用計數(shù)成本

          • 多個指針可指向同一內(nèi)存,獨立性差,容易誤操作

          • 非線程安全,要考慮原子性,多線程需要線程鎖配合

          • 需要引用計數(shù)來控制內(nèi)存釋放,使用不當(dāng)會有野指針、內(nèi)存泄漏和循環(huán)引用的風(fēng)險

          • 允許繼承,但繼承的Side effect就是子類與父類的緊耦合。比如系統(tǒng)的 UIStackView主要目的只是用來布局使用,但卻不得不繼承UIView的所有屬性和方法。


          由此可見,Swift提供了更強大的值類型試圖來解決ObjC時代OOP的子類與父類的緊耦合、對象隱式數(shù)據(jù)共享、非線程安全、引用計數(shù)等典型痛點。翻看Swift 標(biāo)準(zhǔn)庫會發(fā)現(xiàn)其主要由值類型組成,基本類型集合如 Int,Double,F(xiàn)loat,String,Array,Dictionary,Set,Tuple也都是結(jié)構(gòu)體。當(dāng)然,雖然值類型有眾多優(yōu)點,但也不是說要完全拋棄Class,還是要根據(jù)實際情況分析,實際的Swift開發(fā)中更多的是一個種結(jié)合的方式,完全不使用OOP也是不現(xiàn)實的。


          優(yōu)化結(jié)構(gòu)體內(nèi)存


          和使用C語言結(jié)構(gòu)體一樣,Swift結(jié)構(gòu)體的大小就是內(nèi)部屬性內(nèi)存對齊后的大小。結(jié)構(gòu)體中屬性放置在不同的順序會影響最后的內(nèi)存大小。可使用系統(tǒng)提供的 MemoryLayout查看相應(yīng)結(jié)構(gòu)體占用內(nèi)存大小。

          我們從一些細(xì)節(jié)層面做了review,比如對于Int32完全滿足的場景沒有必要使用Int,不要使用String或者Int代替應(yīng)該使用Bool的場景,內(nèi)存小的屬性盡量放在后面等等。

          struct GameBoard {  var p1Score: Int32  var p2Score: Int32  var gameOver: Bool }struct GameBoard2 {  var p1Score: Int32  var gameOver: Bool   var p2Score: Int32}//基于CPU尋址效率考慮,GameBoard2字節(jié)對齊后占用空間更多MemoryLayout<GameBoard>.self.size  //4 + 4 + 1 = 9(bytes)MemoryLayout<GameBoard2>.self.size //4 + 4 + 4 = 12(bytes)


          使用靜態(tài)多態(tài)替換動態(tài)多態(tài)


          上面提到值類型的時候,我們有提到靜態(tài)多態(tài),靜態(tài)多態(tài)是指編譯器能在編譯時期確定類型的多態(tài)。這樣編譯器可以類型降級,在編譯時可產(chǎn)生特定類型的方法。

          將泛型定義為遵守某個協(xié)議的約束可以避免直接把協(xié)議直接當(dāng)做類來傳參使用,否則編譯器會報錯,相當(dāng)于接口支持多態(tài),但調(diào)用時要用特定的類型調(diào)用,從而達(dá)到了靜態(tài)多態(tài)的目的。對于靜態(tài)多態(tài),編譯器會充分利用其靜態(tài)特性做優(yōu)化,同時在設(shè)置了WMO全模塊優(yōu)化(Whole Module Optimization)的情況下會盡量控制由此可能產(chǎn)生的代碼增長。

          簡而言之,開發(fā)者要盡可能多考慮靜態(tài)多態(tài)。比如在使用協(xié)議作為函數(shù)的參數(shù)時,可以引入泛型。WWDC中有很經(jīng)典的討論:

          protocol Drawable {    func draw()}struct Line: Drawable {    var x: Double = 0    func draw() {    }}func drawACopy<T: Drawable>(local: T) {//指定T必須遵守Drawable    local.draw()}

          let line = Line()drawACopy(local: line)//Success (傳入具體的實現(xiàn)了Drawable的結(jié)構(gòu)體,編譯器可推斷其類型)let line2: Drawable = Line()drawACopy(local: line2)//Error,編譯器不允許直接使用Drawable協(xié)議作為入?yún)?/span>


          面向協(xié)議為協(xié)議提供擴展默認(rèn)實現(xiàn)


          對于類的繼承父類和遵守協(xié)議,Swift更愿意選擇后者。ObjC中OOP的形式,在Swift里基本都可以使用 Structs/Enums + Protocols + Protocol extensions + Generics 來實現(xiàn)邏輯抽象。

          我們盡量減少了項目中使用OOP的場景,盡可能的只用值類型面向協(xié)議和利用泛型,這樣編譯器能做更多的靜態(tài)優(yōu)化,更能降低OOP超類帶來的緊耦合。

          同時,Protocol extension能夠為protocol提供一個默認(rèn)實現(xiàn),這也是區(qū)別于ObjC協(xié)議的很重要的優(yōu)化。

          使用時要注意,應(yīng)該用具體的類型去調(diào)用Protocol extension中的方法,而不是用通過類型推斷得到的Protocol來調(diào)用。使用Protocol調(diào)用時,如果該方法沒有在Protocol中定義,Protocol extension中的默認(rèn)實現(xiàn)將被調(diào)用,即使具體的類型中有實現(xiàn)對應(yīng)方法。因為此時編譯器此時只能找到默認(rèn)實現(xiàn)。


          優(yōu)化錯誤處理


          相對于ObjC, Swift 中對 Error 和 Throw 的處理更加完善,這樣顯而易見的好處是API更友好,提高可讀性,利用編輯器檢測降低出錯概率。ObjC時代大家往往不會考慮拋出異常的操作,這個也是習(xí)慣ObjC編碼的程序員在封裝底層API時需要注意的。常見的是使用繼承Error協(xié)議的Enum。

          enum CustomError: Error {   case error1   case error2}


          產(chǎn)生Error后也可以拋出讓外部處理,支持throw的方法后編譯器會做強檢測是否有處理throw。要注意 () throws -> Void 和 () -> Void 是不同的 Function Type。

          //(Int)->Void可以賦值給(Int)throws->Voidlet a: (Int) throws -> Void = { n in}//反之類型不匹配 編譯報錯let b: (Int) -> Void = { n throws in}


          rethrows:如果一個函數(shù)入?yún)⑹且粋€支持throw的函數(shù),那么通過rethrows可以標(biāo)識該函數(shù)同樣可以拋出Error。這樣在使用該函數(shù)時,編譯器會檢測是否需要try-catch。

          這是我們在封裝基礎(chǔ)功能時需要考慮的,系統(tǒng)中友好的示例很多,比如map函數(shù)在系統(tǒng)中的定義:

          public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]let a = [1, 2, 3]enum CustomError: Error {  case error1  case error2}do {  let _ = try a.map { n -> Int in    guard n >= 0 else {      //如果map接受的closure內(nèi)部有拋出throw,編譯器會強制檢測外部是否有try-catch      throw CustomError.error1    }    return n * n  }} catch CustomError.error1 {} catch {}


          用Guard減少if嵌套


          關(guān)鍵性檢測可以使用 Guard,其優(yōu)勢是可以增強可讀性,較少過多的if嵌套。使用Guard時,一般else里面會是 return、 throw、continue、 break等。

          //if嵌套過多,難以閱讀,增加后期維護(hù)成本if (xxx){  if (xxx) {    if (xxx) {    }  }}//使用Guard,整體更清晰,便于后期維護(hù)let dict : Dictionary = ["key": "0"]guard let value1 = dict["key"], value == "0" else {  return}guard let value2 = dict["key2"], value == "0" else {  return}print("\(value1) \(value2)")


          利用Defer


          被defer修飾的closure會在當(dāng)前作用域退出的時候調(diào)用,主要用來避免重復(fù)添加返回前需要執(zhí)行的代碼,提高可讀性。

          比如在我們macOS應(yīng)用中有對文件讀寫的操作,這時候使用defer可以確保不會忘記關(guān)閉文件。

          func write() throws {  //...  guard let file = FileHandle(forUpdatingAtPath: filepath) else {    throw WriteError.notFound  }  defer {    try? file.close()  }  //...}


          另外比較常用的場景是釋放鎖的時候,以及非逃逸閉包回調(diào)等。

          但是defer不要過度使用,使用時要注意closure捕獲變量和作用域的問題。

          比如如果在if語句中使用defer,則跳出if時,該defer就會被執(zhí)行。


          用可選綁定替換所有的強制拆包


          對于可選值,要盡最大可能甚至完全避免強制拆包。大部分情況下如果遇到了需要使用 ! 的情況,很可能說明最初的設(shè)計是不合理的。包括downCasting時,由于類型轉(zhuǎn)換本身就有可能失敗,要避免使用 as! ,盡量使用as?,當(dāng)然try!也要避免。

          對于可選值,永遠(yuǎn)要使用可選綁定檢測,確??蛇x變量具有真正的值存在,然后再進(jìn)行操作:

          var optString: String?if let _ = optString {}


          多考慮懶加載


          將項目中不需要必須創(chuàng)建的屬性,改為懶加載。Swift的懶加載相對于ObjC來說可讀性更好,也更容易實現(xiàn),使用Lazy修飾就好。

          lazy var aLabel: UILabel = {    let label = UILabel()    return label}()


          使用函數(shù)式編程  減少狀態(tài)變量聲明與維護(hù)


          在類里面聲明過多的狀態(tài)變量是不利于后期維護(hù)的。Swift里函數(shù)可以作為函數(shù)參數(shù)、返回值以及變量,可以很好的支持函數(shù)式編程。利用函數(shù)式能有效減少全局變量或者狀態(tài)變量。

          命令式編程更關(guān)注解決問題的步驟。直接反應(yīng)機器指令序列。有變量(對應(yīng)存儲單元),賦值語句(對應(yīng)獲取與存儲指令),表達(dá)式(對應(yīng) 指令算數(shù)計算),控制語句(對應(yīng)跳轉(zhuǎn)指令)。

          函數(shù)式編程更關(guān)注數(shù)據(jù)的映射關(guān)系和數(shù)據(jù)的流向,即輸入和輸出。函數(shù)被當(dāng)做變量,既可以作為其它函數(shù)的參數(shù)(輸入值),也可以從函數(shù)中返回(輸出值)。將計算描述為表達(dá)式求值,自變量的映射f(x)->y,給定x,會穩(wěn)定映射為y。函數(shù)內(nèi)盡量不訪問函數(shù)作用域之外的變量,只依賴入?yún)ⅲ瑴p少狀態(tài)變量的聲明與維護(hù)。同時少用可變變量(對象),多用不可變變量(結(jié)構(gòu)體)。這樣就不會有其他side effects干擾。

          利用柯里化把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)的函數(shù),將部分參數(shù)緩存到函數(shù)內(nèi)部。同時利用函數(shù)合成增加可讀性。比如做加法乘法計算,我們可以封裝加法和乘法函數(shù)然后逐一調(diào)用:

          func add(_ a: Int, _ b: Int) -> Int { a + b }func multiple(_ a: Int, _ b: Int) -> Int { a * b }let n = 3multiple(add(n, 7), 6) //(n + 7) * 6 = 60


          也可以使用函數(shù)式:

          //柯里化add和multiple函數(shù): 由兩個入?yún)⒏臑橐粋€并返回一個(Int)->Int類型函數(shù)func add(_ a: Int) -> (Int) -> Int { { $0 + a} } func multiple(_ a: Int) -> (Int) -> Int { { $0 * a} } //函數(shù)合成 自定義中置運算符 > 增加可讀性infix operator > : AdditionPrecedencefunc >(_ f1: @escaping (Int)->Int,       _ f2: @escaping (Int)->Int) -> (Int) -> Int {  {f2(f1($0))} }//生成新的函數(shù) newFnlet n = 3let newFn = add(7) > multiple(6) // (Int)->Intprint( newFn(n) ) //(n + 7) * 6 = 60


          可以看到,從使用multiple(add(n, 7), 6) 到 let newFn = add(7) > multiple(6), newFn(n),整體更清晰了,尤其是在更復(fù)雜的場景下,其優(yōu)勢會更明顯。


          總結(jié)


          Swift提供了豐富簡便的語法糖以及強大的類型推斷,這些都讓Swift變得很容易上手入門。但是要從性能考慮或者是設(shè)計出更完美API的角度出發(fā),還是需要投入更多的實踐才行。訂單團(tuán)隊正在iOS小組件、AppClips、京東工作站(macOS桌面應(yīng)用)等場景下嘗試盡可能多的使用Swift與SwiftUI開發(fā),開發(fā)效率與項目穩(wěn)定性都有不錯的表現(xiàn)。目前京東集團(tuán)內(nèi)部對Swift的基礎(chǔ)設(shè)施正在逐步完善中,我們相信也希望未來集團(tuán)內(nèi)有更多的同學(xué)參與到Swift的開發(fā)中進(jìn)來。


          參考鏈接:

          https://developer.apple.com/videos/play/wwdc2016/419/

          https://developer.apple.com/videos/play/wwdc2016/416/

          https://swift.org/blog/whole-module-optimizations/

          https://docs.swift.org/swift-book/GuidedTour/GuidedTour.html#//apple_ref/doc/uid/TP40014097-CH2

          瀏覽 42
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  日本一级黄色电影网 | 黄色操逼小视频 | 人妻中文视频免费 | 国产特黄视频 | 成人无码豆花 |