从Swift

时间:2015-12-04 23:45:53

标签: swift memory callback reference-counting

我试图从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的最终清理时间,以便你可以看到实际发生的复活。

  • EXC_BAD_ACCESS(code = 1,address = 0x0)
  • EXC_BAD_ACCESS(code = EXC_I386_GPFLT)

最小的例子可以在这里下载:

运行,然后点击“开始”按钮,然后点击“停止”按钮。欢迎来到噩梦。

仪器&#39;分配视图也很有趣。

1 个答案:

答案 0 :(得分:0)

似乎是编译器错误。解决方法是删除info实例变量的延迟属性。

https://bugs.swift.org/browse/SR-74