思维导图代码改变世界iOS - Developer - Swift 进阶大全

Swift中的指针大法

2019-10-29  本文已影响0人  01_Jack

本文概要


指针简介

打开开发文档,可以从Swift-->Swift Standard Library-->Manual Memory Management中找到指针类型:

指针类型

Swift中的指针类型分为两种:Typed PointersRaw Pointers

转换

以下例子用于说明这8种指针间如何相互转换

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移动字节数

// 将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存取数据,advancedsuccessorpredecessor移动指针。
其中,successorpredecessor函数为指定类型指针特有的函数。successor指向下一个元素,predecessor指向上一个元素。

这里可能有点懵,为啥UnsafeMutableRawPointer中的advanced移动的是字节数,而UnsafeMutablePointer<Int>中的advanced移动的是元素数?这是由数据类型决定的,因为原生指针不知道存储元素的类型,所以指针以字节为单位移动。而指定类型指针知道存储的元素类型,所以指针以元素为单位移动。

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

虽然上文中的四种指针类型不相同,但显然他们的地址是相同的,因为都指向同一块内存区域,只是读取数据的方式不同而已。

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
UnsafeRawBufferPointer.png

前边说过,原生指针以字节为单位移动指针,所以map中打印出的为每个字节上的数据。如果直接以Int类型读取rawBufferP中的值,显然为258。但是如果以Int8类型,也就是字节为单位读取rawBufferP中的值,此时为2,而rawBufferP后一个字节值为1。一个字节最大存储数据为255,根据小段模式,此时数据为12(256进制),转换成十进制就是258。

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)}
buffer pointers.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 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)
Struct实例
  1. 通过withUnsafeBytes获取可变原生缓冲类型指针,可获取到rich中每个字节的值
  2. 通过withUnsafeMutablePointer获取到可变Rich类型指针后,转为可变原生类型指针获取rich地址
  3. 获取moeny地址并赋值
  4. 获取isRich地址并赋值

这里或许有疑问,为什么要转成原生指针?因为原生指针以字节为单位进行操作,更方便获取想要的元素指针。

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)
Class实例
  1. 通过Unmanaged将rich转换为可变原生类型指针
  2. 获取money地址并赋值
  3. 获取isRich地址并赋值

这里应该又出现几个疑问:
1.通过withUnsafeBytes获取到的可变原生缓冲类型指针打印出的字节似乎不对?
2.为什么要通过Unmanaged获取指针而不是withUnsafeMutablePointer?
3.为什么获取rich指针后要偏移16个字节才是money的地址?

再从侧面验证一下这个结论:

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。

因此,实际地址从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!

上一篇 下一篇

猜你喜欢

热点阅读