文章目录
  1. 1. 封装 Objective-C 里的 Out Parameter
  2. 2. 从 Out Parameter 取值
    1. 2.1. 建立泛型与初始化的映射
    2. 2.2. 自动生成注册代码
    3. 2.3. 自动取值
  3. 3. 后续

dart_native 作为一条比 Channel 性能更高开发成本更低的超级通道,通过 C++ 调用 Native 的 API,深入底层且考虑全面。很多 Objective-C 接口含有 NSError ** 这种 out parameter,dart_native 也对这种场景做了支持。

封装 Objective-C 里的 Out Parameter

说白了用的最多的就是 “A pointer to a pointer” 啊!NSError ** 啊!

1
2
NSError *error;
[self fooWithError:&error];

那换成 Dart 语言该咋表示呢???首先要知道 Dart 是不支持 out parameter 的,只能另辟蹊径,在语法上做一些妥协,最终跑通流程实现目的。

1
2
NSObjectRef<NSObject> ref = NSObjectRef<NSObject>();
fooWithError(ref);

还记得之前 dart_native 是如何封装 NSObject * 的么?用一个同名的 Dart 类包一个 OC 对象的指针就行了。那想封装 out parameter 的话,在此基础之上再套一层不就行了!只要用泛型,就能一层层套下去。。。

1
2
3
4
class NSObjectRef<T extends id> {
T value;
Pointer<Pointer<Void>> _ptr;
}

接着要考虑如何初始化 out parameter 了。在 OC 里只需要在栈上的一个地址就够了,也就是声明一个变量。但 Dart 的对象并没有对应指针的概念,但是可以通过 dart ffi 手动创建一个指向指针的指针。不过它指向的内存是在堆上,需要手动释放。此时可以通过我之前讲内存管理的文章里讲到的 PointerWrapper 来实现临时指针变量的自动释放,简单来说就是把 dart ffi 创建的内存交给 OC ARC 管理。

加上构造方法和自动释放后的 NSObjectRef 实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class NSObjectRef<T extends id> {
T value;
Pointer<Pointer<Void>> _ptr;
Pointer<Pointer<Void>> get pointer => _ptr;

NSObjectRef() {
_ptr = allocate<Pointer<Void>>();
_ptr.value = nullptr;
PointerWrapper wrapper = PointerWrapper(_dealloc);
wrapper.value = _ptr.cast<Void>();
}

NSObjectRef.fromPointer(this._ptr);

_dealloc() {
_ptr = null;
}
}

从 Out Parameter 取值

Dart 侧把一个指针传给 OC 后,OC 会创建另一个指针,并把后者赋值给前者指向的内存。还是拿 NSError 举例子:

1
2
3
4
5
- (void)fooWithError:(out NSError **)error {
if (error) {
*error = [NSError errorWithDomain:@"com.dartnative.test" code:-1 userInfo:nil];
}
}

下一步是要将上例中 OC 的 NSError 对象转成 Dart 的对象,并赋值给 NSObjectRefvalue 属性上。

建立泛型与初始化的映射

面对不同泛型的 NSObjectRef 声明,要转成其封装类型的对象。而 Flutter 禁用的 Dart 的反射,即不能通过 NSObjectRef 声明的泛型来初始化对应的类。我维护了个 Map 来建立起 Type 到初始化调用的映射,并提供注册方法:

1
2
3
4
5
6
7
8
9
typedef dynamic ConvertorFromPointer(Pointer<Void> ptr);

Map<String, ConvertorFromPointer> _convertorCache = {};

void registerTypeConvertor(String type, ConvertorFromPointer convertor) {
if (_convertorCache[type] == null) {
_convertorCache[type] = convertor;
}
}

这样调用 registerTypeConvertor 函数就可以很方便地建立起 Native 封装类型到初始化闭包的映射:

1
2
3
registerTypeConvertor('NSString', (ptr) {
return NSString.fromPointer(ptr);
});

接着实现 convertFromPointer 函数,用来调用之前注册的闭包,这样就实现用类名和指针来获取到对应的 Dart 对象了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
dynamic convertFromPointer(String type, dynamic arg) {
Pointer<Void> ptr;
if (arg is NSObject) {
ptr = arg.pointer;
} else if (arg is Pointer) {
ptr = arg;
} else {
return arg;
}

if (ptr == nullptr) {
return arg;
}

ConvertorFromPointer convertor = _convertorCache[type];
if (convertor != null) {
return convertor(ptr);
} else if (arg is Pointer) {
return NSObject.fromPointer(arg);
}
return arg;
}

最后在 NSObjectRef 里添加了个 syncValue 方法,将转换好的 Dart 对象赋值给 value 属性:

1
2
3
4
5
syncValue() {
if (_ptr != null && _ptr.value != nullptr) {
value = convertFromPointer(T.toString(), _ptr.value);
}
}

自动生成注册代码

那么多 Native 类型,总不能手写代码一个个去调用 registerTypeConvertor 吧。dart_native 提供了 Annotation 用于自动生成这些注册代码,只需要在封装 Native 类的上面加一个 @native 即可:

1
2
3
4
5
6
@native
class NSString extends NSSubclass<String> {
NSString.fromPointer(Pointer<Void> ptr) : super.fromPointer(ptr) {
value = perform(SEL('UTF8String'));
}
}

这样只需要在项目目录里运行下面的命令,所有加了 @native 的类都会在同一个 dart 文件中生成注册初始化闭包的代码:

1
flutter packages pub run build_runner build --delete-conflicting-outputs

建议在运行上面的 build 之前先 clean 下:

1
flutter packages pub run build_runner clean

这是 dart_native 里带的一份自动生成的文件 ``:

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
35
36
37
38
39
40
41
42
43
44
// GENERATED CODE - DO NOT MODIFY BY HAND

// **************************************************************************
// DartNativeGenerator
// **************************************************************************

import 'package:dart_native/dart_native.dart';
import 'package:dart_native/src/ios/foundation/collection/nsarray.dart';
import 'package:dart_native/src/ios/foundation/collection/nsdictionary.dart';
import 'package:dart_native/src/ios/foundation/collection/nsset.dart';
import 'package:dart_native/src/ios/foundation/nsvalue.dart';
import 'package:dart_native/src/ios/foundation/nsnumber.dart';
import 'package:dart_native/src/ios/foundation/notification.dart';
import 'package:dart_native/src/ios/foundation/nsstring.dart';

void runDartNative() {
registerTypeConvertor('NSArray', (ptr) {
return NSArray.fromPointer(ptr);
});

registerTypeConvertor('NSDictionary', (ptr) {
return NSDictionary.fromPointer(ptr);
});

registerTypeConvertor('NSSet', (ptr) {
return NSSet.fromPointer(ptr);
});

registerTypeConvertor('NSValue', (ptr) {
return NSValue.fromPointer(ptr);
});

registerTypeConvertor('NSNumber', (ptr) {
return NSNumber.fromPointer(ptr);
});

registerTypeConvertor('NSNotification', (ptr) {
return NSNotification.fromPointer(ptr);
});

registerTypeConvertor('NSString', (ptr) {
return NSString.fromPointer(ptr);
});
}

考虑到 Flutter 的 plugin 和 App 都可能会用到 dart_native,那么各自的 Native 类就都要生成对应的注册代码。所以这里的入口函数名是根据 package 名生成的,不用担心重名问题。

利用 Annotation 自动生成代码的实现原理就不细说了,网上文章很多,可以参考闲鱼的 annotation_route。我只是做了一点微小的优化工作,可能以后也不会单开一片文章来讲。

PS: 自动生成代码这块一开始是给 callback 功能用的,这里写下,只是蹭了蹭篇幅。

自动取值

syncValue() 方法实现后就比较简单了,下一步就只是找个合理的时机调用的问题了。这只需要在 dart_nativemsgSend 方法中加入对参数类型的判断。如果是 NSObjectRef 类型,则需要在调用完 Native 侧的方法后再次调用它的 syncValue() 方法。

这里仅截取一段相关的实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 省略部分逻辑
List<NSObjectRef> outRefArgs = [];
// 省略部分逻辑
if (args != null) {
// 省略部分逻辑
for (var i = 0; i < argCount; i++) {
var arg = args[i];
if (arg == null) {
arg = nil;
} else if (arg is NSObjectRef) {
outRefArgs.add(arg);
}
// 省略部分逻辑
}
}
outRefArgs.forEach((ref) => ref.syncValue());

dart_native 中的msgSend 方法顾名思义,虽然表面上是复刻 OC 的实现,实则接口和原理差很多。这里也不详细展开讲,感兴趣的可以直接去看代码。

后续

NSObjectRef 目前只考虑了对 NSObject 及其子类的 out parameter 的封装,理论上对其他基本类型和结构体也是可以支持的,不过使用场景可能没 NSError ** 那么多,等遇到的时候再搞吧。

内行看门道,外行看热闹。我这么简单的内容都能水出一篇文章,跪求大佬们轻喷,不嘲笑就好。

文章目录
  1. 1. 封装 Objective-C 里的 Out Parameter
  2. 2. 从 Out Parameter 取值
    1. 2.1. 建立泛型与初始化的映射
    2. 2.2. 自动生成注册代码
    3. 2.3. 自动取值
  3. 3. 后续