2015-12-04 13 views
0

我想從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)

最小例如可以在這裏下載:

運行,然後點擊開始按鈕,然後點擊停止按鈕。歡迎來到噩夢。

儀器的分配視圖也很有趣。

回答