Swift 指针 & 内存管理

目录

一、指针

Swift 指针的不安全性:

  1. 可以指向已经释放的内存区域(内存区域已经被释放),会导致未定义的风险
  2. 数组越界,⽐如创建⼀个⼤⼩为10的数组,如果通过指针访问到了 index = 11 的位置,这个时候是不是就越界了,访问了⼀个未知的内存空间。
  3. 类型不一致,指针类型与内存的值类型不⼀致,也是不安全的。

指针类型

  1. typed pointer 指定数据类型指针(泛型指针)。
  2. raw pointer 未指定数据类型的指针(原⽣指针)。
SwiftObjctive-C备注
UnsafePointerconst Pointee *指针可变,指向的内容不可变
UnsafeMutablePointerPointee *指针及指向的内容都可变
UnsafeRawPointerconst void *指针指向的内存区域未定
UnsafeMutableRawPointervoid *指针指向的一块连续的内存区域
UnsafeBufferPointerconst Pointee *指针指向的一块连续的内存区域
UnsafeMutableBufferPointerPointee *指针指向的一块连续可变的内存区域
UnsafeRawBufferPointerconst void *指针指向的一块连续的内存区域
UnsafeMutableRawBufferPointervoid *指针指向的一块连续可变的内存区域

1.1 原生指针

原⽣指针是指当前指针未绑定到了具体的类型。

// 指针操作
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
   
print(MemoryLayout<Int>.stride) // 内存对齐后的步长
print(MemoryLayout<Int>.size)  // 当前类型的大小
print(MemoryLayout<Int>.alignment) // 内存对齐方式

for i in 0...4 {
	p.advanced(by: MemoryLayout<Int>.stride * i).storeBytes(of: i, as: Int.self)
}
        
for i in 0...4 {
	let value = p.load(fromByteOffset: i * 8, as: Int.self)
	print("\(i) - \(value)")
}

1.2 泛型指针

泛型指针是指当前指针已经绑定到了具体的类型。

// 通过指针属性 pointee 修改值
var age: Int = 18
age = withUnsafePointer(to: &age) { ptr in
	print(ptr.pointee + 12)
	return ptr.pointee
}
print(age)
        
// 通过指针属性 pointee 修改值
let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 8)
ptr.initialize(to: age)
print(ptr.pointee)
struct JHStruct {
	var age: Int
	var name: String
}
        
var jhPtr = UnsafeMutablePointer<JHStruct>.allocate(capacity: 2)
// 方式一:初始化
jhPtr[0] = JHStruct.init(age: 10, name: "张三")
jhPtr[1] = JHStruct.init(age: 10, name: "李四")

// 方式二:初始化
//        jhPtr.initialize(to: JHStruct.init(age: 10, name: "张三"))
//        jhPtr.advanced(by: MemoryLayout<JHStruct>.stride).initialize(to: JHStruct.init(age: 10, name: "李四"))
        
jhPtr.deinitialize(count: 2)
jhPtr.deallocate()

1.3 指针应用

1.3.1 获得某个变量的指针
int age = 10
// 获取实例指针
var ptr = withUnsafePointer(to: &age) { $0 }
var ptr2 = withUnsafeMutablePointer(to: &age) { $0 }
1.3.2 获取指向堆空间实例的指针
// 获取指向堆空间实例指针
class Person {
  var age: Int 
  init(age: Int) {
    self.age = age
  } 
}

var person = Person(age: 21)
// 获取 person 指针
var ptr1 = withUnsafePointer(to: &person) { UnsafeRawPointer($0) }
// 获取 person 指针存储的地址值
var personObjcAddress = ptr1.load(as: UInt.self) 
// 堆空间地址值
var ptr2 = UnsafeMutableRawPointer(bitPattern: personObjcAddress)
1.3.3 创建指针
var ptr = malloc(16)
// 存
ptr?.storeBytes(of: 10, as: Int.self)
ptr?.storeBytes(of: 20, toByteOffset: 8, as: Int.self)

// 取
ptr?load(as: Int.self)
ptr?load(fromByteOffset: 8, as: Int.self)

// 销毁
free(ptr)

// 创建指针
var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
// 偏移
aPtr = ptr.advanced(by: 8)
// 销毁
ptr.deallocate()

// 创建指针
var ptr = UnsafeMutablePointer<Int>.allocate(capacity: 3)
ptr.initialize(repeating: 10, count: 3) // 用3个10去初始化
ptr.initialize(to: 10) // 用10去初始化前8个字节

// successor 后继指针
ptr.successor().initialize(to: 10)
ptr.successor().successor().initialize(to: 33)

ptr.pointee
// 这里+1,代表是偏移一个泛型字节数,如果是非泛型的,+x就是加x
(ptr+1).pointee
(ptr+2).pointee

ptr[0] // 10
ptr[1] // 10
ptr[2] // 33
 
// 需要反初始化,前面初始化几个,就反初始几个
ptr.deinitialize(count: 3)
// 销毁
ptr.deallocate()
1.3.4 指针之间的转换

原生指针转换为泛型指针

// 创建指针
var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
 
// 通过assumingMemoryBound函数将非泛型指针转换为泛型指针
var ptr2 = ptr.assumingMemoryBound(to: Int.self)

// 强制转换
var ptr3 = unsafeBitCast(ptr, to: UnsafeMutablePointer<Int>.self)

unsafeBitCast是忽略数据类型的强制转换,不会因为数据类型的变化而改变原来的内存数据

结构体转换为指针

class JHStruct {
	var age: Int = 10
	var name: String = "jh"
}
        
struct HeapObject {
	var metadata: UnsafeRawPointer
	var refCounted1: UInt32
	var refCounted2: UInt32
}
        
let t = JHStruct()
    
// 获取对象指针
let objRawPtr = Unmanaged.passUnretained(t as AnyObject).toOpaque()
// 类型绑定
let objPtr = objRawPtr.bindMemory(to: HeapObject.self, capacity: 1)
print(objPtr.pointee)

swift 提供了三种不同的 API 来绑定/重新绑定指针

  • assumingMemoryBound(to:)
  • bindMemory(to: capacity:)
    func testPoint(_ p: UnsafePointer<Int>) {}
    let tuple = (10, 20)
    withUnsafePointer(to: tuple) { (tuplePtr: UnsafePointer<(Int, Int)>) in 
    		// UnsafeRawPointer() 转换为原始指针,不保留指针类型
      	// 对于我们能明确知道指针类型,就可以告诉编译器预期的类型,让编译器绕过类型检查,并不会发生实际的转换
      	testPoint(UnsafeRawPointer(tuplerPtr).assumingMemoryBound(to: Int.self))
      	// ⽤于更改内存绑定的类型,如果当前内存还没有类型绑定,则将⾸次绑定为该类型;否则重新绑定该类型,并且内存中所有的值都会变成该类型。
      	testPoint(UnsafeRawPointer(tuplerPtr).bindMemory(to: Int.self, capacity: 1))
    }
    
  • withMemoryRebound(to: capacity: body:)
    func testPointer(_ p: UnsafePointer<Int8>){ }
    
    uint8Ptr.withMemoryRebound(to: Int8.self, capacity:count){ (int8Ptr : UnsafePointer<Int8>) in 
        // 转换类型
        testPointer(int8Ptr)
    }
    

二、内存管理

  • 跟OC一样,Swift也是使⽤⾃动引⽤计数(ARC)机制来追踪和管理内存
  • Swift的ARC中有3中引用
    • 强引用(strong reference):默认情况下,引用都是强引用
    • 弱引用(weak reference):通过weak定义弱引用
      • 必须是可选类型的var,因为实例销毁后,ARC会自动将弱引用设置为nil
      • ARC自动给弱引用设置为nil时,不会出发属性观察器
    • 无主引用(unowned reference):通过unowned定义无主引用
      • 不会产生强应用,可以是非可选类型,实例销毁后仍然存储这实例的内存地址(类似于OC的unsafe_retained)
      • 试图在实例销毁后访问无主引用,会产生运行时错误(野指针)

weak、unowned只能用在类实例上面

// swift 实例指针结构
// Swift 引用计数存储结构(初始化函数 swift_allocObject,默认引用计数初始值 UnownedRefCount = 1,StrongExtraRefCount = 0)

HeapObject { 
  // 实例指针
  isa 
  // 引用计数结构
  InlineRefCounts { 
    atomic<InlineRefCountBits> { 
      // InlineRefCountBits(uint64_t): 继承 RefCountBitsT
      // 无弱引用结构: Swift 引用计数由 8 字节存储,64位(0:isImmortal,1~31:UnownedRefCount(无主引用计数),32:isDeinitingMask(是否正在析构),32~62:StrongExtraRefCount(强引用计数),63:UserSlowRC)
      strong RC + unowned RC + flags 
      OR(或者)
      // 有弱引用结构: Swift 引用计数由 8 字节存储,64位(sideTable: 引用计数散列表地址,62:UserSlowRC,63:SideTableMarkShift)
      HeapObjectSideTableEntry* 
    } 
  } 
}

HeapObjectSideTableEntry { 
  // 实例指针
  object pointer 
  // 散列表结构
  SideTableRefCounts { 
    atomic<SideTableRefCountBits> { 
      // SideTableRefCountBits(uint64_t + uint32_t): 继承 RefCountBitsT
      strong RC + unowned RC + weak RC + flags 
    } 
  } 
} 

小结:⼀个对象在初始化的时候后是没有 SideTable 的,当我们创建⼀个弱引⽤的时候,系统会创建⼀个 Side Table 。

Autoreleasepool 自动释放池

public func autoreleasepool<Result>(invoking body: () throws -> Result) rethrows -> Result

// 自动释放池使用
autoreleasepool {
  let p = MJPerson(age: 20, name: "Jack")
  p.run()
}

2.1 内存访问冲突

内存访问冲突会在两个访问满足下列条件时发生:

  • 至少一个是写入操作
  • 它们访问的是同一块内存
  • 它们的访问时间重叠(比如在同一个函数内)
// 不存在内存访问冲突
func plus(_ num: inout Int) -> Int { num + 1 }
var number = 1
number = plus(&number)

// 存在内存访问冲突
var step = 1
func increment(_ num: inout Int) -> Int { num += step }
increment(&step)

// 解决内存访问冲突
var copyStep = step
increment(&step)
step = copyStep

如果下面的条件可以满足,就说明重叠访问结构体的属性是安全的

  • 只访问实例存储属性,不是计算属性或者类属性
  • 结构体时局部变量而非全局变量
  • 结构体要么没有被闭包捕获要么只被非逃逸闭包捕获
// ok, 局部变量,编译器认为是安全的,可控的,故不会有问题
func test() {
  var tulpe = (health: 10, energy: 20)
  balance(&tulpe.health, &tulpe.energy)
  
  var holly = Player(name: "Holly", health: 10, energy: 20)
  balance(&holly.health, &holly.energy)
}

test()

三、循环引用

weak、unowned 都能解决循环引用的问题,unowned 要比 weak 少一些性能消耗

  • 在生命周期中可能会变成 nil 的使用 weak
  • 初始化赋值后再也不会变为 nil 的使用 unowned,容易出现野指针

3.1 闭包的循环引用

闭包表达式默认会对用到的外层对象产生额外的强引用(对外层对象进行了retain操作)

class Person {
	var fn: (() -> ())?
	func run() { print("run") }
	deinit { print("deinit") }
}

func test() {
  let p = Person()
  p.fn = { p.run() } // 对person实例进行了retain操作,产生了循环引用
}

// 处理循环引用
func test() {
  let p = Person()
  p.fn = { [weak p] in // [weak p] 捕获列表
  	p?.run()  // weak 定义的必须为可选类型
  } 
}
// 或者
func test() {
  let p = Person()
  p.fn = { [weak wp = p, unowned up = p, a = 10 + 20] in
  	wp?.run()  // weak 定义的必须为可选类型
  } 
}

test()

如果想在定义闭包属性的同时引用 self,这个闭包必须是 lazy 的(因为在实例初始化完毕之后才能引用 self)

class Person {
  // 提示错误,因为在实例初始化完毕后才能使用self,那这里fn在初始化过程中就使用了self,可以用lazy
  var fn: (() -> ()) = {
    self.run()
  }
  
  // 正确
  lazy var fn: (() -> ()) = {
    self.run()
  }
  
  func run() { print("run") }
  deinit { print("deinit") }
}

如果 lazy属性时闭包调用的结果,那么不用考虑循环引用的问题(因为闭包调用后,闭包的生命周期就结束了)

class Person {
  var age: Int = 0
  // lazy 后面的()就相当于调用了闭包表达式,代表闭包的声明周期结束
  lazy var getAge: Int = {
    self.age
  }()
  
  deinit { print("deinit") }
}

循环引用补充知识点

// 捕获列表
var a = 10
var stu: JHStudent = JHStudent()
let closure = { [a, weak stu] in // [a] 为捕获列表参数,不可变
	print(a)
  // 延长生命周期: 方式一
  if let strongSelf = stu {}
  // 延长生命周期: 方式二
  withExtendedLifetime(stu) {
    // 区间内延长生命周期
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值