用 Dart 來寫 Objective-C 程式碼
問題背景
先說說為什麼會有開發效率的問題。Flutter 的跨平臺多適用於 UI 等上層需求,本來是可以提升開發效率的。但是諸如 LBS、系統和裝置資訊、獲取相簿等常用功能都需要兩端去寫很多 Native 程式碼。最終原本的『兩端開發』最後成了『三端開發』。很少會有完全用 Flutter 開發的 App,原因如下:
- 一些跟系統和裝置強相關的功能只能靠呼叫 API 來實現
- 舊專案引入 Flutter 後需呼叫已有的 Native 模組程式碼
既然『三端開發』無法避免,那麼增加了哪些成本呢?
- 開發過程中需要在至少兩個 IDE 開啟的工程中來回切換,需單獨執行,無論是寫程式碼還是 Debug 都體驗不連貫,降低效率
- 如果 Flutter 和 Native 程式碼由不同的人來開發和維護,增加了溝通成本
- Flutter 需要通過編寫 channel 程式碼來與 Native 層互動,需要兩端開發時統一資料傳輸協議。不僅 channel 呼叫效能較差,Model資料在 Native 與 Flutter 之間傳遞過程的序列化和反序列化也降低效能。
- 通過 channel 在 Flutter 和 Native 之間呼叫時只支援非同步回撥
分析問題
既然無法避免呼叫 Native 的 API,那麼就要面對這個事實。下一步是如何能讓呼叫 Native API 的這個過程效率更高。具體體現如下:
- 開發效率提高:直接用 Dart 語言在 Flutter 工程裡編寫和除錯程式碼,無需切換到 Xcode 等其他 IDE 開啟的 Native 工程
- 執行效率提高:channel 的呼叫效能差一直被詬病
所以思路就是:
- 將 Native API 封裝成對應的 Dart 語言,解決一系列語言之間的型別轉換和語法相容問題
- 通過一個更高效的方式來呼叫 Native API,這裡使用 dart:ffi 呼叫 C函式,再通過 Runtime 機制呼叫 Native
廣州品牌設計公司https://www.houdianzi.com
使用方法
假如你寫了個 Objective-C 的類叫 RuntimeStub,並實現了個 fooBlock:方法,引數和返回值都是個 block 物件。
@interface RuntimeStub () @end @implementation RuntimeStub typedef int(^BarBlock)(NSObject *a); - (BarBlock)fooBlock:(BarBlock)block { ... } @end
利用dart_objc寫 Dart 程式碼呼叫過程如下:
初始化一個 NSObject物件,傳入類名就可以 new任意型別的物件。 perform()方法可以呼叫任意物件的任何方法,跟 Objective-C 的用法基本一致。
NSObject stub = NSObject('RuntimeStub');
Block block = stub.perform(Selector('fooBlock:'), args: [barFunc]);
Objective-C 中 Block 這種匿名函式或閉包的概念在 Dart 中其實就是 Function,所以當引數是 Block 物件的時候,可以直接傳入一個與之函式簽名一樣的 Dart Function 物件。dart_objc會自動完成引數型別轉換和呼叫等一系列底層細節。所以用 Dart 實現的 barFunc與 Objective-C 介面BarBlock的簽名需要一致:
Function barFunc = (NSObject a) {
print('hello block! ${a.toString()}');
return 101;
};
Dart 呼叫 Block 也很簡單,呼叫 invoke方法就行:
int result = block.invoke([stub]);
最後也可以用 Dart 封裝下 RuntimeStub類,這樣呼叫程式碼更簡潔。這種模板程式碼後續會做成自動生成的,而不用手寫。
class RuntimeStub extends NSObject {
RuntimeStub() : super('RuntimeStub');
Block fooBlock(Function func) {
return perform(Selector('fooBlock:'), args: [func]);
}
}
後續
由於dart_objc元件還在基於 dev 版本的 Dart 開發,可能後續還會有比較大的變動,甚至是 API 的變化。所以沒有過多展開講實現細節,感興趣可以去自己看程式碼:https://github.com/yulingtianxia/dart_objc
目前的 Cocoa API 封裝打算參考 Swift 版本的文件,畢竟 Dart 有些語法跟 Swift 還有點像。
Android 平臺的實現也在規劃中,最終將會結束 Flutter 三端開發現狀,實現真正的前端大一統。