TOLL-FREE BRIDGING 和 UNMANAGED

TOLL-FREE BRIDGING 和 UNMANAGED


有經驗的讀者看到這章的標題就能知道我們要談論的是 Core Foundation。在 Swift 中對於 Core Foundation (以及其他一系列 Core 開頭的框架) 在內存管理進行了一系列簡化,大大降低了與這些 Core Foundation (以下簡稱 CF ) API 打交道的複雜程度。

首先值得一提的是對於 Cocoa 中 Toll-Free Bridging 的處理。Cocoa 框架中的大部分 NS 開頭的類其實在 CF 中都有對應的類型存在,可以說 NS 只是對 CF 在更高層面的一個封裝。比如 NSURL 和它在 CF 中的 CFURLRef 內存結構其實是同樣的,而 NSString 則對應着 CFStringRef。

因爲在 Objective-C 中 ARC 負責的只是 NSObject 的自動引用計數,因此對於 CF 對象無法進行內存管理。我們在把對象在 NS 和 CF 之間進行轉換時,需要向編譯器說明是否需要轉移內存的管理權。對於不涉及到內存管理轉換的情況,在 Objective-C 中我們就直接在轉換的時候加上 __bridge 來進行說明,表示內存管理權不變。例如有一個 API 需要 CFURLRef,而我們有一個 ARC 管理的 NSURL 對象的話,這樣來完成類型轉換:

NSURL *fileURL = [NSURL URLWithString:@"SomeURL"];
SystemSoundID theSoundID;
//OSStatus AudioServicesCreateSystemSoundID(CFURLRef inFileURL,
//                             SystemSoundID *outSystemSoundID);
OSStatus error = AudioServicesCreateSystemSoundID(
        (__bridge CFURLRef)fileURL,
        &theSoundID);

而在 Swift 中,這樣的轉換可以直接省掉了,上面的代碼可以寫爲下面的形式,簡單了許多:

import AudioToolbox

let fileURL = NSURL(string: "SomeURL")
var theSoundID: SystemSoundID = 0

//AudioServicesCreateSystemSoundID(inFileURL: CFURL,
//        _ outSystemSoundID: UnsafeMutablePointer<SystemSoundID>) -> OSStatus
AudioServicesCreateSystemSoundID(fileURL!, &theSoundID)

細心的讀者可能會發現在 Objective-C 中類型的名字是 CFURLRef,而到了 Swift 裏成了 CFURL。CFURLRef 在 Swift 中是被 typealias 到 CFURL 上的,其實不僅是 URL,其他的各類 CF 類型都進行了類似的處理。這主要是爲了減少 API 的迷惑:現在這些 CF 類型的行爲更接近於 ARC 管理下的對象,因此去掉 Ref 更能表現出這一特性。

另外在 Objective-C 時代 ARC 不能處理的一個問題是 CF 類型的創建和釋放。雖然不能自動化,但是遵循命名規則來處理的話還是比較簡單的:對於 CF 系的 API,如果 API 的名字中含有 Create,Copy 或者 Retain 的話,在使用完成後,我們需要調用 CFRelease 來進行釋放。

不過 Swift 中這條規則已成明日黃花。既然我們有了明確的規則,那爲什麼還要一次一次不厭其煩地手動去寫 Release 呢?基於這種想法,Swift 中我們不再需要顯式地去釋放帶有這些關鍵字的內容了 (事實上,含有 CFRelease 的代碼甚至無法通過編譯)。也就是說,CF 現在也在 ARC 的管轄範圍之內了。其實背後的機理一點都不復雜,只不過在合適的地方加上了像 CF_RETURNS_RETAINED 和 CF_RETURNS_NOT_RETAINED 這樣的標註。

但是有一點例外,那就是對於非系統的 CF API (比如你自己寫的或者是第三方的),因爲並沒有強制機制要求它們一定遵照 Cocoa 的命名規範,所以貿然進行自動內存管理是不可行的。如果你沒有明確地使用上面的標註來指明內存管理的方式的話,將這些返回 CF 對象的 API 導入 Swift 時,它們的類型會被對對應爲 Unmanaged。

這意味着在使用時我們需要手動進行內存管理,一般來說會使用得到的 Unmanaged 對象的 takeUnretainedValue 或者 takeRetainedValue 從中取出需要的 CF 對象,並同時處理引用計數。takeUnretainedValue 將保持原來的引用計數不變,在你明白你沒有義務去釋放原來的內存時,應該使用這個方法。而如果你需要釋放得到的 CF 的對象的內存時,應該使用 takeRetainedValue 來讓引用計數加一,然後在使用完後對原來的 Unmanaged 進行手動釋放。爲了能手動操作 Unmanaged 的引用計數,Unmanaged 中還提供了 retain,release 和 autorelease 這樣的 “老朋友” 供我們使用。一般來說使用起來是這樣的 (當然這些 API 都是我虛構的):

// CFGetSomething() -> Unmanaged<Something>
// CFCreateSomething() -> Unmanaged<Something>
// 兩者都沒有進行標註,Create 中進行了創建

let unmanaged = CFGetSomething()
let something = unmanaged.takeUnretainedValue()
// something 的類型是 Something,直接使用就可以了

let unmanaged = CFCreateSomething()
let something = unmanaged.takeRetainedValue()

// 使用 something

//  因爲在取值時 retain 了,使用完成後進行 release
unmanaged.release()

切記,這些只有在沒有標註的極少數情況下才會用到,如果你只是調用系統的 CF API,而不會去寫自己的 CF API 的話,是沒有必要關心這些的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章