惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

有赞技术团队
有赞技术团队
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
P
Palo Alto Networks Blog
C
Cisco Blogs
The Hacker News
The Hacker News
T
Threatpost
S
Schneier on Security
K
Kaspersky official blog
Spread Privacy
Spread Privacy
博客园_首页
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
NISL@THU
NISL@THU
量子位
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Google DeepMind News
Google DeepMind News
Security Latest
Security Latest
博客园 - 司徒正美
云风的 BLOG
云风的 BLOG
博客园 - 叶小钗
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
News and Events Feed by Topic
爱范儿
爱范儿
P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
Project Zero
Project Zero
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Cisco Talos Blog
Cisco Talos Blog
GbyAI
GbyAI
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Apple Machine Learning Research
Apple Machine Learning Research
T
Tenable Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
V
Vulnerabilities – Threatpost
Forbes - Security
Forbes - Security
博客园 - 三生石上(FineUI控件)
C
Cyber Attacks, Cyber Crime and Cyber Security
N
News and Events Feed by Topic
V
V2EX
Webroot Blog
Webroot Blog
The Register - Security
The Register - Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Blog — PlanetScale
Blog — PlanetScale
M
MIT News - Artificial intelligence
Scott Helme
Scott Helme
Simon Willison's Weblog
Simon Willison's Weblog
L
LangChain Blog
W
WeLiveSecurity
Cloudbric
Cloudbric

yulingtianxia's blog

Flutter 官方终于出手了,DartNative 将何去何从? CPP-Summit 2020 实现 Native 异步回调 Flutter 如何实现 Flutter 同步调用 Native API DartNative 内存自动管理 如何实现一行命令自动生成 Flutter 插件 告别 Flutter Channel,调用 Native API 仅需一行代码! BlockHook and Memory Safety 在 Flutter 中玩转 Objective-C Block DartNative Struct DartNative Memory Management: C++ Non-Object DartNative Memory Management: NSObject 谈谈 dart_native 混合编程引擎的设计 用 Dart 来写 Objective-C 代码 App 二进制文件重排已经被玩坏了 BlockHook with Invocation(2) BlockHook with Invocation(1) BlockHook with Private Data BlockHook with Revocation
Passing Out Parameter in DartNative
杨萧玉 · 2020-04-25 · via yulingtianxia's blog

文章目录

  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 

建议在运行上面的 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






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 ** 那么多,等遇到的时候再搞吧。

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