我试图从Swift访问需要使用C回调的C API。
typedef struct {
void * info;
CFAllocatorRetainCallBack retain;
CFAllocatorReleaseCallBack release;
} CFuncContext;
typedef void (* CFuncCallback)(void * info);
CFuncRef CFuncCreate(CFuncCallback callback, CFuncContext * context);
void CFuncCall(CFuncRef cfunc);
void CFuncRelease(CFuncRef cfunc);
CFuncCreate
将回调和上下文存储在堆上(使用malloc),然后调用retain
回调以保留信息指针。 CFuncCall
只是为了演示而调用回调,CFuncRelease
调用release
回调,然后释放内存。
在Swift代码中,我想使用info
的专用对象,它将弱引用保留回主对象。当主对象为deinit
时,调用CFuncRelease
以清理C API内存。
这样,即使C API决定从不同的运行循环执行一些延迟回调,它总是有一个有效的info
指针,直到它最终完成时决定调用release
回调。只是一些防御性编程:)
info
对象具有以下结构:
final class CInfo<T: AnyObject> {
/// This contains the pointer back to the main object.
weak private(set) var object: T?
init(_ object: T) {
self.object = object
}
/// This variable is used to hold a temporary strong
/// `self` reference while retainCount is not 0.
private var context: CInfo<T>?
/// Number of times that `retain` has been called
/// without a balancing `release`.
private var retainCount = 0 {
didSet {
if oldValue == 0 {
context = self
}
if retainCount == 0 {
context = nil
}
}
}
func retain() {
++retainCount
}
func release() {
--retainCount
}
}
我的主要对象SwiftObj
使用C API。
final class SwiftObj {
typealias InfoType = CInfo<SwiftObj>
private lazy var info: InfoType = { InfoType(self) }()
private lazy var cFunc: CFuncRef = {
var context = CFuncContext(
info: &self.info,
retain: { UnsafePointer<InfoType>($0).memory.retain(); return $0 },
release: { UnsafePointer<InfoType>($0).memory.release() }
)
return CFuncCreate(
/* callback: */ cFuncCallback,
/* context: */ &context
)
}()
func call() {
CFuncCall(cFunc)
}
deinit {
CFuncRelease(cFunc)
}
init() {
call()
}
}
func cFuncCallback(info: UnsafeMutablePointer<Void>) {
print("==> callback from C")
}
在我的测试代码中,我首先通过IBAction分配SwiftObj
。然后,我通过第二次IBAction再次将参考设置回nil
。
自动清理代码现在应该正确地删除SwiftObj
,并且应该通知C API它应该清理它的内存。然后,应该调用release
指针上的info
回调,这导致info
指针的引用计数达到零,并且还释放info
指针
然而,deinit理论似乎没有成功。删除SwiftObj
的最终引用后,在调用release
回调闭包时添加另一个引用,并在CInfo.release
方法期间重新添加另一个引用(如使用Instrument的Allocations跟踪器观察。行为还取决于时间 - 例如日志语句的数量。
使用如下所示的最小样本,它会在初始release
回调闭包中立即崩溃,这两个消息中的任何一个取决于时间 - 第一个更常见,第二个可以通过快速实现空间标签空间,如果你很幸运。也许还有更多 - 正如我所说,在我的完整示例中,如果你输入足够的日志语句,有时它会延长SwiftObj
的最终清理时间,以便你可以看到实际发生的复活。
最小的例子可以在这里下载:
运行,然后点击“开始”按钮,然后点击“停止”按钮。欢迎来到噩梦。
仪器&#39;分配视图也很有趣。