Swift-指针
Swift-指针
一、指针
1.指针的不安全性
1.1.1 野指针
什么是野指针;我们在创建对象时需要在堆上分配内存空间,但内存空间的生命周期是有限的,如果我们使用指针指向这块空间,如果后续内存空间的额生命周期结束(当引用计数为0时),那么此时的指针就成为了野指针。
1.1.2 指针越界
什么是指针越界:我们都知道内存空间是有边界的,如果存在一个大小为10的数组,我们通过指针可以访问index = 11的位置时候,指针就出现了越界的情况。
1.1.3 类型不一致
什么是类型不一致;比如说存储的内容为Int类型,但是如果指针式Int8类型,就会导致精度损失。
1.2指针类型
1.2.1 数据类型计算工具
- MemoryLayout<Int>.size MemoryLayout<Int>.stride MemoryLayout<Int>.alignment
- size:计算数据类型的实际大小
- stride:计算数据的步长(连续存储,从当前实例的开始位置到下一个实例的开始位置的内存长度)
- aligment:字节对齐方式(内存会自动对齐)
下面我们通过一个例子来说明:
struct LGTeacher{ var age: Int = 18 var name: Bool = true } print(MemoryLayout<LGTeacher>.size) print(MemoryLayout<LGTeacher>.stride) print(MemoryLayout<LGTeacher>.alignment)
//9 INT是8个字节、BOOL是1个字节
//16 按照8字节对齐后,步长是两个8字节
//8 对齐以8字节对齐
1.2.2常用的指针类型
- 常用的指针类型,带<T>表示数据类型指针,带Raw表示表示原生指针,带Buffer表示连续内存地址
1.2.3 raw pointer(原生指针)
- UnsafeMutableRawPointer(可修改原生指针):可用StoreBytes和load进行数据的存储与读取
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8) for i in 0..<4{ p.advanced(by: i * MemoryLayout<Int>.stride).storeBytes(of: i, as: Int.self) } for i in 0..<4{ let value = p.load(fromByteOffset: i*8, as: Int.self) print("index:\(i),value:\(value)") } p.deallocate()
//
index:0,value:0
index:1,value:1
index:2,value:2
index:3,value:3
-
allocate(byteCount: 32, alignment: 8)表示创建一个X字节的容量,用X字节对齐方式的内存空间
-
advanced(by:)表示每次向后移动X字节
-
storeBytes(of: i, as:T)将T类型的值i存到指针中
-
load(fromByteOffset: i*8, as: T)表示偏移X字节来读取T类型的值
-
deallocate()表示回收内存空间
1.2.4 typed pointer(泛型指针)
- 通过withUnsafePointer等来访问当前变量地址,数据存取不再通过storeBytes和load进行,而是通过操作当前泛型指针内置的变量pointee
- unsafePointer的两种获取方式(通过已有变量获取、或者分配内存)
第一种:通过已有变量获取
var age = 20 age = withUnsafePointer(to: &age){ ptr in ptr.pointee + 10 //ptr指针的值 print(age) //30
第二种:通过直接分配内存获取
var ptr = UnsafeMutablePointer<LGTeacher>.allocate(capacity: 5) ptr[0] = LGTeacher(age: 10, name: true) ptr[1] = LGTeacher(age: 11, name: false) print("\(ptr)\n,\(ptr.pointee)\n,\(ptr[0])\n,\(ptr[1])\n") ptr.deinitialize(count: 5) ptr.deallocate() // 0x00000001013259c0 ,LGTeacher(age: 10, name: true) ,LGTeacher(age: 10, name: true) ,LGTeacher(age: 11, name: false)
-
allocate(capacity: 5)表示创建多大的空间
-
deinitialize(count: 5)表示将这五个空间抹零
-
deallocate()释放
第三种:通过advanced分配内存
var ptr = UnsafeMutablePointer<LGTeacher>.allocate(capacity: 5) ptr.initialize(to: LGTeacher()) ptr.advanced(by: MemoryLayout<LGTeacher>.stride).initialize(to: LGTeacher()) print(ptr.pointee) ptr.deinitialize(count: 5) ptr.deallocate() // LGTeacher(age: 18, name: true)
二、内存绑定(Swift有三种绑定/重新绑定的API)
2.1 assumingMemoryBound(to:)
- 目的:绕过编译器检查并省去繁琐的类型转换
- 使用场景:我们在处理代码中,只有原始指针(没有指针类型),但此刻我们明确知道指针的类型,那么我们可以通过这个API告诉编译器预期的类型
- 并未发生实际类型的转换
var tuple = (10,20) withUnsafePointer(to:tuple){(tuplePtr:UnsafePointer<(Int,Int)>) in printMemory(ptr: UnsafeRawPointer(tuplePtr).assumingMemoryBound(to: Int.self)) } func printMemory(ptr:UnsafePointer<Int>){ print(ptr[0]) print(ptr[1]) } // 10 20
-
UnsafeRawPointer(tuplePtr).assumingMemoryBound(to: Int.self))先将tuple转化为原生指针,使用assumeMemoryBound来告诉编译器类型,此时编译器就不会作出检查了。
2.2 bindMemory(to:capaity)
- 目的:更改内存绑定的类型
- 如果内存没有绑定类型,则首次绑定该类型,否则重新绑定,并且内存中的所有值都会变为该类型
- 发生类型的转换
2.3 withMemeoryRebound(to:capaity:body:)
- 目的:临时更改绑定类型
- 当我们给娃补函数传递参数时,可能会存在数据类型不一致的问题,如果我们进行类型转化,会产生数据的来回复制,这个时候我们就可以用withMemeoryRebound,更改的类型仅在作用域内生效
let Int8Ptr = UnsafePointer<Int8>.init(bitPattern: 20) Int8Ptr?.withMemoryRebound(to: UInt8.self, capacity: 1, {(UInt8Ptr:UnsafePointer<UInt8>)in printMemory(ptr:UInt8Ptr) }) func printMemory(ptr:UnsafePointer<UInt8>){ print(ptr) } // 0x0000000000000014(16进制下的20)
- 这里解释一下,因为向printMemory传递参数的时候需要参数转换,此时就可以用临时绑定的API。
三、内存管理(从引用计数的角度出发)
- Swift中使用自动引用计数(ARC)机制来追踪和管理内存,那么我们都知道Swift对象是有Metadata和refcounts组成的,那么refcounts就是管理引用计算的工具。
- 查看refcounts需要用到swift对象的内存指针,他位于后8个字节(16进制中2位为一个字节)
- 对于实例对象的内存指针的查看,我们可以用 Unmanaged.passRetained(XXX as AnyObject).toOpaque()来打印
let teacher = LGTeacher() print(Unmanaged.passRetained(teacher as AnyObject).toOpaque()) // 0x0000000101531f70
- 我们来观察一下对象内存地址中内容的变化
let teacher = LGTeacher() print(Unmanaged.passRetained(teacher as AnyObject).toOpaque()) let t1 = teacher print(Unmanaged.passRetained(teacher as AnyObject).toOpaque()) let t2 = teacher print(Unmanaged.passRetained(teacher as AnyObject).toOpaque()) print()
- 红色标出的就是refcount的位置,我们可以发现每产生一次引用时,refcount就发生了变化。
3.1 强引用
- 我们在HeapOject.h中可以发现refcounts属于InlineRefCounts的宏定义(Swift源码可以在github中找到)
我们通过查看lnlineRefCount内容发现
- InlineRefCounts传入的是InlineRefCountBits
- SideTableRefCounts(弱引用使用),传入SideTableRefCountBits
- 接下来我们查看一下RefCounts的模版结构
- 接下来,我们看一下RefCountsBits
- BitsType宏定义的bits,后续操作都是关于这个参数的,由此我们可以得知,无论是swift还是OC里面的引用计数中都有一个64位的位域信息,在这个64位中中存储了当前类运行的生命周期相关的引用计数。
- 那么引用计数是怎么增加的呢,我们接下来看一个实例引用计数的变化,首先找到他的创建方法
- 进入方法查看
- 接下来,我们看一下什么是RefCountBits
- 这里其实是对控制台输出的引用计数+1的值的计算方式,那接下来我们看看引用计数是怎么+1的
3.2 弱引用
- swift提供了两种方式来避免循环引用的问题(无主引用、弱引用)
- 弱引用不会持有实例,在实例被释放时,可能存在弱引用仍然引用着这个实例,因此,ARC会在实例被释放时自动设置弱引用为nil,这同时也意味着弱引用可以是可选类型
- 声明一个weak变量相当于定义了一个WeakRefence对象,就会形成一个散列表
- 下面我们先看一个强引用的例子
class LGTeacher{ var age:Int = 19 var subject:LGSubject? } class LGSubject{ var name:String init(name:String){ self.name = name } var t = LGTeacher() var s = LGSubject.init(name: "swift") t.subject = s
- 此时,我们就可以通过一个弱引用来解决这个问题(weak var t = XXX)
- 通过汇编我们可以发现弱引用会走一个swift_weakinit的方法
- 创建一个弱引用时,系统会创建一个Side Table,Side Table是一种类名,为HeapObjectSideTableEntry的结构,里面有RefCounts成员,内部是SideTableRefCountBits,其实就是原来的uint64_t加上一个存储弱引用数的uint32_t
3.3 无主引用
- 无主引用不能设置为nil,不是一个可选类型
- 如果弱引用双方的生命周期之间没有任何关联,就是用weak
- 如果一个对象销毁,另一个对象也跟着销毁,就要使用unowned
- 一般来说,我们都是用weak,但是unowned的性能更好,他不需要再创建一个散列表,直接对refcounts的64位操作即可
四、闭包循环引用
- 闭包内部对变量的修改会导致外部原始变量值的改变
- 如果我们在class内部定义了一个闭包,当前闭包在访问属性的过程中,就会对我们当前的实例对象进行捕获,如果实例又调用了闭包就会产生循环引用
class LGTeacher{ var age = 18 var complateCallBack:(()->())? } let t = LGTeacher() t.complateCallBack{ t.age += 1 }
- 如果要避免这类问题,就需要使用weak/unowned来修饰捕获列表
t.complateCallBack = {[Unowned t] in t.age += 1 }
五、捕获列表
- 默认情况下,闭包表达式从周围环境中捕获常量和变量,并强引用这些值,可以使用捕获列表来显式控制如何在闭包中捕获该值
- 在参数列表之前,捕获列表被写为用逗号括起来的表达式,并用方括号括起来,如果使用捕获列表,则即使省略参数名称,参数类型和返回类型,也必须使用in关键字
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)