我想從Swift中訪問需要使用C回調的C API。從Swift使用C回調時的對象復活
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
回調函數,然後釋放內存。
在斯威夫特代碼我想使用一個專用的對象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清理其內存。然後,應該調用info
指針上的release
回調,這將導致info
指針的引用計數達到零,並且還將取消分配info
指針。
但是,deinit理論似乎並沒有解決。在SwiftObj
的最終參考被刪除之後,當調用release
回調關閉時再添加一個參考,並在CInfo.release
方法期間重新添加另一個參考(如使用Instrument的Allocations跟蹤器所觀察到的)。行爲還取決於時間 - 例如日誌語句的數量。
對於下面的最小樣本,它會在初始release
回調關閉中立即崩潰,並根據時間順序使用這兩個消息中的任何一個 - 第一個更常見,第二個可以通過快速空間選項卡空間如果你幸運的話。也許還有更多 - 正如我所說的,在我的完整示例中,如果您輸入足夠的日誌語句,有時會延長最終清理的足夠時間,以便您可以看到實際的復活發生。
- EXC_BAD_ACCESS(碼= 1,地址=爲0x0)
- EXC_BAD_ACCESS(代碼= EXC_I386_GPFLT)
最小例如可以在這裏下載:
運行,然後點擊開始按鈕,然後點擊停止按鈕。歡迎來到噩夢。
儀器的分配視圖也很有趣。