swift知识技巧搜集收藏swift

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

2018-10-09  本文已影响464人  stonly916

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形式的指针有更多的可操作性,是以下讲的重点。

指针的获取

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

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

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)
  }
}
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_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)

对于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的指针,这里使用两种方式:

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

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

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

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中的一段内存

上一篇 下一篇

猜你喜欢

热点阅读