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
地址赋给了指针num8s
,int8_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是直接寻址,即栈地址对应的内存中直接存储了数据:
-
而
var
声明的label是间接寻址,局部变量指针是0x7ffee69288a0的栈地址,栈地址内存储的是堆地址0x7fdc0cd088b0,堆地址中存储了label的实际内容:
上面我们知道了对象变量大都存储在堆中,独立基本类型大都存储在栈中,下面我们再看指针的获取方法。
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)})
):
需要注意的是我们这里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方法会自动把数组元素拷贝过去。 - 直接使用
numbers
,numbers
可以作为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中的一段内存