Swift & OC 混編 淺析


轉載自:http://www.infoq.com/cn/articles/wangyi-cartoon-swift-mixed-practice?utm_campaign=rightbar_v2&utm_source=infoq&utm_medium=articles_link&utm_content=link_text

網易漫畫App在Swift上的實踐。

主要內容:

  1. 使用Swift歷程?

     
  2. Swift混編實踐

  3. 基於Swift的架構演變及建議

 

1. 使用Swift歷程?

在公司量級產品中嘗試新的不穩定技術其實是風險比較高的而矛盾的。一方面有來自產品的需求,需要保證產品的穩定性及快速迭代,另一方面有來自技術人員迫切想使用新技術的需求。因此,我們轉到Swift也是逐步嘗試地過程。

  • Swift 1.0 Beta版本發布到1.2版本,中間經歷了各種Beta版本的迭代及改進,Swift其實並不穩定。 ----> 考慮到產品穩定性及迭代周期,我們對是否采用Swift還持保留態度。

     
  • Swift 1.2版本發布后,Swift在編譯速度、類型安全及穩定性上進行了進一步的改進。同時Objective-C增加了Nullability等特性以增強對Swift交互的支持。 ----> 我們也決定先小范圍嘗試Swift混編,主要是比較獨立的、不涉及網絡請求的獨立模塊,比如一些自定義View,但此時我們對Swift的寫法也是停留在Objective-C面向對象的Swift重寫。

  • Swift 2.0版本發布,Swift增加了一些新特性,如面向協議編程范式、Guard、Defer、異常處理等等。Objective-C也增加輕量級泛型支持和__kindof關鍵字。 ----> 基於之前版本的小范圍嘗試,我們認為Swift和Objecitve-C混編也沒有太大的技術門檻,至少從1.2版本到2.0的過渡,我們並沒有花費過多的時間。Swift面向協議等新特性也足夠吸引到了我們,因此,我們決定從2.0版本開始,所有新的業務代碼,包括公用組件采用Swift開發。

  • Swift 2.0 ~ Swift 2.2 ,Swift進一步進行了各種改進並開源 ----> 此時,我們Swift基礎組庫也逐步豐富,比如NESwiftKits(HTTP),NEExtensionHelps,NEShortCutManager,NEUserDefaults,PublicUI(CustomLoading,NEAnimatedTabbar)...

  • Swift 3.0 ~ 不久的將來 ----> Swift層架構逐漸探索演變中... TODO:業務層架構逐步改進,底層庫改用Swift封裝、SPM替代Cocoapods...

2. 項目中Swift混編及建議

Apple其實為我們做了大部分混編所需要做的工作,蘋果的官方文檔對Swift & Objective-C混編也總結地很好,並可以很好地解決你混編的大部分問題。

Swift和Objective-C混編無非涉及到兩個方向的調用:

  1. Swift調用Objective-C;

  2. Objective-C調用Swift,兩個方向的調用其實是一致的。

如果你的項目based on Objective-C,那在混編初期,大部分的情況都是Swift調用Objective-C的代碼。通過Bridging Header文件即可import需要提供給Swift的Objective-C頭文件,Swift即可調用對應的Objective-C代碼。 這里只是概括下網易漫畫App混編實踐中,語言層面上,值得關注的8條實踐總結。

2.1 Optional

上圖為Optional在標准庫中的定義,Optional其實為可解包的遵循NilLiteralConvertible協議的枚舉類型。在 Swift 中,nil用來表示值缺失,任何可選類型都可以被設置為nil。而Objective-C中nil表示空指針,完全不同的意義。

建議:

  • 如果你無法更改你原有Objective-C代碼添加Nullability屬性,比如用到第三方Framework。此時,如果你不確定返回的object是否為空,則需要加判斷條件if object != nil {},然后進行強制解包。

  • 避免對可選類型強解包,除非你確定該可選值不為nil,或者希望該值為nil值觸發運行時錯誤(Debug時)。

  • Objective-C屬性或者方法如果不加Nullability屬性的話,則默認為隱式可選類型。要慎用隱式可選類型,如果確定你的數據一直有值,則可用隱式解析可選。

  • 建議所有Objective-C代碼所有可空的屬性前加上nullable標識,並用if let可選綁定進行Optional類型的判斷。 在涉及網絡請求中,使用Optional需特別注意。如果Model的定義都是用Objective-C定義,最好用Nullability屬性表明data是nullable or __nonnull。事實上,很多涉及網絡請求的業務不太確定某一值是否為空。比如用戶昵稱String,如果你在業務開發時認為服務端不可能返回一個空的昵稱(可能你舊版本的bug導致很多昵稱為空的用戶昵稱),然后強制解包,此時就會觸發運行時錯誤。

2.2 Closure

在混編時Objective-C block可以映射到Swift closure類型。如:大部分網絡請求都是block回調,如下圖為網易漫畫項目中Swift調用Objective-C的網絡請求接口。

(點擊放大圖像)

(點擊放大圖像)

關於Closure有以下幾點需要注意:

  1. Closure會自動持有被截獲變量的引用,這樣可以在內部直接修改變量。Swift同時做了一些性能優化,由於持有變量的引用的開銷比直接持有變量開銷大,Swift會判斷你是否在Closure中或者外面是否修改了該變量,如果沒有修改則Closure會直接持有該變量。

  2. Swift循環引用weak & unowned:當一個引用在其生命周期中可能為nil,就把這個引用定義為weak。相反,則定義成unowned引用。

  3. 非class類型的協議不能被標識為weak, 當一個協議需求所定義的行為能夠確保:遵循這個協議的類型是引用類型而非值類型的時候,使用class類型協議。

  4. 盡量使用尾隨閉包,代碼更簡潔。

2.3 AnyObject

Swift中AnyObject定義為Protocol,所有的Swift類類型都遵循AnyObject協議,AnyObject的類型需要在運行時才能確定。Objective-C的id定義為指向對象的指針,Objective-C id可以無縫地轉換到Swift AnyObject類型。

  1. 慎用as!:建議使用as?進行AnyObject類型的轉換,if let xxx = aAnyObject as? aObjectType {xxx},除非你能夠確定AnyObject的類型才使用as!進行強制類型轉換,或者先使用is進行類型判斷。

  2. Objective-C llvm 7.0 編譯器開始支持輕量型泛型,集合類型NSArray、NSDictionary等轉換為Swift時對應的Object都默認轉換為AnyObject類型。建議你的Objective-C代碼對應的集合類型都指定泛型類型,如NSArray<BookCityUpdateItem *>。

2.4 拋棄OOP? 擁抱POP?或者FP?

Swift是多編程范式的語言,支持面向協議、面向對象、函數式編程、泛型編程,同時Swift更推薦值類型而非引用類型,值類型相對引用類型是線程安全的,並且Swift對值類型的拷貝進行了足夠的優化。Swift對枚舉、結構體、函數給予了更大的能力。 關於語言編程范式的問題其實已經超過了語言層面關於混編的范疇。但我們在開發一些組件或者業務時,使用Swift & Objective-C混編必然會導致我們在老和新的編程范式上進行抉擇。

  1. 如果你的模塊不涉及混編,那你可以很大膽地去使用 Swift的面向協議&函數式范式,只不過在Objective-C調用你的Swift模塊時,你需要在接口層考慮對Objective-C的兼容性。

  2. 如果當你現在的模塊基於Objective-C,當用Swift去擴展現有Objective-C模塊時,你需要在一定程度上做出取舍。繼續沿用之前Objective-C(面向對象)架構或者用Swift進行重構。

我們目前項目中大部分還是基於OOP的代碼。無法拋棄也完全沒必要拋棄OOP的原因:

  1. Cocoa的核心是基於OOP,比如我們要去自定義UIKit相關組件時必須使用繼承。

  2. 我們必須繼承一個現有Objective-C代碼的基類來去獲取基類定義的方法或者屬性。事實上,我們工程中還存在不少Super Class,如各種Objective-C工廠類。然而當我們用Swift去擴展時,不破壞原有框架地同時很簡單地方法就是采用繼承(多態)。

  3. 我們項目Model是基於Objective-C Class定義。

當然,Swift POP的引入也給我們在架構上帶來了更多的空間。

  1. 協議能夠被類、結構體和枚舉遵守,而基類和繼承只能限制在類上使用。

  2. 協議擴展為值類型和類提供了一種定義默認行為的能力。比如通過協議擴展很容易將UITableViewDelegate、UITableViewDataSource分離。

  3. 一個類型能夠實現多於一個協議,從而實現多繼承所擁有的能力。

  4. 值類型是線程安全的。

我們目前混編中:

  1. 在業務層,我們目前也盡量不去更改原有Objective-C的代碼來過渡到Swift,因為原有代碼已經足夠穩定了。

  2. 在框架層,我們會逐漸過渡到Swift,除了運用Objective-C一些黑魔法而無法實現的功能,當然這種過渡也是需要時間周期去逐漸演化的。

  3. 我們目前項目中還沒有運用太多函數式編程范式。

2.5 Enum

Enum在Swift賦予了更大的能力,支持原始值、關聯值、定義函數、擴展、遵循協議等特性。

  1. 使用typedef NS_ENUM(NSUInteger, xxxx) {},不要使用C-Style枚舉定義。

  2. 如果你的Objective-C代碼用到Swift定義的枚舉,相對於Objective-C的新特性將無法使用。

2.6 Objective-C調用Swift

Swift 的類或協議必須用@objc屬性來標記,以便在 Objective-C 中可訪問。這個屬性告訴編譯器Swift代碼可以從Objective-C 代碼中訪問。如果你的Swift 類是 Objective-C 類的子類,編譯器會自動為你添加@objc。

Runtime支持 Swift語言本身對Runtime並不支持,需要在屬性或者方法前添加dynamic修飾符才能獲取動態型,繼承自NSObject的類其繼承的父類的方法也具有動態型,子類的屬性和方法也需要加dynamic才能獲取動態性。

2.7 With C

Swift對C的交互性也提供了很好地支持,如原始類型CBool、CUnsignedLongLong,指針類型CConstVoidPointer、COpaquePointer,類型化指針CMutableVoidPointer<Type>,我們項目中需要Swift與C直接交互的代碼很少,在這里就不展開講了。

但是目前,Swift對C++的交互不是很好的支持(原因蘋果認為C++是個很復雜的語言,與C++的交互性需要考慮很多東西,是件很長遠的事情,至少在3.0及3.0版本之前Swift不支持),所以如果有些庫需要與C++混編可以考慮用Objective-C作為橋接。

2.8 其它更多

如宏定義、基本類型和Foundation類型轉換、Swift方法重命名和重定義(NS_SWIFT_NAME、NS_REFINED_FOR_SWIFT),這里就不一一展開講了。

但需要注意的是,Swift所特有的特性而Objective-C沒有是無法在Objective-C調用的, 解決辦法是通過Objective-C所支持的特性去重新封裝外部接口。

3. 基於Swift的架構演變及建議

 

3.1 現有架構

我們基於Swift的架構也不斷在演變和探索中。下圖這是我們目前大概的一個混編架構圖。

(點擊放大圖像)

其中紅色部分和紅色箭頭是我們目前需要考慮Objective-C和Swift兼容的地方。

  1. Service層統一對業務層的Swift & Objective-C接口兼容。其中包括:網絡請求NEKits,數據緩存(圖片、文件、數據庫等),Hybird,統計,Crash組件,Hotpatch,Autolayout組件、動畫、公用UI組件等等,又分為外部組件和公司內部組件(公司內部組件考慮到穩定性全部采用Objective-C進行編寫)。

  2. Model的定義,我們一直沿用Objective-C定義,用Mantle進行Runtime解析。這部分主要是業務層調用,兼容Swift並沒有花費我們太多時間。由於Swift對Runtime並不是很好地支持,我們目前沒有打算用Swift對Model進行重寫。

  3. 業務層部分Swift業務模塊和老的Objective-C業務模塊相互調用,此時也需要考慮接口的兼容性。

3.2 現有問題

在Swift混編前期,很多情況下是業務邏輯開發過程中,才發現原有的Objective-C代碼(特別是C類型的接口)無法很好地用在Swift中,在一定程度上影響了我們業務開發的效率。這種不兼容和接口不友好等問題基本對外封裝兼容性接口就能解決。

我們現有的混編架構還在不斷嘗試和演變中,我們的Swift模塊也逐漸在業務&基礎組件化。

  1. Swift編碼規范。

  2. OC的舊接口,如果涉及Swift代碼調用,需要考慮舊OC代碼的Swift接口兼容性。

  3. Swift的新接口,如果涉及舊OC代碼的調用,需要考慮OC的接口兼容性。

  4. 新架構考慮,我們也在探索中...

  5. POP + MVVM?   或者  POP + MVP?  或者 ……
    NO OOP? NO Inherit?Only Protocol?
    RxSwift?
    Swift Hotpatch?
    Runtime?

  6. 未來:Swift 3 compatible ?

下圖,當然不是我們期待的架構?

(點擊放大圖像)

3.3 混編建議

  1. 先小范圍、不重要的業務模塊嘗試Swift。

  2. Swift組件化。

  3. Objective-C考慮與Swift的交互性,如范型、Nullability。如果可能,對你現有的Objective-C代碼也提高與Swift的交互。

  4. 沒必要去更改現有的Objective-C代碼,成本很大。除非現有的Objective-C代碼需要重構,而Swift在設計層面很好地解決了你的重構問題。

  5. 沒必要去追求你工程中Swift的代碼占有量,用Objective-C能夠解決但是Swift解決不了的問題,那就使用Objective-C吧,雖然這種情況比較少,如Objective-C Runtime。

  6. 考慮適合你們產品的Swift架構和最佳實踐。


未來,Swift的開放性、跨平台、多編程范式、核心庫的逐漸豐富,也給予了我們更大的發揮和想象空間。 謝謝大家,今天的內容分享結束,希望對大家有幫助,也期待后續與大家共同交流探討~ ~

 

QA環節

Q:Swift對你們開發效率的提升有多大作用,有這方面的統計么?

A:Swift並沒有提升我們的效率,現在的開發效率和OC差不多,同時開發效率也和個人開發者對Swift的熟練程度有關。

Q:Swift組件化問題:如果某個pod內部帶有Swift代碼,則會導致整個app最低版本支持iOS 8,無法支持iOS 7,請問這個是如何解決的?

A:我們目前也在考慮組件話的問題,我們app最低支持iOS 7,不過由於產品屬性(偏年輕),我們不久會只支持iOS 8,到時會考慮用Carthage。目前所有Swift代碼都放在工程內的。

Q:Swift3之后還需要在應用中帶Swift運行時嗎?ABI是否固定了?

A:Swift 3.0會保持 ABI的穩定性,意味着,即便源代碼語言發生了變化,用以后版本的 Swift 開發的應用程序和編譯庫能在二進制層次上和 Swift 3.0 版本的應用程序和編譯庫相互調用。

Q:Swift的高效率表現在什么地方?

A:非運行時對消息事件的處理,值類型的使用,對高階函數地支持等等。

Q:請問你們混合業務組件的接口是怎么設計的?

A:混合業務組件的接口如果涉及到OC的調用則需要考慮Swift的兼容,基本方法就是語言重新封裝為OC的接口。

Q:Swift的優勢在哪里,與OC相比有什么不同?

A:1.性能。  2.多編程范式。  3.跨平台並開源。

Q:Swift為了與OC兼容,在某些特性上做了一些妥協,比如不完善的runtime支持,您是否認為這是Swift的一個缺點?

A:Swift其實做了很多妥協,比如Cocoa的妥協,我認為這並不是Swift缺點,兼容需要過渡時期。

Q:在混編的情況下,單元測試怎么做呢?可以用Swift寫單元測試,來測OC代碼嗎?

A:如果Swift的代碼需要OC調用的話,我們會用OC去寫單元測試。但是也並沒有統一規定。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM