Dart 2.18 發佈,Objective-C 和 Swift interop

語言: CN / TW / HK

theme: smartblue

原文鏈接: https://medium.com/dartlang/dart-2-18-f4b3101f146c

Dart 2.18 版本開始提供與 Objective-C 和 Swift 交互的能力預覽,以及在這基礎上構建的新 iOS / macOS 包支持。

Dart 2.18 還包含對通用函數的類型推斷改進、異步代碼的性能改進、新的 pub.dev 功能支持以及對工具和核心庫的整理

最後,還有最新的 null safety 遷移狀態解析,以及通往完全 null safety 的重要路線圖更新。

Dart 支持與 Objective-C 和 Swift 交互的能力

在 2020 年的時候我們預覽了用於調用原生 C API 的 Dart 外函數接口(FFI),並於 2021 年 3 月在 Dart 2.12 中發佈了它。

自該版本發佈以來,大量軟件包利用此功能與現有的原生C API集成,例如: file_pickerprintingwin32objectboxrealmisartflite_flutterdbus 等。

Dart 團隊希望支持所運行平台上所有主要語言的交互能力,而 Dart 2.18達到了實現這一目標的下一個里程碑

在 2.18, Dart 代碼可以調用 Objective-C 和 Swift 代碼,這通常用於調用 macOS 和 iOS 平台上的API,Dart在任何應用中都支持這種互操作機制,從CLI 應用到後端代碼和 Flutter UI。

這種新機制其實是利用了 Objective-C 和 Swift 代碼可以基於 API 綁定 C 代碼公開,Dart API 包裝了生成工具 ffigen ,可以從 API 標頭創建這些綁定。

使用Objective-C的時區示例

macOS 有一個 API 可用於查詢 NSTimeZone 上公開的時區信息,開發者可以查詢該 API 以瞭解用户為其設備配置的時區和 UTC 時區偏移量

以下示例中 Objective-C 使用此時區 API 獲取系統時區和GMT偏移量:

```

import

​ int main(int argc, const char * argv[]) {    @autoreleasepool {        NSTimeZone *timezone = [NSTimeZone systemTimeZone]; // Get current time zone.        NSLog(@"Timezone name: %@", timezone.name);        NSLog(@"Timezone offset GMT: %ld hours", timezone.secondsFromGMT/60/60);   }    return 0; } ```

這裏導入了 Foundation.h,其中包含 Apple Foundation 庫的 API headers。

接下來,在 main 方法中,它從 NSTimeZone 類調用了 systemTimeZone 方法,此方法返回設備上選定時區的 NSTimeZone 實例。

最後,應用向控制枱輸出兩行結果,其中包含時區名稱和UTC偏移量(以小時為單位)。

如果運行此程序,它應該會返回類似於以下內容的東西,具體取決於開發者的位置:

Timezone name: Europe/Copenhagen Timezone offset GMT: 2 hours

使用 Dart 的時區示例

讓我們使用新的 Dart 與 Objective-C 一起重新實現上面的結果。

首先創建一個新的 Dart CLI :

$ dart create timezones

然後編輯 pubspec文件以包含 ffigen 配置,配置指向頭文件,並列出了哪些 Objective-C 接口應該生成包裝器:

​ ffigen: name: TimeZoneLibrary language: objc output: "foundation_bindings.dart" exclude-all-by-default: true objc-interfaces:   include:     - "NSTimeZone" headers:   entry-points:     - "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/         Headers/NSTimeZone.h"

這就為 NSTimeZone.h 中的 headers 選擇 Objective-C 綁定,並僅包括NSTimeZone接口中的API,要生成 wrappers, 可以允行 ffigen

$ dart run ffigen

該命令會創建一個新文件 foundation_bindings.dart,其中包含一堆生成的API綁定,使用該綁定文件,就可以編寫 Dart main 方法,此方法鏡像Objective-C 代碼:

void main(List<String> args) async {  const dylibPath =      '/System/Library/Frameworks/Foundation.framework/Versions/Current/Foundation';  final lib = TimeZoneLibrary(DynamicLibrary.open(dylibPath)); ​  final timeZone = NSTimeZone.getLocalTimeZone(lib);  if (timeZone != null) {    print('Timezone name: ${timeZone.name}');    print('Offset from GMT: ${timeZone.secondsFromGMT / 60 / 60} hours'); } }

就這樣,從 Dart 2.18 開始,這種新的支持在實驗狀態下可用,該能力增強了Dart 的交互支持,以直接調用 macOS 和 iOS API 支持。

並且這也反向補充了 Flutter 的插件,提供了允許開發者直接從 Dart 代碼調用macOS 和 iOS API 的能力。

要了解有關這種互操作性的更多信息,請參閲 Objective-C 和 Swift 交互指南

特定於平台的http庫

Dart 裏包括一個通用的多平台http庫,該庫允許開着編寫代碼而無需考慮平台細節,但是有時候開發者可能希望編寫特定於特定 native 平台的 網絡 API的代碼,例如:蘋果的網絡 庫NSURLSession允許指定僅限 WiFi 或 VPN的網絡。

為了支持這些用例,我們為 macOS 和 iOS 平台創建了一個新的網絡包 cupertino_http,該能力建立在上一節中提到的 Objective-C 直接交互的基礎上。

Cupertino http library 示例

以下示例將 Flutter 的 http 客户端設置為在其他平台上使用 cupertino_http庫,以及 dart:io 下的 http 庫:

​ late Client client; if (Platform.isIOS || Platform.isMacOS) {  final config = URLSessionConfiguration.ephemeralSessionConfiguration()   ..allowsCellularAccess = false   ..allowsExpensiveNetworkAccess = false;  client = CupertinoClient.fromSessionConfiguration(config); } else {  client = Client(); // Uses an HTTP client based on dart:io }

初始配置後,應用會對特定客户端進行後續網絡調用,例如 http get() 請求現在類似於以下內容:

final response = await get(  Uri.https(    'www.googleapis.com',    '/books/v1/volumes',   {'q': 'HTTP', 'maxResults': '40', 'printType': 'books'}, ), );

當開發者無法使用通用客户端接口時,就可以直接使用 cupertino_http 庫調用蘋果的網絡API:

``` final session = URLSession.sessionWithConfiguration( URLSessionConfiguration.backgroundSession('com.example.bgdownload'), onFinishedDownloading: (s, t, fileUri) { actualContent = File.fromUri(fileUri).readAsStringSync(); });

final task = session.downloadTaskWithRequest( URLRequest.fromUrl(Uri.https(...)) ..resume(); ```

多平台應用程序中特定於平台的網絡

在設計該功能時,目標仍然是使應用盡支持更多的平台,為了實現這個目標,我們為基本的 http 操作保留了通用的多平台 http API 集,並允許為每個平台配置要使用的網絡庫。

package:http 將需要編寫的特定於平台的代碼量降至最低,此 API 可以按平台配置,但以獨立於平台的方式使用。

Dart 2.18 提供了對兩個對於 package:http 特定於平台的 http 庫的實驗性支持:

  • cupertino_http 基於 NSURLSession 的 macOS/iOS 支持。
  • cronet_http基於 Cronet,Android 上流行的網絡庫支持。

將一個通用客户端 API 與多個 HTTP 實現相結合,以獲得特定於平台的行為,同時仍然從所有平台的一組共享源中維護應用。

改進的類型推斷

Dart 使用了許多通用函數,例如 fold方法,它將元素集合減少為單個值,如計算整數列表的總和:

List<int> numbers = [1, 2, 3]; final sum = numbers.fold(0, (x, y) => x + y); print(‘The sum of $numbers is $sum’);

對於 Dart 2.17 或更早版本,這個方法返回類型錯誤:

line 2 • The operator ‘+’ can’t be unconditionally invoked because the receiver can be ‘null’.

Dart 2.18 改進了類型推斷,前面的示例通過了靜態分析,可以推斷出 x 和 y 都是不可為空的整數,此更改允許開發者編寫更簡潔的 Dart 代碼,同時保留強推斷類型的完整可靠性屬性。

異步性能改進

此版本的 Dart 改進了 Dart VM 應用 async 方法和 async*/sync*生成器功能的方式。

這減少了代碼大小,在兩個大型內部 Google 應用程序中,我們看到 AOT 快照大小減少了約 10%,還可以看到微基準測試的性能有所提高。

這些變化包括額外的小行為變化;要了解更多信息,請參閲更改日誌

pub.dev 改進

結合 2.18 版本,我們對 pub.dev包 存儲庫進行了兩項更改。

個人業餘時間通過 pub.dev 維護和發佈的可能會產生一些時間上的投入,為了促進贊助,我們現在在 中支持一個新 funding 標籤,pubspec包發佈者可以使用該標籤列出指向一種或多種贊助包的方式的鏈接。然後這些鏈接顯示pub.dev在側邊欄中:

要了解更多信息,請參閲pubspec文檔

此外,我們希望鼓勵豐富的開源軟件包生態系統,為了突出這一點,自動包評分對使用 OSI 批准的許可證pub.dev包額外獎勵 10 分。

一些重大變化

Dart 非常注重簡單和易學的能力,在添加新功能時,我們一直在努力保持謹慎的平衡。

保持簡單的一種方法是刪除歷史功能和 API,Dart 2.18 清理了此類別中的項目,包括一些較小的重大更改:

  • 我們早在 2020 年 10 月就添加了統一的 dart CLI 開發人員工具,在 2.18 中我們完成了過渡。此版本刪除了最後兩個已棄用的工具 dart2js (use dart compile js) 和 dartanalyzer (use dart analyze)。
  • 隨着語言版本控制的引入,pub生成了一個新的解析文件:.dart_tool/package_config.json 。 之前的 .packages 文件使用了一種不能包含版本的格式,而現在我們停止使用 .packages文件,如果你有任何.packages文件,現在可以刪除它們了。
  • 不能使用未擴展的類的混合 Object(重大更改#48167)。
  • dart:ioRedirectExceptionuri 屬性已更改為可為空(重大更改#49045)。
  • dart:io遵循 SCREAMING_SNAKE 約定的網絡 API 中的常量已被刪除(重大更改# 34218;以前已棄用),請改用相應的 lowerCamelCase 常量。
  • Dart VM 在退出時不再恢復初始終端設置,更改 Stdin 設置 lineModeechoMode 現在負責在程序退出時恢復設置(重大更改#45630)。

空安全更新

自 2020 年 11 月發佈測試版和 2021 年 3 月發佈 Dart 2.12 以來,我們很高興看到 null 安全性的廣泛使用。

首先,大多數流行包的開發人員都在 pub.dev 遷移到了零安全性,分析表明,100% 的前 250 個和 98% 的前 1000 個最常用的包支持零安全。

其次,大多數應用開發人員在具有完全空安全遷移的代碼庫中工作,這是至關重要的條件,在遷移所有代碼和所有依賴項(包括傳遞性)之前, Dart 健全的 null safety 不會發揮作用。

下圖顯示了 flutter run 在引入零安全和沒有引起之間的對比,隨着應用開始遷移到零安全,開發人員進行了部分遷移,但仍存在部分內容未遷移到 null safety。

隨着時間的推移可以看到, null safety 使用在健康地增長。到上月底,與不使用 null safety 相比, null safety 多出四倍,所以我們希望,在接下來的幾個季度中,我們將看到 100% 的可靠零安全方法。

重要的零安全路線圖更新

同時支持空安全和非空安全會增加開銷和複雜性。

首先,Dart 開發者需要學習和理解這兩種模式,每當閲讀一段 Dart 代碼時,檢查語言版本以查看類型是否默認為非空(Dart 2.12 及更高版本)或默認可空(Dart 2.11 及更早版本)。

其次,在我們的編譯器和運行時同時支持這兩種模式會減慢 Dart SDK 的發展以支持新功能。

基於非空安全的開銷和上一節中提到的非常積極的採用數字,我們的目標是過渡到僅支持可靠的空值安全,並停止非空值安全和不健全的空值安全模式,我們暫時將其定於 2023 年年中發佈

這將意味着停止對 Dart 2.11 及更早版本的支持,具有低於 2.12 的 SDK 約束的 Pubspec 文件將不再在 Dart 3 及更高版本中解析。

在包含語言標記的源代碼中,如果設置為小於 2.12(例如// @dart=2.9)也會失敗。

如果已遷移到可靠的 null 安全性,那麼你的代碼將在 Dart 3 中以完全的 null 安全性工作,如果還沒有,請立即遷移!

要了解有關這些更改的更多信息,請參閲此 GitHub 問題