美文网首页
Swift 5.3 调用C语言API以及Unmanaged源码分

Swift 5.3 调用C语言API以及Unmanaged源码分

作者: Sunooo | 来源:发表于2020-07-26 10:56 被阅读0次

在使用Swift开发的时候,有时候会遇到与C语言交互,调用C语言的API。

// test.h
int add(int a, int b) ;

// test.c
int add(int a, int b) {
    return a + b;
}

在.h文件中有一个add函数的声明,在.c文件中有add函数的实现,这种非常简单的C语言函数,在Swift中的调用也非常简单,直接使用即可

let x = add(20, 30)
print(x)

但是如果是C语言的指针该如何调用呢

// test.h
int callback(void (* execute)(void *), void * obj) ;

// test.c
int callback(void (* execute)(void *), void * obj) {
    execute(obj);
    return 0;
}

声明一个返回值为int的函数,函数名称为callback,里面接收2个参数。
第一个参数 void (* execute)(void *) 是一个名称为execute的函数指针,它的返回值是void,参数是一个void *指针
第二个参数是void *指针
这个函数的作用就是接收一个指针,并将这个指针作为参数传递给函数指针。
那么如何调用上面这个C语言函数呢?

直接看代码吧

let user = User()
let userPtr = Unmanaged<User>.passRetained(user).toOpaque()

class User {
    func eat() {
        print(#file, #line, "eat now")
    }
    
    deinit {
        print("User instance deinit")
    }
}

首先定义一个User类,生成实例对象,接下来操控的都是user实例的指针,如果想取得的它的指针,就需要使用Unmanaged,如上,就取到了userPtr指针

var closureTest: @convention(c) (UnsafeMutableRawPointer?) -> Void = { userPtr in
    guard let userPtr = userPtr else { return }
    let user = Unmanaged<User>.fromOpaque(userPtr).takeRetainedValue()
    user.eat()
}

然后,定义一个符合void (* execute)(void *)类型的闭包closureTest,在swift符合类型的闭包写法就是这样的,必须要加上@convention(c)表明这是一个针对C语言的类型,UnsafeMutableRawPointer?就是Swift中指针的表示方式,使用之所以加问号,使用可选类型,是因为在C语言中它可以是空指针,可以为nil。使用Unmanaged将指针还原为user实例对象,调用eat方法,证明这个还原成功。
不要忘记在桥接文件中导入C语言的.h文件

callback(closureTest, userPtr)

这样就可以使用这个C语言函数了,他的原理很简单,就是取出实例对象的指针,再传递指针。
这里有一个很重要的类型就是Unmanaged,他是一个结构体,主要包含以下方法。

public struct Unmanaged<Instance> where Instance : AnyObject {
    public struct Unmanaged<Instance> where Instance : AnyObject {
    // 将实例对象作为属性,之后操作的其实都是这个属性
    internal unowned(unsafe) var _value: Instance
    // 从实例对象初始化一个Unmanaged对象,说白了就是对实例对象的一层封装
    internal init(_private: Instance) { _value = _private }
    // 从一个指针中还原Unmanaged对象
    public static func fromOpaque(_ value: UnsafeRawPointer) -> Unmanaged<Instance>
    // 将一个Unmanaged对象转换为指针
    public func toOpaque() -> UnsafeMutableRawPointer
    // 将实例对象包装为Unmanaged对象,并且引用计数 +1
    public static func passRetained(_ value: Instance) -> Unmanaged<Instance>
    // 将实例对象包装为Unmanaged对象
    public static func passUnretained(_ value: Instance) -> Unmanaged<Instance>
    // 从Unmanaged对象中还原实例对象
    public func takeUnretainedValue() -> Instance
    // 从Unmanaged对象中还原实例对象,并且引用计数 -1
    public func takeRetainedValue() -> Instance
    // 引用计数 +1
    public func retain() -> Unmanaged<Instance>
    // 引用计数 -1
    public func release()
    // 自动释放
    public func autorelease() -> Unmanaged<Instance>
}

它是如何实现的呢?
查看Swift开源代码可以帮助我们 Unmanaged.swift
还是很容易看懂的,Unmanaged其实就是实例对象和指针之间的媒介,负责相互之间的转换,需要注意的是平时使用的时候passRetainedtakeRetainedValue是一对,passUnretainedtakeUnretainedValue是一对。
如果熟悉引用计数的话就明白,前者在使用的时候进行了引用计数的增减,后者并没有,如果传递的对象中途有被销毁的风险,就要用前者。引用计数,有增就有减。

Unmanaged

后记

public func retain() -> Unmanaged {
    Builtin.retain(_value)
    return self
  }

在源码中看到Builtin.retain(_value),特别感兴趣想去查看Builtin是什么,他是如何计算引用计数的,retain release的如何实现的?
但是在Swift开源代码里没有找到,网上的信息也不是很多,在论坛看了几篇其他人的提问,结论就是Builtin就是LLVM编译器内置代码与外界的map,例如+符号的定义,Int类型的定义就是需要Bulitin,具体在里面做了什么,就是我们不得而知了的,他是闭源的。

Swift Forums上面的提问
Swift Forums

一篇很古老2016年的文章
swift-mysterious-builtin-module

相关文章

网友评论

      本文标题:Swift 5.3 调用C语言API以及Unmanaged源码分

      本文链接:https://www.haomeiwen.com/subject/cmsplktx.html