下面這個 Objective-C 方法會返回一個 int
指針,或者說 C 術語裡面的 (int *)
:
@interface PointerBridge : NSObject {
int count;
}
- (int *) getCountPtr;
@end
@implementation PointerBridge
- (instancetype) init {
self = [super init];
if(self) {
count = 23;
}
return self;
}
- (int *) getCountPtr {
return &count;
}
@end
上面的代碼定義了一個 PointerBridge
類,它包含 getCountPtr
方法,這個方法返回一個值為 23 的 int
型內存地址。 這個 Int
其實是 count
的實例,它在構造方法 init
中被賦值為 23 。
我把這段代碼放在一個 Objective-C 的頭文件中,然後把這個頭文件 import 到我的橋接頭文件(XXX-bridging-header.h)中,這樣就可以在 Swift 中使用。然後我在 Swift 中創建一個名為 bridge
的 PointerBridge
實例,然後獲得 getCountPtr()
方法的返回值…
let bridge = PointerBridge()
let theInt = bridge.getCountPtr()
print(theInt)
print(theInt.memory)
在 Xcode 中按住 Option 鍵點擊 theInt
檢查它的類型,你會發現他的 Swift 類型是 UnsafeMutablePointer
。這是指向 Int
型的指針,和 Int
型不一樣,它僅僅是指向它的指針。
如果運行這個程序然後執行這段 Swift 代碼,我們會發現 theInt
在命令行中輸出類似 0x00007f8bdb508ef8 這樣的內存地址,然後,然後我們會看到 memory
成員變量輸出的值 23 。訪問指針指向的內存通常返回其底層指向的對象,在這個例子中就是原來的 32 位 int
(在 Swift 中就是 Int32
)
現在讓 Objective-C 類支持設置 count
的值。
@interface PointerBridge : NSObject {
int count;
}
- (int *) getCountPtr;
- (void) setCount:(int)newCount;
@end
@implementation PointerBridge
- (instancetype) init {
self = [super init];
if(self) {
count = 23;
}
return self;
}
- (int *) getCountPtr {
return &count;
}
- (void) setCount:(int)newCount {
count = newCount;
}
@end
我們可以調用 setCount()
方法來修改 count
的值。因為 theInt
是一個指針,所以通過 setCount
修改 count
也會更新 theInt.memory
。別忘了內存地址是不會變的,變的是值。
也就是說,下面的代碼會在命令行中打印數字 23, 然後打印數字 1000。
let bridge = PointerBridge()
let theInt = bridge.getCountPtr()
print(theInt.memory) // 23
bridge.setCount(1000)
print(theInt.memory) // 1000
如果想避免每次都寫 .memory
,有一條捷徑就是把.memory
賦值給一個變量:
let bridge = PointerBridge()
let theInt = bridge.getCountPtr()
let countVal = theInt.memory
print(countVal) // 23
就像之前一樣, 命令行會輸出 23。然而,如果我們像之前那樣調用 setCount()
方法修改 count
的值,問題出現了:
let bridge = PointerBridge()
let theInt = bridge.getCountPtr()
let countVal = theInt.memory
print(countVal) // 23
bridge.setCount(1000)
print(countVal) // 23
出現問題的原因是 countVal
是通過值(value)來賦值的。賦值的時候值(value)就是23,所以 countVal
有它自己的內存地址,這個地址永久地保存了 23 這個值,所以已經失去了指針的特性。 countVal
現在只是一個普通的 Int32
型。
如果我們想要做和上面相反的事情呢?不是用 Int
型來給 count
賦值,而是傳入一個指針呢?
我們假設在 Objective-C 的代碼中有如下的一個方法:
- (void) setCountPtr:(int *)newCountPtr {
count = *newCountPtr;
}
這個方法很作,其實就是把 newCountPtr
重新賦值給 count,但在 Swift 開發中你確實會碰到這樣一些需要傳入指針的場景。用這麼作的方式只是為了向你展示如何在 Swift 中創建指針類型,然後傳入到 Objective-C 的方法中。
你可能會簡單的認為只要使用一個類似 & 的引用操作符就可以傳入 Int
值,就像你在 C 中所做的那樣。在 Objective-C 中你可以這樣寫:
int mcount = 500;
[self setCountPtr:&mcount];
這段代碼可以成功地把 count
的值更新為 500。然而在 Swift 中,通過自動補全你會發現它更復雜(而且更冗長)。它需要傳入一個UnsafeMutablePointer
類型的 newCountPtr
變量。
我知道這個類型很惡心,而且它看起來確實很復雜。但是,事實上它相當簡單,特別是在你了解 Obj-C 中的指針的情況下。如果要創建一個UnsafeMutablePointer
類型的對象,我們只需要調用構造方法,這個構造方法唯一需要傳入的參數就是指針的大小(你應該知道 C 的指針是不存儲類型的,所以它也不會存儲大小的信息)
let bridge = PointerBridge()
let theInt = bridge.getCountPtr()
print(theInt.memory) // 23
let newIntPtr = UnsafeMutablePointer.alloc(1)
newIntPtr.memory = 100
bridge.setCountPtr(newIntPtr)
print(theInt.memory) // 100
唯一需要給 UnsafeMutablePointer
構造方法傳入的參數就是需要分配空間的對象的個數,所以我們傳入 1 即可,因為我們只需要一個Int32
對象 。然後,只需要把我們之前對 memory
所做的事情反過來,我們就可以為我們新建的指針賦值。最終,我們只需要簡單滴把 newIntPtr
傳入到 setCountrPtr
方法中,再把之前 theInt
指針的值打印出來,我們就會發現它的值已經被更新為 100。
UnsafeMutablePointer
類型的兄弟類型 UnsafePointer
從根本上說只是 C 指針的一個抽象。你可以把它們看作 Swift 的可選類型,這樣更容易理解。它們不是直接等於一個確切的值,而是在一個確切的值上面做了一層抽象。它們的類型是泛型,這樣就可以允許其使用其他的值,而不單單是 Int32
。比如你需要傳入一個 Float
對象那麼你可能需要 UnsafeMutablePointer
。
重點是:你不是把一個 Int
強轉 為 UnsafeMutablePointer
,因為指針不是簡單地一個 Int
值。所以,如果需要創建一個新的對象,你需要調用構造方法 UnsafeMutablePointer
。
在本文之後我們會繼續深入研究函數指針的一些細節,然後學習如何利用這些特性的優勢去更好地與 C 和 Objective-C 的 API 進行交互。一定要注冊我們的 Newsletter ,這樣你才不會錯過這些精彩的內容!