Swift中的指针大法
本文概要
- 指针的种类及区别
- 不同指针间的相互转换及常用方法
- 各种类型的指针获取及应用
- more than that
指针简介
打开开发文档,可以从Swift-->Swift Standard Library-->Manual Memory Management中找到指针类型:
![](https://img.haomeiwen.com/i329694/2d02ab519b127e1a.png)
Swift中的指针类型分为两种:Typed Pointers
和Raw Pointers
。
-
Typed Pointers
typed pointer为类型指针,用于指向某种特定类型 -
Raw Pointers
raw pointer为原始指针,未指明指向的类型,相当于C语言中的void *
-
Mutable
指针中含Mutable
的为可变类型指针,表明可对指针指向的内存进行写操作 -
Buffer
指针中含Buffer
的为缓冲类型指针,这类指针遵守了Sequence
和Collection
协议,可以方便的进行一些集合操作,如fliter
、map
、reduce
转换
以下例子用于说明这8种指针间如何相互转换
- UnsafeMutableRawPointer
let count = 2
let alignment = MemoryLayout<Int>.alignment // 8
let stride = MemoryLayout<Int>.stride // 8
let byteCount = count * stride // 16
// 分配2个Int类型所需字节数给UnsafeMutableRawPointer指针
let mRawP = UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment: alignment)
// 使用defer确保释放UnsafeMutableRawPointer指针,以防遗漏
defer {
mRawP.deallocate()
}
// 在mRawP中存储Int类型数据,257
mRawP.storeBytes(of: 257, as: Int.self)
// 在mRawP向后偏移8个字节的位置存储Int类型数据,100
mRawP.storeBytes(of: 100, toByteOffset: stride, as: Int.self)
// 获取mRawP向后偏移8个字节位置的指针
let anotherMRawP = mRawP.advanced(by: stride)
print("--------\(type(of: mRawP))---------")
print(mRawP.load(as: Int.self)) // 257
print(mRawP.load(fromByteOffset: stride, as: Int.self)) // 100
print(anotherMRawP.load(as: Int.self)) // 100
可变原生指针通过storeBytes
存储数据,load
读取数据,advanced
移动字节数
- UnsafeMutablePointer<Int>
// 将mRawP的内存数据绑定到Int类型上,生成UnsafeMutablePointer<Int>指针
let mP = mRawP.bindMemory(to: Int.self, capacity: byteCount)
// 将mP内存中的数据加1
mP.pointee += 1
// 将mP后一个指针中的内存数据加1
(mP + 1).pointee += 1
print("--------\(type(of: mP))---------")
print(mP.pointee) // 258
print(mP.advanced(by: 1).pointee) // 101
print(mP.successor().pointee) // 101
print(mP.advanced(by: 1).predecessor().pointee) // 258
可变类型指针通过pointee
存取数据,advanced
、successor
、predecessor
移动指针。
其中,successor
与predecessor
函数为指定类型指针特有的函数。successor指向下一个元素,predecessor指向上一个元素。
这里可能有点懵,为啥UnsafeMutableRawPointer中的advanced移动的是字节数,而UnsafeMutablePointer<Int>中的advanced移动的是元素数?这是由数据类型决定的,因为原生指针不知道存储元素的类型,所以指针以字节为单位移动。而指定类型指针知道存储的元素类型,所以指针以元素为单位移动。
- UnsafeRawPointer与UnsafePointer<Int>
let rawP = UnsafeRawPointer(mRawP)
print("--------\(type(of: rawP))---------")
print(rawP.load(as: Int.self)) // 258
let p = rawP.bindMemory(to: Int.self, capacity: stride)
print("--------\(type(of: p))---------")
print(p.pointee) // 258
不可变指针,没什么好说的
- 地址比较
let mRawPAddress = String(Int(bitPattern: mRawP), radix: 16)
let mPAddress = String(Int(bitPattern: mP), radix: 16)
let rawPAddress = String(Int(bitPattern: rawP), radix: 16)
let pAddress = String(Int(bitPattern: p), radix: 16)
print("--------address---------")
print(mRawPAddress == rawPAddress) // true
print(mRawPAddress == mPAddress) // true
print(mRawPAddress == pAddress) // true
虽然上文中的四种指针类型不相同,但显然他们的地址是相同的,因为都指向同一块内存区域,只是读取数据的方式不同而已。
- UnsafeRawBufferPointer
let rawBufferP = UnsafeRawBufferPointer(start: rawP, count: byteCount)
print("-------\(type(of: rawBufferP))--------")
let _ = rawBufferP.map{print($0)}
print("-------")
print(rawBufferP.load(as: Int.self)) // 258
print("-------")
print(rawBufferP.load(as: Int8.self)) // 2
print("-------")
print(rawBufferP.load(fromByteOffset: 1, as: Int8.self)) // 1
![](https://img.haomeiwen.com/i329694/17e314cb7a4fdae1.png)
前边说过,原生指针以字节为单位移动指针,所以map中打印出的为每个字节上的数据。如果直接以Int类型读取rawBufferP中的值,显然为258。但是如果以Int8类型,也就是字节为单位读取rawBufferP中的值,此时为2,而rawBufferP后一个字节值为1。一个字节最大存储数据为255,根据小段模式,此时数据为12(256进制),转换成十进制就是258。
- UnsafeBufferPointer<Int>、UnsafeMutableRawBufferPointer、UnsafeMutableBufferPointer<Int>
let bufferP = rawBufferP.bindMemory(to: Int.self)
print("-------\(type(of: bufferP))--------")
let _ = bufferP.map{print($0)}
let mRawBufferP = UnsafeMutableRawBufferPointer(mutating: rawBufferP)
print("-------\(type(of: mRawBufferP))--------")
let _ = mRawBufferP.map{print($0)}
let mBufferP = UnsafeMutableBufferPointer(mutating: bufferP)
print("-------\(type(of: mBufferP))--------")
let _ = mBufferP.map{print($0)}
![](https://img.haomeiwen.com/i329694/c7940277967b6b22.png)
各种类型的指针获取及应用
- 可变类型指针的获取
var a = 1
withUnsafeMutablePointer(to: &a){$0.pointee += 1}
print(a) // 2
var arr = [1, 2, 3]
withUnsafeMutablePointer(to: &arr){$0.pointee[0] += 1}
print(arr) // [2, 2, 3]
arr.withUnsafeMutableBufferPointer{$0[0] += 1}
print(arr) // [3, 2, 3]
通过withUnsafeMutablePointer获取可变类指针,通过withUnsafeMutableBufferPointer获取可变缓冲类型指针
- Struct实例指针的获取
struct Rich {
var money: Int
var isRich: Bool
}
var rich = Rich(money: 99999999, isRich: true)
withUnsafeBytes(of: &rich) { bytes in
for byte in bytes {
print(byte)
}
}
print("---------------")
let richP = withUnsafeMutablePointer(to: &rich) { UnsafeMutableRawPointer($0) }
let moneyP = richP.assumingMemoryBound(to: Int.self)
moneyP.pointee = 0
print(rich.money)
let isRichP = richP.advanced(by: MemoryLayout<Int>.stride).assumingMemoryBound(to: Bool.self)
isRichP.pointee = false
print(rich.isRich)
![](https://img.haomeiwen.com/i329694/b0b2fe79f5ac5096.png)
- 通过withUnsafeBytes获取可变原生缓冲类型指针,可获取到rich中每个字节的值
- 通过withUnsafeMutablePointer获取到可变Rich类型指针后,转为可变原生类型指针获取rich地址
- 获取moeny地址并赋值
- 获取isRich地址并赋值
这里或许有疑问,为什么要转成原生指针?因为原生指针以字节为单位进行操作,更方便获取想要的元素指针。
- Class实例指针的获取
class CRich {
var money = 0
var isRich = false
}
var crich = CRich()
withUnsafeBytes(of: &crich) { bytes in
for byte in bytes {
print(byte)
}
}
print("---------------")
let crichP = Unmanaged.passUnretained(crich as AnyObject).toOpaque()
let cmoneyP = crichP.advanced(by: 16).assumingMemoryBound(to: Int.self)
cmoneyP.pointee = 99999999
print(crich.money)
let cisRichP = crichP.advanced(by: 16 + MemoryLayout<Int>.stride).assumingMemoryBound(to: Bool.self)
cisRichP.pointee = true
print(crich.isRich)
![](https://img.haomeiwen.com/i329694/da11680301fb323e.png)
- 通过Unmanaged将rich转换为可变原生类型指针
- 获取money地址并赋值
- 获取isRich地址并赋值
这里应该又出现几个疑问:
1.通过withUnsafeBytes获取到的可变原生缓冲类型指针打印出的字节似乎不对?
2.为什么要通过Unmanaged获取指针而不是withUnsafeMutablePointer?
3.为什么获取rich指针后要偏移16个字节才是money的地址?
- 值引用与类型引用
Swift中的Struct属于值引用,而Class属于类型引用。对于值引用,所见即所得,所以上文通过withUnsafeBytes可以获取结构体各字节的值,而类型引用实际是指针指向具体的类型,所以通过withUnsafeBytes获取到的结果不对。
再从侧面验证一下这个结论:
print(MemoryLayout<Rich>.alignment) // 8
print(MemoryLayout<Rich>.size) // 9
print(MemoryLayout<Rich>.stride) // 16
print("--------------")
print(MemoryLayout<CRich>.alignment) // 8
print(MemoryLayout<CRich>.size) //8
print(MemoryLayout<CRich>.stride) //8
由于Rich是值引用,所以size为8 + 1
等于9,由于内存对齐,所以stride等于16。而CRich是类型引用,实际是个指针,因此size与stride都是8。
- Class的前16字节
1.在32-bit设备上,前4byte存储类型信息,后8byte存储引用计数器,共12byte
2.在64-bit设备上,前8byte存储类型信息,后8byte存储引用计数器,共16byte
因此,实际地址从16byte开始(以64-bit设备为例)
Class trick
既然说到这里,是否可以拿Class前8byte中存储的类型信息玩些小把戏?
class Dog {
func description() {
print("This is a dog.")
}
}
class Cat {
func description() {
print("This is a cat.")
}
}
var dog = Dog()
var cat = Cat()
let dogP = Unmanaged.passUnretained(dog as AnyObject).toOpaque()
let catP = Unmanaged.passUnretained(cat as AnyObject).toOpaque()
catP.bindMemory(to: Dog.self, capacity: 8).pointee = dogP.load(as: Dog.self)
cat.description() // This is a dog.
由于Dog与Cat内存分布相同,强制将cat前8byte中的类型信息替换成dog的类型信息,此时通过cat调用description函数实际是调用到dog中去。
其实Objective-C中通过object_setClass
函数也可以玩这种把戏:
@interface Dog : NSObject
@end
@implementation Dog
- (NSString *)description {
return @"This is dog.";
}
@end
@interface Cat : NSObject
@end
@implementation Cat
- (NSString *)description {
return @"This is cat";
}
@end
Dog *dog = [Dog new];
Cat *cat = [Cat new];
object_setClass(cat, dog.class);
NSLog(@"%@", cat.description); // This is dog.
Have fun!