Swift中的指针大法

  • 时间:2020-11-08 06:06 作者:01_Jack 来源: 阅读:66
  • 扫一扫,手机访问
摘要:本文概要指针的种类及区别不同指针间的相互转换及常用方法各种类型的指针获取及应用more than that指针简介打开开发文档,可以从Swift Swift Standard Library Manual Memory Management中找到指针类型:指针类型Swift中的指针类型分为两

本文概要

  • 指针的种类及区别
  • 不同指针间的相互转换及常用方法
  • 各种类型的指针获取及应用
  • more than that

指针简介

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

指针类型

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

  • Typed Pointers
    typed pointer为类型指针,用于指向某种特定类型

  • Raw Pointers
    raw pointer为原始指针,未指明指向的类型,相当于C语言中的void *

  • Mutable
    指针中含Mutable的为可变类型指针,表明可对指针指向的内存进行写操作

  • Buffer
    指针中含Buffer的为缓冲类型指针,这类指针遵守了SequenceCollection协议,可以方便的进行少量集合操作,如flitermapreduce

转换

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

  • UnsafeMutableRawPointer
let count = 2let alignment = MemoryLayout<Int>.alignment     // 8let stride = MemoryLayout<Int>.stride       // 8let byteCount = count * stride      // 16//  分配2个Int类型所需字节数给UnsafeMutableRawPointer指针let mRawP = UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment: alignment)//  使用defer确保释放UnsafeMutableRawPointer指针,以防遗漏defer {    mRawP.deallocate()}//  在mRawP中存储Int类型数据,257mRawP.storeBytes(of: 257, as: Int.self)//  在mRawP向后偏移8个字节的位置存储Int类型数据,100mRawP.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))     // 257print(mRawP.load(fromByteOffset: stride, as: Int.self))     // 100print(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内存中的数据加1mP.pointee += 1// 将mP后一个指针中的内存数据加1(mP + 1).pointee += 1print("--------\(type(of: mP))---------")print(mP.pointee)       // 258print(mP.advanced(by: 1).pointee)       // 101print(mP.successor().pointee)       // 101print(mP.advanced(by: 1).predecessor().pointee)     // 258

可变类型指针通过pointee存取数据,advancedsuccessorpredecessor移动指针。
其中,successorpredecessor函数为指定类型指针特有的函数。successor指向下一个元素,predecessor指向上一个元素。

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

  • UnsafeRawPointer与UnsafePointer<Int>
let rawP = UnsafeRawPointer(mRawP)print("--------\(type(of: rawP))---------")print(rawP.load(as: Int.self))      // 258let 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)      // trueprint(mRawPAddress == mPAddress)        // trueprint(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))        // 258print("-------")print(rawBufferP.load(as: Int8.self))       // 2print("-------")print(rawBufferP.load(fromByteOffset: 1, as: Int8.self))        // 1
UnsafeRawBufferPointer.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)}
buffer pointers.png

各种类型的指针获取及应用

  • 可变类型指针的获取
var a = 1withUnsafeMutablePointer(to: &a){$0.pointee += 1}print(a)        // 2var 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 = 0print(rich.money)let isRichP = richP.advanced(by: MemoryLayout<Int>.stride).assumingMemoryBound(to: Bool.self)isRichP.pointee = falseprint(rich.isRich)
Struct实例
  1. 通过withUnsafeBytes获取可变原生缓冲类型指针,可获取到rich中每个字节的值
  2. 通过withUnsafeMutablePointer获取到可变Rich类型指针后,转为可变原生类型指针获取rich地址
  3. 获取moeny地址并赋值
  4. 获取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 = 99999999print(crich.money)let cisRichP = crichP.advanced(by: 16 + MemoryLayout<Int>.stride).assumingMemoryBound(to: Bool.self)cisRichP.pointee = trueprint(crich.isRich)
Class实例
  1. 通过Unmanaged将rich转换为可变原生类型指针
  2. 获取money地址并赋值
  3. 获取isRich地址并赋值

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

  • 值引用与类型引用
    Swift中的Struct属于值引用,而Class属于类型引用。对于值引用,所见即所得,所以上文通过withUnsafeBytes可以获取结构体各字节的值,而类型引用实际是指针指向具体的类型,所以通过withUnsafeBytes获取到的结果不对。

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

print(MemoryLayout<Rich>.alignment)     // 8print(MemoryLayout<Rich>.size)      // 9print(MemoryLayout<Rich>.stride)        // 16print("--------------")print(MemoryLayout<CRich>.alignment)        // 8print(MemoryLayout<CRich>.size)     //8print(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!

  • 全部评论(0)
最新发布的资讯信息
【系统环境|】极客时间-数据分析实战45讲【完结】(2021-09-02 16:26)
【系统环境|windows】字节跳动前台面试题解析:盛最多水的容器(2021-03-20 21:27)
【系统环境|windows】DevOps敏捷60问,肯定有你想理解的问题(2021-03-20 21:27)
【系统环境|windows】字节跳动最爱考的前台面试题:JavaScript 基础(2021-03-20 21:27)
【系统环境|windows】JavaScript 的 switch 条件语句(2021-03-20 21:27)
【系统环境|windows】解决 XML 数据应用实践(2021-03-20 21:26)
【系统环境|windows】20个编写现代CSS代码的建议(2021-03-20 21:26)
【系统环境|windows】《vue 3.0探险记》- 运行报错:Error:To install them, you can run: npm install --save core-js/modules/es.arra...(2021-03-20 21:24)
【系统环境|windows】浅谈前台可视化编辑器的实现(2021-03-20 21:24)
【系统环境|windows】产品经理入门迁移学习指南(2021-03-20 21:23)
血鸟云
手机二维码手机访问领取大礼包
返回顶部