swift的指针介绍,指针的常用函数和使用

OC指针

在OC中的对象Object我们都是用的指针,像下面这些:

NSString *str = ...;
NSObject *obj = ...;
NSArray *array = @[];

很显然在OC中我们使用*来表示对象,其实是声明指针,而且使用&符号来取地址,比如我们在使用C的数组时,可以直接使用指针的+、- 来获得当前元素的下一个或上一个元素(这里是指针的加减而不是地址的加减,指针+1可能地址跳了一位或者一个字节甚至更多):(代码a0)

int nums[2] =  {1,3,5};
printf("%d",*nums);  //1
printf("%d",*(nums+1));  //3

//32513 = 0111 1111 0000 0001
int32_t num = 32513;
int8_t *num8s;
//取 num 地址赋给指针 num8s
num8s = #
//0000 0001 =  1
int num_1 = *num8s;  //1
//0111 1111 =  127
int num_2 = *(num8s+1);  //127

看上面代码,我们给num赋值32513,然后取num地址赋给了指针num8sint8_t这个类型是8位整形,这时候我们就相当于把num这个数字拆分为每8位为一个元素的数组(这里的int32_t是4字节,32位),数组容量为4,我们用二进制表示的话就是:
0...0, 0...0, 01111111, 00000001
因为iphone是小端序,低地址存储数值低位,所以拆成数组就是:
int8_t num8s[4] = {1,127,0,0};
我们获取*num8s相当于num8s[0]等于1,获取*(num8s+1)相当于num8s[1]=127。


对于swift语言来说,他也有指针,不过没有OC指针那么方便的使用,接下来我们讲讲swift里的指针使用,以及用swift指针怎么实现上面那段代码里的拆分内存并读取。

swift指针

首先先来了解下swift有哪几种指针的类:

UnsafePointer
UnsafeRawPointer
UnsafeBufferPointer
UnsafeRawBufferPointer
//还有他们对应的Mutable形式
UnsafeMutablePointer
...

mutable形式的指针有更多的可操作性,是以下讲的重点。

  • UnsafeMutablePointer:常用指针类,要求绑定存储的类型,通常的类对象、实例对象、基本类型都可以用这种指针(类似NSString *, UILabel *,NSObject *)。
  • UnsafeMutableRawPointer:原始指针类,不需要绑定存储类型(类似void *)。
  • UnsafeMutableBufferPointer:集合类对象指针,swift对集合类指针有单独的函数和属性(类似NSArray<Pointee> *,NSSet<Pointee> *,NSDictionary<keyPointee, valuePointee> *),有count,startIndex,endIndex等属性。
  • UnsafeMutableRawBufferPointer:原始buffer指针,可以理解为BufferPointer的首地址。
指针的获取

在OC中我们用&获取指针,用(void *)number强制类型转换把整数转为指针,这就是我们在OC中指针的获取和创建了,
那么我们在swift中如何获取指针呢

首先swift中变量用var声明,常量用let来声明:

  • let声明的常量都存储在数据区,且不允许再改变值
  • var声明的变量,要看是什么类型,object类存在堆区,独立的基本类型(附加在类中的属性不算在此)存在栈中
    举例说明,如下:代码(b0)
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //32513 = 0111 1111 0000 0001
        // let number:Int16 = 32513
        var number: Int16 = 32513
        var label = UILabel()
        label.text = "aHaha"
        label.font = UIFont.systemFont(ofSize: 17)
  }
}
  • 当number声明为let时,他的地址是0x117e295b0的数据区地址;

  • 当number声明为var时,他的地址就是0x7ffee69288a8的栈区地址,
    此时number是直接寻址,即栈地址对应的内存中直接存储了数据:

    number的直接寻址图.png

  • var声明的label是间接寻址,局部变量指针是0x7ffee69288a0的栈地址,栈地址内存储的是堆地址0x7fdc0cd088b0,堆地址中存储了label的实际内容:

    label的间接寻址图.png



    上面我们知道了对象变量大都存储在堆中,独立基本类型大都存储在栈中,下面我们再看指针的获取方法。
    1.基本类型的指针
    使用swift.Misc库中withUnsafe...方法临时获取指针,前提必须是可变变量var:

let raw_number = withUnsafePointer(to: &number, {  bb -> Int in
     return Int(bitPattern: bb)
})

如果是声明为let的常量那么编译会报错,因为let常量存储在数据区,数据区除了常量还有APP代码等,为了安全性考虑不允许修改内容(在debug时,let常量可以在控制台输出po withUnsafePointer(to: &number, {bb->Int in return Int(bitPattern: bb)})):

withUnsafePointer函数参数为常量的报错.png

需要注意的是我们这里return出来的是 Int 值,原因是withUnsafePointer这类函数的闭包中获取的指针都是临时指针,不能在闭包外使用,这Int值我们可以继续处理成指针:

let number_pointer = UnsafePointer<Int16>.init(bitPattern: raw_number)
number_pointer?.pointee == number //true



2.object对象的指针
我们拿 代码(b0) 中的label举例,label是一个对象,那么获取指针的方法就很多了:

        let label_UnsafeMutableRawPointer = Unmanaged.passUnretained(label).toOpaque()
        
        let label_unsafeBitCast_Int = unsafeBitCast(label, to: Int.self)
        let with_raw_label = withUnsafeMutablePointer(to: &label) { (wp) -> Int in
            return Int(bitPattern: wp)
        }

//对于直接寻址的number来说,使用unsafeBitCast方法获取到的就是number局部变量对应指针的内存中的值,
//因为是直接寻址,所以就是数值 32513
        let number_bitCast = unsafeBitCast(number, to: Int16.self)
  • 对于实例对象label来说,可以使用非托管类的passUnretained方法来创建一个非托管对象,然后获取其原始指针地址。
  • 使用swift.C库函数unsafeBitCast,这个方法就是获取label局部变量指针对应内存中的值,因为是间接寻址,实际获得的是label对应在堆内存中的地址(这里返回的是Int值)140583084525744,转为16进制就是7fdc0cd088b0(在上文的 label间接寻址图 有)。
  • 使用swift.Misc库的withUnsafeMutablePointer函数,获取临时指针,这里获取到的是label局部变量指针即栈指针140732766783648,转16进制就是7ffee69288a0(在 label的间接寻址图 有),再通过Int(bitPattern:)函数获取到指针地址的Int值。

对于上面获取的label_UnsafeMutableRawPointer,我们可以转为常用的指针类型UnsafeMutablePointer:(代码b1)

let heap_labelPointer = label_UnsafeMutableRawPointer.bindMemory(to: UILabel.self, capacity: 1)

对于我们获取到的label的两个指针地址Int值:栈指针0x7ffee69288a0和堆指针0x7fdc0cd088b0(指针地址的Int值,不是指针),到底怎么使用,使用哪个呢?看:(代码b2)

let stack_labelPointer = UnsafeMutablePointer.init(bitPattern: with_raw_label)
let heap_labelPointer = UnsafeMutablePointer.init(bitPattern: label_unsafeBitCast_Int)

stack_labelPointer.pointee == label //true
heap_labelPointer.pointee == label //false

对于指针UnsafeMutablePointer来说,指针本身作为一个结构体有一个内存,结构体中的pointee用来存储指针所指向的内容。
上面代码中stack_labelPointer是用label的临时指针地址初始化的,stack_labelPointer.pointee就是label;
heap_labelPointer.pointee却不是label,要知道label_unsafeBitCast_Int是label在堆中的存储地址,所以我们用label_unsafeBitCast_Int来初始化指针导致这个结构体本身就是label(因为结构体和类的一些相似性,不会报错),pointee就不是指向label了。
那么heap_labelPointer.pointee不是label是什么呢:(代码b3)

        let what = heap_labelPointer!.pointee
        //这里正常输出,label不是一个类对象,而是一个实例对象
        let label_notClassObject = label as? AnyClass == .none

      //下面两句都是true,第一句证明what是UILabel的类对象,
      //第二句证明what可以调用UILabel的类方法areAnimationsEnabled,
      //综上what确实是UILabel类对象
        let what_isUILabelClass = what as? AnyClass == Optional.some(UILabel.self)
        let what_hadUILabelClassMethod:String = what.responds(to: #selector(getter: UILabel.areAnimationsEnabled)) ? "true":"false"

通过上面的代码我们发现heap_labelPointer.pointee就相当于在OC中的'isa',OC中label对象的isa是UILabel,这里 pointee 也是指向UILabel类对象,并且可以调用UILabel的类方法。
这里仅当是OC中存在的NSObject时,堆指针地址的.pointee才是指向类对象,如果是其他对象,那么在强制转为指针的过程中,其 ‘pointee’ 在指针结构体中的偏移地址(为0) 对应在内存中地址就是.pointee(作为NSObject时,pointee偏移地址为0,刚好对应isa)。

3.数组的指针

var numbers:[Int] = [7,3,2,4,0]

对于数组numbers来说,他的存储有两部分,一部分是数组的标识、属性等,一部分是数组元素,一般数组元素就存储在数组标识、属性等的相邻内存(邻接数组ContiguousArray就是这种存储方式)。看:(代码c0)

        //获取局部变量numbers的指针(&numbers),栈指针
        let stack_raw_numbers = withUnsafePointer(to: &numbers, { (bb) -> Int in
            return Int(bitPattern: bb)
        })
        //numbers 存储在堆中地址,该存储地址不是数组元素首地址,而是以数组标识属性等开始的数组内存
        let heap_raw_numbers = unsafeBitCast(numbers, to: Int.self)
        //numbers 可以直接作为 UnsafeRawPointer 使用, 获取 数组元素存储的首地址
        let raw_numbers = Int(bitPattern: numbers)

对于上面的三个指针地址,我们实际用到的一般只有两个:stack_raw_numbers 和 raw_numbers ,这两个就是获取数组元素的两种方法,使用如下:(代码c1)

        //1.取局部变量栈指针,通过该指针的pointee来指向数组
        var numbersPointer_test = UnsafeMutablePointer<[Int]>.init(bitPattern: stack_raw_numbers)
        let get_numbers = numbersPointer_test!.pointee

        //2.取数组元素存储区的首地址,即数组第一个元素的地址
        let numbersPointer_0 = UnsafeMutablePointer<Int>.init(bitPattern: raw_numbers)
        //之后的元素可以根据元素地址,向后推移
        let numbersPointer_1 = numbersPointer![1]
        let numbersPointer_2 = numbersPointer![2]
         //也可以直接使用元素首地址初始化bufferPointer
        var numbers_bufferPointer = UnsafeMutableBufferPointer.init(start: numbersPointer, count: 5)
  • 使用stack_raw_numbers来初始化指针可以直接用pointee;
  • 使用数组元素首地址初始化指针,该指针指向第一个元素,第二个元素可以用numbersPointer![1]来表示,以此类推;
    还可以根据首地址初始化numbers_bufferPointer,numbers_bufferPointer[0] == 7,numbers_bufferPointer[1] == 3 ...

对于heap_raw_numbers来说,他是数组的首地址(直接指向数组标识、属性等),但是数组内元素实际上是存储在旁边内存里(邻接数组的元素存储在相邻内存,一般会相差4个字节):

        //这里的指针本身就是指向数组了,这导致调用pointee反而出错,pointee并不指向数组元素
        var what_numbers_pointer = UnsafeMutablePointer<[Int]>.init(bitPattern: heap_raw_numbers)
//跟 代码b3 类似,不过这里不是OC对象NSArray,而是swift对象Array
        let some_what_numbersPointer = what_numbers_pointer!.pointee


指针的创建

上面我们讲了根据现有对象初始化指针,这里我们讲指针的创建,这两者最大的区别就是指针创建需要申请内存,这个指针如果使用label来初始化,那他也不是原来的label了,他是一个新的 存储在指针 .pointee 内的对象。

指针一般用allocte来创建,需要注意的是capacity参数是容量的意思,非集合指针一般设为1,

struct SYPerson {
    var name: String
    var age: Int
}

    let systructPointer = UnsafeMutablePointer<SYPerson>.allocate(capacity: 1)

initialize来初始化,如果类型是基本类型,那么可以直接assign,因为内存中保存的必定是0或1,相当于已经初始化了。
initialize(repeating: obj, count: 1) 可以用 initialize(to: obj)代替。

//这里是自定义结构体SYPerson,直接写assign会崩溃,因为pointee还没有申请内存,无法拷贝内容进去
//systructPointer.assign(repeating: systruct, count: 1)    //wrong
    systructPointer.initialize(repeating: systruct, count: 1)
    systructPointer.initialize(to: systruct)

然后用assign来分配内存,如果指针容量大于1,assign就会将repeating拷贝进内存并循环拷贝count次,count就是repeating的重复次数

    systructPointer.assign(repeating: systruct_other, count: 1)
//等同于
    systructPointer[0] = systruct
//等同于
    systructPointer.pointee = systruct

最后在不用的时候deallocate释放内存。

    systructPointer.deallocate()

实际使用过程中,所谓allocte就是申请一个指针内存,但是这个指针没有为.pointee准备好内存,而initialize有为pointee申请内存并初始化内容;assign则是将新的内容拷贝到pointee的内存中,
assign是不能用在未初始化的指针上的initialize方法介绍上说应该使用在未初始化的指针上,但是实际上可以作用在已初始化的指针上。

指针的使用

1.使用下标来直接取值:

let systruct = SYPerson(name: "sd", age: 12)
let systructPointer = UnsafeMutablePointer<SYPerson>.allocate(capacity: 1)
systructPointer.initialize(to: systruct)

systructPointer[0].age == 12  //true

对于上面的代码,我们可以直接用下标取pointee值:systructPointer[0]
原变量systruct是let修饰的,但是systructPointer.pointee是新的变量,是可以修改的:systructPointer[0].name = "changed"

var numbers:[Int] = [7,3,2,4,0]
//numbers 可以直接作为 UnsafeRawPointer 使用, 获取 数组元素存储的首地址
        let raw_numbers = Int(bitPattern: numbers)
let numbersPointer = UnsafeMutablePointer<Int>.init(bitPattern: raw_numbers)
numbersPointer![0] == 7  //true
numbersPointer![1] == 3  //true

上面这段代码是作用在原数组numbers上的指针,如果指针改变值,原数组也会改变。
创建一个新的指针可以这样:(代码d0)

let numberPointer_other = UnsafeMutablePointer<Int>.allocate(capacity: 5)
numberPointer_other.initialize(from: &numbers, count: 5)
//等同于
numberPointer_other.initialize(from: numbers, count: 5)

numberPointer_other[0] == 7  //true
numberPointer_other[1] == 3  //true

上面这段代码根据已有数组来创建容量为5的指针,这里使用两种方式:

  • 使用&numbers表示数组的局部变量,栈指针,初始化的时候initialize方法会自动把数组元素拷贝过去。
  • 直接使用numbersnumbers可以作为UnsafeRawPointer使用(数组元素的首地址),相当于以数组元素的首地址来初始化新的指针。

数组也可以用bufferPointer,这在 代码c0和c1 中已经展示了,不过方便点的方法还是获取临时指针:

numbers.withUnsafeBufferPointer { (bb) in
      bb.first! == 7  //true
      bb[0] == 7  //true
}

2.简单的说一下指针的一些方法
其实我是故意把这些常用方法放到最后来讲。

  • UnsafeMutablePointer:
    .withMemoryRebound(to: Int8.self, capacity: 1) { (bb) -> UnsafeMutablePointer<Int8> in ...}:临时绑定内存,这里绑定了Int8类型,闭包返回Int8类型指针。
    .deinitialize(count: 1):反初始化,获得原始指针rawPointer。
    .advanced(by: n):返回向后移位n位的指针,这里一位就表示一个指针类型内存,如果指针是SYPerson类型,那么就是向后移动n个SYPerson,即MemoryLayout<SYPerson>.stride * n字节数。

  • UnsafeMutableRawPointer:
    .bindMemory(to: Int8.self, capacity: 1):返回 将原始指针绑定到类型Int8 的指针。
    .load(as: Int8.self):将原始指针内存加载位Int8类型并返回值。

  • UnsafeMutableBufferPointer:
    .last .first:内存缓冲区的第一和最后一个元素。
    index(...):获取相应位置的元素。
    .baseAddress:返回内存缓冲区的首地址。
    基本可以直接当做数组来操作。

  • UnsafeMutableRawBufferPointer:
    .last .first:基本单位是一字节的Int8(格外注意),如果存储的是别的类型,需要重新绑定内存。
    .bindMemory(to: Int.self):返回重新绑定为Int类型的BufferPointer指针。
    .baseAddress:返回内存缓冲区的首地址的原始地址。

3.使用swift指针模仿OC 代码a0 操作
根据上面的指针方法,我们直接取得number指针,绑定Int8类型,然后用advanced方法向后移一字节,就得到了127:代码d0

//32513 = 0111 1111 0000 0001
var number: Int16 = 32513
let raw_number = withUnsafePointer(to: &number, {  bb -> Int in
        return Int(bitPattern: bb)
})
let number_pointer = UnsafeMutablePointer<Int8>.init(bitPattern: raw_number)
//number_pointer![1] == 127  //true
let number_8_pointer = number_pointer?.advanced(by: 1)
number_8_pointer[0] == 127  //true

//重新创建指针
let unsafeMutablePointer = UnsafeMutablePointer<Int16>.allocate(capacity: 1)
unsafeMutablePointer.initialize(repeating: number, count: 1)
unsafeMutablePointer.withMemoryRebound(to: Int8.self, capacity: 2) { bb in
            bb[1] == 127  //true
}

如果是OC中UIKit类对象,如UILabel,UIView等,我们可以直接用
view.hash
获取原始直接的Int值,如果这些类没有重写Hashable协议,我们还可以用.hashValue

var label = UILabel()
let label_unsafeBitCast_Int = unsafeBitCast(label, to: Int.self)
let raw_value = label.hash
label_unsafeBitCast_Int == raw_value   //true



swift指针相当于扩展了OC指针,由原本的指向内存的地址,变为了以该地址为起始地址的结构体;由原本的数值型地址,转变为了swift中的一段内存

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • 跳蚤市场 昨晚让她挑一些自己想要卖的东西,挑来挑去只有几件她小舅舅的古董玩具,自己的即使再破烂一件也不舍得卖。汗!...
    Joyce_kexin阅读 86评论 0 1
  • 这样想通了以后,次日二人再去养老院看望奶奶时,就着重强调给奶奶,"过去你们有很多过节和不悦,自古婆媳也确实难处,到...
    东隅之桑阅读 3,737评论 2 2
  • 明诚的思绪一下子回到了从前。 他从小便是孤儿,不知道自己的父母是谁?一直住在孤儿院里,直到两岁,一位女人突然出现。...
    空谷飘零阅读 3,472评论 2 15