站在汇编角度深入了解 Swift(十五)

555 阅读3分钟

内存管理

  • 跟 OC 一样,Swift 也是采用基于引用计数的 ARC 内存管理方案(针对堆空间)
  • Swift 中的 ARC 有3种引用
    • 强引用:默认情况下,引用都是强引用
    • 弱引用:通过 weak 定义弱引用
      • 必须是可选类型的 var,因为实例销毁后,ARC 会自动将弱引用设置为 nil
      • ARC 自动给弱引用设置为 nil 时,不会触发属性观察器
    • 无主引用:通过 unowned 定义无主引用
      • 不会产生强引用,非可选类型,实例销毁后仍然存储着实例的内存地址(和 oc 的 unsafe_unretained 类似)
      • 试图在实例销毁后访问无主引用,会产生运行时错误(野指针)

weak、unowned 的使用限制

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

Autoreleasepool

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

循环引用(Reference Cycle)

  • weak、unowned 都能解决循环引用的问题,unowned 要比 weak 少一些性能消耗
    • 在生命周期可能会变为 nil 的使用 weak
    • 初始化赋值后再也不会变为 nil 的使用 unowned

闭包的循环引用

  • 闭包表达式默认会对用到的外层对象产生额外的强引用
  • 这个参考 oc 中的 block 就可以了

@escaping

  • 逃逸闭包
  • 不允许捕获输入输出参数

内存访问冲突

  • 内存访问冲突会在两个访问满足下列条件时发生:
    • 至少一个是写入操作
    • 它们访问的是同一块内存
    • 它们访问时间重叠
    • 怎么解决?可以利用 Copy In Copy Out
Simultaneous accesses to 0x1006469c0, but modification requires exclusive access.
var step = 1
func increment(_ num: inout Int) {
    num += step
}
increment(&step)
解决方案
var t = step
increment(&t)
step = t

指针

  • UnsafePointer <-> const Pointee *
  • UnsafeMutablePointer <-> Pointee *
  • UnsafeRawPointer <-> const void *
  • UnsafeMutableRawPointer <-> void *

获取指向某个变量的指针

  • withUnsafeMutablePointer(to: &age) { $0 }
  • withUnsafePointer(to: &age) { $0 }
@inlinable public func withUnsafeMutablePointer<T, Result>(to value: inout T, _ body: (UnsafeMutablePointer<T>) throws -> Result) rethrows -> Result {
    body(to)
}
var age = 10
var ptr = withUnsafeMutablePointer(to: &age) { $0 }
ptr.pointee = 20
print(age)

var ptr1 = withUnsafeMutablePointer(to: &age) { UnsafeMutableRawPointer($0) }
ptr1.storeBytes(of: "a", as: String.self)
print(age)
---------------执行结果---------------
20
97

获得指向某个堆空间实例的指针

  • withUnsafePointer(to: &person) { UnsafeRawPointer($0) }
  • UnsafeMutableRawPointer(bitPattern: ptr.load(as: UInt.self))
class Person {
    var age: Int = 20
}
var person = Person()
var ptr = withUnsafePointer(to: &person) { UnsafeRawPointer($0) }
var ptr1 = UnsafeMutableRawPointer(bitPattern: ptr.load(as: UInt.self))
print(ptr, ptr1)
---------------执行结果---------------
0x00007ffeefbff428 Optional(0x0000000100712200)

创建指针

  • UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
  • deallocate()
  • malloc(16)
  • free(ptr)

上面两种都可以创建

var ptr = malloc(16)
ptr?.storeBytes(of: 11, as: Int.self)
ptr?.storeBytes(of: 22, toByteOffset: 8, as: Int.self)
print(ptr?.load(as: Int.self))
print(ptr?.load(fromByteOffset: 8, as: Int.self))
free(ptr)
class Person {
    var age: Int = 0
    var name: String = ""
    
    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
    
    deinit {
        print("deinit")
    }
}

var ptr = UnsafeMutablePointer<Person>.allocate(capacity: 1)
defer {
    ptr.deinitialize(count: 1)
    ptr.deallocate()
}
ptr.initialize(to: Person(age: 10, name: "json"))
print(ptr[0].age)

HandyJson 中大量使用了这个指针,后面我给会写博客具体解读他。

指针之间的转换

  • unsafeBitCast 是忽略数据类型的强制转换,不会因为数据类型的变化而改变原来的内存数据
    • 也就是说你内存中原来是什么,转换之后的还是什么
    • 如果用 unsafeBitCast 强转,强转后的字节数不同,那么会报错
var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
ptr.assumingMemoryBound(to: Int.self).pointee = 11
(ptr + 8).assumingMemoryBound(to: Double.self).pointee = 20.0

print(unsafeBitCast(ptr, to: UnsafePointer<Int>.self).pointee)
print(unsafeBitCast(ptr + 8, to: UnsafePointer<Double>.self).pointee)
var age = -1
var age1 = unsafeBitCast(age, to: UInt.self)
print(age, age1)
-----------------------执行结果-----------------------
(lldb) x/wg 0x7ffeefbff438
0x7ffeefbff438: 0xffffffffffffffff
(lldb) x/wg 0x7ffeefbff430
0x7ffeefbff430: 0xffffffffffffffff
-1 18446744073709551615

思考

  1. swift 中的 weak 变量是怎么被初始化的?

  2. 获取对象堆内存的地址有没有更简单的方法?

    var ptr = unsafeBitCast(person, to: UnsafeRawPointer.self) 这个就相当于将 person 里面存储的地址强转换成 UnsafeRawPointer 类型