0%

【文章翻譯】Dart 2.18: Objective-C & Swift interop

【文章內容使用 Gemini 1.5 Pro 自動翻譯產生】

Dart 2.18:Objective-C 與 Swift 互通性

Dart 2.18 現已推出。此版本預覽了 Objective-C 和 Swift 互通性,以及基於此互通性構建的新 iOS/macOS 網路套件。它還包含針對泛型函數的改進類型推斷、異步程式碼的效能改進、新的 pub.dev 功能,以及工具和核心函式庫的清理。

最後,我們有最新的空安全遷移狀態數據,以及關於我們邁向完全空安全 Dart 的重要路線圖更新。請閱讀到最後!

將 Dart 引入 Objective-C 與 Swift 互通性

我們在 2020 年預覽了用於調用原生 C API 的 Dart 外部函數介面 (FFI),並於 2021 年 3 月在 Dart 2.12 中發佈。自該版本以來,大量的套件利用此功能與現有的原生 C API 整合。一些例子包括 file_pickerprintingwin32objectboxrealmisartflite_flutterdbus

Dart 團隊希望 Dart 支援與 Dart 運行的平台上的所有主要語言的互通性。Dart 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 偏移量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
@autoreleasepool {
NSTimeZone *timezone = [NSTimeZone systemTimeZone];
NSInteger secondsFromGMT = [timezone secondsFromGMT];
int hoursFromGMT = secondsFromGMT / 3600;

NSLog(@"Timezone name: %@", [timezone name]);
NSLog(@"Timezone offset GMT: %d hours", hoursFromGMT);
}
return 0;
}

該應用程式匯入了 Foundation.h,其中包含 Apple Foundation 函式庫的 API 標頭檔。接下來,在 main 方法內,它從 NSTimeZone 類別調用 systemTimeZone 方法。此方法返回一個 NSTimeZone 實例,其中包含設備上選定的時區。最後,應用程式向控制台輸出兩行,其中包含時區的名稱和以小時為單位的 UTC 偏移量。

如果您運行此應用程式,它應該返回類似以下內容,具體取決於您的位置:

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

使用 Dart 的時區範例

讓我們使用新的 Objective-C 互通性在 Dart 中複製此結果。

首先建立一個新的 Dart CLI 應用程式:

1
$ dart create timezones

然後編輯您的 pubspec 檔案以包含 ffigen 設定。此設定指向標頭檔,並列出應該生成包裝器的 Objective-C 介面:

1
2
3
4
5
6
7
8
9
10
ffigen:
name: 'FoundationBindings'
output: 'foundation_bindings.dart'
headers:
entry-points:
- '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/Headers/NSTimeZone.h'
objc-interfaces:
include:
- 'NSTimeZone'
description: 'Bindings to Foundation'

這將為 NSTimeZone.h 中的標頭檔選擇 Objective-C 綁定,並且僅包含 NSTimeZone 介面中的 API。要生成包裝器,請運行 ffigen

1
$ dart run ffigen

此命令建立一個新檔案 foundation_bindings.dart,其中包含一堆生成的 API 綁定。使用此綁定檔案,我們可以編寫我們的 Dart main 方法。此方法反映了 Objective-C 程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import 'dart:ffi';
import 'dart:io';

import 'package:ffi/ffi.dart';
import 'package:timezones/foundation_bindings.dart';

typedef SystemTimeZoneC = Pointer<NSTimeZone> Function();
typedef SystemTimeZoneDart = Pointer<NSTimeZone> Function();

typedef SecondsFromGMTC = Int64 Function(Pointer<NSTimeZone>);
typedef SecondsFromGMTDart = int Function(Pointer<NSTimeZone>);

typedef GetNameC = Pointer<Utf8> Function(Pointer<NSTimeZone>);
typedef GetNameDart = Pointer<Utf8> Function(Pointer<NSTimeZone>);


void main() {
final dylib = DynamicLibrary.open(Platform.isMacOS ? '/System/Library/Frameworks/Foundation.framework/Versions/Current/Foundation' : 'Foundation');

final systemTimeZonePointer = dylib.lookupFunction<SystemTimeZoneC, SystemTimeZoneDart>('NSTimeZone_systemTimeZone');
final timezone = systemTimeZonePointer();

final secondsFromGMTPointer = dylib.lookupFunction<SecondsFromGMTC, SecondsFromGMTDart>('NSTimeZone_secondsFromGMT');
final secondsFromGMT = secondsFromGMTPointer(timezone);
final hoursFromGMT = secondsFromGMT / 3600;


final getNamePointer = dylib.lookupFunction<GetNameC, GetNameDart>('NSTimeZone_name');
final name = getNamePointer(timezone).toDartString();

print('Timezone name: $name');
print('Timezone offset GMT: $hoursFromGMT hours');
}

就這樣!從今天的 Dart 2.18 開始,此新支援以實驗狀態提供。這增強了 Dart 的通用互通性支援,可以直接調用 macOS 和 iOS API。反過來,這補充了 Flutter 的 Plugin,提供了在任何 Dart 應用程式中都能運作的新支援,並允許您直接從 Dart 程式碼調用 macOS 和 iOS API。

我們歡迎您的回饋。請透過在 GitHub 上的 回饋議題 中評論,讓我們知道哪些有效、哪些可以更改或您遇到的問題。要進一步了解此互通性,請參閱 Objective-C 和 Swift 互通性指南

平台特定的 http 函式庫

Dart 包含一個通用的多平台 http 函式庫。此函式庫允許您編寫程式碼,而無需考慮平台細節。有時,您可能希望編寫特定於特定主機平台網路 API 的程式碼。

例如,Apple 的網路函式庫 NSURLSession 允許指定僅限 WiFi 的網路或需要 VPN。為了支援這些使用案例,我們建立了一個新的網路套件,用於 macOS 和 iOS 平台,cupertino_http。這是建立在上一節提到的新的 Objective-C 互通性之上的。它使用從 Foundation 中 Apple 的網路 API 生成 的大量 API 包裝器。

Cupertino http 函式庫範例

以下範例將 Flutter 應用程式的 http 用戶端設定為在 macOS 和 iOS 上使用 cupertino_http 函式庫,並在其他平台上使用來自 dart:io 的 Dart 常規 http 函式庫:

1
2
3
4
5
6
7
8
9
10
import 'package:cupertino_http/cupertino_http.dart' as cupertino;
import 'package:http/http.dart' as http;

late final http.Client client;

if (Platform.isIOS || Platform.isMacOS) {
client = cupertino.Client();
} else {
client = http.Client();
}

在此初始設定之後,應用程式會在特定用戶端上進行任何後續的網路調用。例如,http get() 請求現在類似於:

1
2
3
4
final url = Uri.parse('https://example.com/whatsit/create');
final response = await client.get(url);
print('Response status: ${response.statusCode}');
print('Response body: ${response.body}');

當您無法使用通用用戶端介面時,您可以使用 cupertino_http 函式庫直接調用 Apple 的網路 API:

1
2
3
4
5
6
7
8
final session = cupertino.cupertinoHttp.NSURLSession.sharedSession();

// Now you can use session to perform network requests such as
// dataTaskWithRequest, dataTaskWithURL, etc
// For example to get data from a URL:
final url = cupertino.cupertinoHttp.NSURL(string: 'www.google.com')!;
// Now use the URL to get data or perform any other NSURLSession task.

多平台應用程式中的平台特定網路

當我們設計此功能時,目標仍然是盡可能保持應用程式的多平台性。為了實現此目標,我們保留了用於基本 http 操作的通用多平台 http API 集,並允許為每個平台設定要使用的網路函式庫。您可以透過使用 package:http Client API 來最大限度地減少您需要編寫的平台特定程式碼的數量。此 API 可以為每個平台進行設定,但以平台獨立的方式使用。

Dart 2.18 提供了對兩個支援 package:http Client API 的平台特定 http 函式庫的實驗性支援:

將通用用戶端 API 與多個 HTTP 實作相結合,為您提供了兩全其美的優勢。您可以獲得平台特定的行為,同時仍然可以從所有平台的單一共享來源集中維護您的應用程式。我們很樂意聽到您對此 GitHub 議題 的回饋。

改進的類型推斷

Dart 使用許多泛型函數。考慮 fold 方法,它將元素的集合簡化為單個值。以下範例計算整數列表的總和:

1
2
3
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 或更早版本中,此方法返回類型錯誤:

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

Dart 的類型推斷無法在參數之間傳遞資訊。這導致 x 的類型不確定。要解決潛在的錯誤,您需要指定類型:

1
final sum = numbers.fold(0, (int x, int y) => x + y);

Dart 2.18 改進了類型推斷。之前的範例通過了靜態分析,並且可以推斷 xy 都是非空的整數。此更改允許您編寫更簡潔的 Dart 程式碼,同時保留強類型推斷的完整可靠性屬性。

異步效能改進

此版本的 Dart 改進了 Dart VM 應用異步方法和 async*/sync* 生成器函數的方式。這減少了程式碼大小。在兩個大型內部 Google 應用程式上,我們看到 AOT 快照大小減少了大約 10%。我們還看到微基準測試中的效能有所提高。

這些更改包括其他一些小的行為更改;要進一步了解,請參閱 變更日誌

pub.dev 的改進

與 2.18 版本一起,我們對 pub.dev 套件儲存庫進行了兩項更改。

個人通常在業餘時間維護發佈在 pub.dev 上的套件。這在時間和財務方面都可能付出高昂的代價。為了促進贊助,我們現在在 pubspec 中支援一個新的 funding 標籤,套件發佈者可以使用它來列出一個或多個贊助套件的連結。然後,這些連結會顯示在 pub.dev 的側邊欄中:

要進一步了解,請參閱 pubspec 文件

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

一些重大變更

Dart 非常注重簡潔性和可學習性。在添加新功能時,我們一直在努力保持謹慎的平衡。保持事物簡潔的一種方法是移除使用較少或有更好替代方案的歷史功能和 API。Dart 2.18 清理了此類別中的項目,包括一些較小的重大變更:

  • 我們在 2.18 中完成了過渡。此版本移除了最後兩個已棄用的工具 dart2js(使用 dart compile js)和 dartanalyzer(使用 dart analyze)。
  • 隨著語言版本控制的引入,pub 生成了一個新的解析檔案:.dart_tool/package_config.json。之前的檔案 .packages 使用的格式無法包含版本。我們停止使用 .packages 檔案。如果您有任何 .packages 檔案,您可以將其刪除。
  • 不能使用不擴展 Object 的類別的 Mixin(重大變更 #48167)。這種行為從未被設計過。
  • dart:ioRedirectExceptionuri 屬性已更改為可空(重大變更 #49045)。
  • dart:io 的網路 API 中遵循 SCREAMING_SNAKE 約定的常數已被移除(重大變更 #34218;先前已棄用)。請改用相應的 lowerCamelCase 常數。
  • Dart VM 不再在退出時恢復初始終端設定。更改 Stdin 設定 lineModeechoMode 的程式現在負責在程式退出時恢復設定(重大變更 #45630)。

空安全更新

我們很高興看到自 2020 年 11 月的 Beta 版本和 2021 年 3 月的 Dart 2.12 版本以來,空安全的廣泛使用。

首先,pub.dev 上幾乎所有流行套件的應用程式開發人員都遷移到了空安全。分析顯示,前 250 名中最常用的套件中有 100% 支援空安全,前 1,000 名中最常用的套件中有 98% 支援空安全。

其次,大多數應用程式開發人員都在完全遷移到空安全的程式碼庫中工作。這一點至關重要。Dart 的完整 可靠空安全 在您遷移所有程式碼和所有依賴項(包括傳遞依賴項)之前不會生效。我們正在透過 flutter run 命令的遙測數據來追蹤這一點。

下圖顯示了 flutter run 的不可靠與可靠空安全執行。在引入空安全之前,兩者都沒有。隨後是不可靠空安全的快速增長。隨著應用程式開始遷移到空安全,開發人員進行了部分遷移。有些部分仍然需要遷移。隨著時間的推移,我們看到可靠空安全會話的健康增長。到上個月底,可靠空安全會話的數量是不可靠空安全會話的四倍。我們希望在接下來的幾個季度中,我們將看到可靠空安全方法達到 100%!

空安全路線圖的重要更新

同時支援不可靠和可靠的空安全會增加開銷和複雜性。

首先,Dart 開發人員需要學習和理解兩種模式。每當閱讀一段 Dart 程式碼時,請檢查 語言版本,以查看類型預設是否為非空(Dart 2.12 及更高版本)或可空(Dart 2.11 及更早版本)。

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

基於不可靠空安全的開銷以及上一節提到的非常積極的採用數據,我們的目標是過渡到僅支援可靠空安全,並停止非空安全和不可靠空安全模式。我們暫定於 2023 年年中發佈此版本。

這意味著將停止支援 Dart 2.11 及更早版本。SDK 約束下限小於 2.12 的 Pubspec 檔案將不再在 Dart 3 及更高版本中解析。在包含語言標記的原始程式碼中,如果設定小於 2.12(例如 // @dart=2.9),則會失敗。

如果您已遷移到可靠空安全,您的程式碼將在 Dart 3 中以完全空安全的方式工作。如果您還沒有遷移,請立即遷移!要進一步了解這些更改,請參閱此 GitHub 議題

總結

對互通性、網路、類型推斷和 pub.dev 的新支援現已推出。要開始使用,您可以直接下載 Dart 2.18 版本,或者將其作為今天的 Flutter 3.3 SDK 版本的一部分嵌入。


Dart 2.18:Objective-C 與 Swift 互通性 最初發佈在 Medium 的 Dart 上,人們在那裡透過醒目顯示和回應這個故事來繼續討論。