Swift学习:内存管理,weak,unowned,autoreleasepool,循环引用,escaping,指针

内存管理

  • 跟OC一样,Swift也是采取基于引用计数的ARC内存管理方案(针对堆空间) 
class Person {
    deinit {
        print("Person.deinit")
    }
}

print(1)
var p: Person? = Person()
print(2)
p = nil
print(3)
//输出结果为:
//1
//2
//Person.deinit
//3

由上图可以看出,p置为nil时,对象销毁,走deinit方法 

  • Swift的ARC中有3种引用:
  • 强引用(strong reference):默认情况下,引用都是强引用
  • 弱引用(weak reference):通过weak定义弱引用
  1. 必须是可选类型的var,因为实例销毁后,ARC会自动将弱引用设置为nil 
  2. ARC自动给弱引用设置nil时,不会触发属性观察器
class Dog { }
class Person {
    weak var dog: Dog? {
        willSet { }
        didSet { }
    }
    deinit {
        print("Person.deinit")
    }
}
  • 无主引用(unowned reference):通过unowned定义无主引用
  1. 不会产生强引用,实例销毁后仍然存储着实例的内存地址(类似于OC中的unsafe_unretained)
  2. 试图在实例销毁后访问无主引用,会产生运行时错误(野指针)
  3.  Fatal error: Attempted to read an unowned reference but object 0x0 was already deallocated
class Dog { }
class Person {
    unowned var dog: Dog {
        willSet { }
        didSet { }
    }
    deinit {
        print("Person.deinit")
    }
    init() {
        self.dog = Dog()
    }
}

weak、unowned的使用限制

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

 其中的Livable协议继承自AnyObject,所以只有类能遵守此协议,所以只有类实例能用


autoReleasepool


循环引用(Reference Cycle)

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


闭包的循环引用

  • 闭包表达式默认会对用到的外层对象产生额外的强引用(对外层对象进行了retain操作) 
  • 下面代码会产生循环引用,导致Person对象无法释放(看不到Person的deinit被调用)
class Person {
    var fn: (() -> ())?
    func run() { print("run")}
    deinit {
        print("deinit")
    }
}

func test() {
    let p = Person()
    //p对闭包fn有一个强引用,闭包对p也有一个强引用
    p.fn = {
        p.run()
    }
}

test() //这里不会输出deinit,因为闭包产生了循环引用

通过反汇编我们可以窥探其原理,也就是引用计数的变化:

  •  在闭包表达式的捕获列表声明weak或unowned引用,解决循环引用问题

weak的方式:

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

func test() {
    let p = Person()
    //p对闭包fn有一个强引用,闭包对p也有一个强引用
    p.fn = {
        [weak p] in
        p?.run()
    }
}

test() //deinit 对象销毁

unowned的方式:

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

func test() {
    let p = Person()
    //p对闭包fn有一个强引用,闭包对p也有一个强引用
    p.fn = {
        [unowned p] in
        p.run()
    }
}

test() //deinit 对象销毁

甚至可以在捕获列表中给P对象重命名,或设置新的参数a

  •  如果想在定义闭包属性的同时引用self,这个闭包必须是lazy的(因为在实例初始化完毕之后才能引用self) 
class Person {
    //lazy保证了只用调用fn函数时才会初始化里面的self
    lazy var fn: (() -> ()) = {
        //消除循环引用
      [weak self] in
      self?.run()
      //unowned也可以消除循环引用
      //[unowned self] in
      //self.run()
    }
    func run()  {
        print("run")
    }
    deinit {
        print("deinit")
    }
}

func test() {
    var p = Person()
    p.run()
}

test()
//输出结果
//run
//deinit
  • 如果上边的闭包fn内部如果用到了实例成员(属性、方法),编译器会强制要求明确写出self
  • 如果lazy属性是闭包调用的结果,那么不用考虑循环引用的问题(因为闭包调用后,闭包的生命周期就结束了)


@escaping

  • 非逃逸闭包、逃逸闭包,一般都是当做参数传递给函数
  • 非逃逸闭包:闭包调用发生在函数结束前,闭包调用在函数作用域内

  • 逃逸闭包:闭包有可能在函数结束后调用,闭包调用逃离了函数的作用域,需要通过@escaping声明

闭包fn只是赋给了全局变量gFn,gFn可能在函数结束后调用,所以闭包fn也可能在函数结束后调用,所以要加@escaping

因为下图中的fn()的实现调用是在全局队列异步函数中,所以可能在test3函数结束后调用fn,这时如果fn函数里使用了test3函数里的变量或方法,是会报错的,因为已经销毁了,所以要加@escaping

Dispatch结合逃逸闭包实例如下:

typealias Fn = () -> ()
class Person {
    var fn: Fn
    //fn是逃逸闭包
    init(fn: @escaping Fn) {
        self.fn = fn
    }
    func run( ) {
        //DispatchQueue.global().async也是一个逃逸闭包
        //它用到了实例成员(属性、方法),编译器会强制要求明确写出self,这样会对Person的对象的生命周期产生影响,保证在DispatchQueue.global().async中调用后再销毁
        DispatchQueue.global().async {
            self.fn()
            //如果想要保证如果self这个Person实例销毁了就不调用fn函数,防止崩溃,就要用weak声明self,并把实例对象p写成可选型,代码如下:
//            [weak p = self] in
//            p?.fn()
        }
    }
}
  • 逃逸闭包不可以捕获inout参数

示例1: 当调用test函数,传入一个value的地址,由于other2是逃逸闭包,有可能在test函数执行完,value已经销毁的情况下执行,这时候传给other2的value的地址已经销毁,所以会报错

示例2:  这里的返回值是plus,是一个闭包函数,并不知道什么时候调用,但是用到了test方法里的参数,有可能造成和示例1里一样逃逸闭包的问题,所以报错


内存访问冲突(Conflicting Access to Memory)

  • 内存访问冲突会在两个访问满足下列条件时发生: 
  1.  至少一个是写入操作
  2. 它们访问的是同一块内存
  3. 它们的访问时间重叠(比如在同一个函数内)

下图的情况会造成内存访问冲突:

解决内存访问冲突:

访问冲突示例:

  这里报错的代码相当于修改同一个类的属性,也就是访问同一个类的内存,会报错

下图的代码也属于访问同一个类的不同属性,也是相当于同时访问一块内存,所以也是错的:

  • 但是如果你满足下面的条件,就说明重叠访问结构体的属性是安全的:
  1. 你只访问实例存储属性,不是计算属性或者类属性
  2. 结构体是局部变量而非全局变量
  3. 结构体要么没有被闭包捕获要么只被非逃逸闭包捕获


指针

Swift中也有专门的指针类型,这些都被定性为“Unsafe”(不安全的),常见的有以下4种类型 

  1. UnsafePointer<Pointee> 类似于 const Pointee *,这表示只读的指针,只能访问不能修改内存,<Pointee>是泛型,如果Pointee是Int,就代表Int类型的指针
  2. UnsafeMutablePointer<Pointee> 类似于 Pointee *,这表示既能访问内存,又能修改内存的指针,<Pointee>是泛型,如果Pointee是Double,就代表Double类型的指针
  3. UnsafeRawPointer 类似于 const void *,这表示只读的指针,只能访问不能修改内存,并且没有泛型约束类型,所以它代表的指针类型不确定,也就是可以代表任意类型
  4. UnsafeMutableRawPointer 类似于 void *,这表示既能访问内存,又能修改内存的指针,并且没有泛型约束类型,所以它代表的指针类型不确定,也就是可以代表任意类型
var age = 10
func test1(_ ptr:UnsafeMutablePointer<Int>) {
    ptr.pointee += 10
}

func test2(_ ptr:UnsafePointer<Int>) {
    print(ptr.pointee)
}

test1(&age)
test2(&age) //20
print(age) //20
var age = 10
func test3(_ ptr: UnsafeMutableRawPointer) {
    ptr.storeBytes(of: 20, as: Int.self)
}
func test4(_ ptr: UnsafeRawPointer) {
    print(ptr.load(as: Int.self))
}

test3(&age)
test4(&age)//20
print(age)//20

指针的应用实例

var arr = NSArray(objects: 11, 22, 33, 44)
arr.enumerateObjects { (obj, idx, stop) in
    if idx == 2 {
        stop.pointee = true //下标为2停止遍历,但是是在当前循环的代码块执行完后再停止
    }
    print(idx, obj)
//输出结果:
//    0 11
//    1 22
//    2 33
}

在Swift中表推荐如下遍历方式:

//(idx, obj)为元组
for (idx, obj) in arr.enumerated() {
    print(idx, obj)
    if idx == 2 {
        break
    }
//输出结果:
//    0 11
//    1 22
//    2 33
}

获得指向某个变量的指针

  • 获取UnSafePointer和UnsafeMutablePointer类型指针
var age = 11
//ptr1类型 UnSafePointer<Int>
var ptr1 = withUnsafePointer(to: &age) { $0 }
//ptr2类型 UnsafeMutablePointer<Int>
var ptr2 = withUnsafeMutablePointer(to: &age){ $0 }
ptr2.pointee = 22
print(age)
print(ptr1.pointee)
  • 获取UnsafeMutableRawPointer和UnsafeRawPointer类型指针 
var age = 11
//ptr3类型 UnsafeMutableRawPointer
var ptr3 = withUnsafeMutablePointer(to: &age) { UnsafeMutableRawPointer($0) }
//ptr4类型 UnsafeRawPointer
var ptr4 = withUnsafePointer(to: &age) { UnsafeRawPointer($0)}
ptr3.storeBytes(of: 33, as: Int.self)
print(age) //33
print(ptr4.load(as: Int.self)) //33

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

示例1: 由下面的代码我们可以看到通过person对象获取的指针ptr实际就是实例对象person的地址。

class Person {
    var age : Int
    init(age: Int) {
        self.age = age
    }
}
var person = Person(age: 21)
var ptr = withUnsafePointer(to: &person) { $0 }
print(ptr) //0x0000000100559de8
print(ptr.pointee.age) //21

// 获得变量的内存地址
print(Mems.ptr(ofVal: &person)) //0x0000000100559de8
// 获得引用所指向内存的地址
print(Mems.ptr(ofRef: person))  //0x0000000100e5eff0

 示例2: 通过UnsafeRawPointer指针获取实例对象person的地址,和person所指向的内存地址

class Person {
    var age : Int
    init(age: Int) {
        self.age = age
    }
}
var person = Person(age: 21)
//ptr是指向person对象地址的指针
var ptr = withUnsafePointer(to: &person) { UnsafeRawPointer ($0) }
//ptr.load(as: Int.self)指的是person对象所指的堆空间地址值
//heapPtr是指向person对象所指的堆空间地址的指针
var heapPtr = UnsafeRawPointer(bitPattern: ptr.load(as: Int.self))
print(ptr) //0x000000010055add8
print(heapPtr!) //0x0000000100f2ab00
print(Mems.ptr(ofVal: &person)) //x000000010055add8
print(Mems.ptr(ofRef: person)) //0x0000000100f2ab00

创建指针

创建指针方式1:

//创建:ptr指向一个地址为0x100001234堆空间
var ptr1 = UnsafeRawPointer(bitPattern: 0x100001234)
print(ptr1!) //0x0000000100001234
//创建:申请16个字节的堆空间,ptr指向这个堆空间
// ptr2类型:UnsafeMutableRawPointer
var ptr2 = malloc(16)
//存:
//前8个字节存 11
ptr2?.storeBytes(of: 11, as: Int.self)
//偏移8个字节,后8个字节存 22
ptr2?.storeBytes(of: 22, toByteOffset: 8, as: Int.self)
//取:
//取前8个字节
print((ptr2?.load(as: Int.self))!) //11
//从第8个字节起,取后8个字节
print((ptr2?.load(fromByteOffset: 8, as: Int.self))!) //22

free(ptr2)

创建指针方式2:

//创建指针
var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
//前8个字节存 11
ptr.storeBytes(of: 11, as: Int.self)
//后8个字节存 22
ptr.advanced(by: 8).storeBytes(of: 22, as: Int.self)
print(ptr.load(as: Int.self)) //11
print(ptr.advanced(by: 8).load(as: Int.self)) //22
//释放
ptr.deallocate()

创建指针方式3:

var ptr = UnsafeMutablePointer<Int>.allocate(capacity: 3)//3表示3个Int,申请24个字节的内存
//ptr.initialize(repeating: 10, count: 3)//表示24个字节存的3个Int为10
//存第一个Int 11
ptr.initialize(to: 11)
//存第二个Int 22
ptr.successor().initialize(to: 22)
//存第三个Int 33
ptr.successor().successor().initialize(to: 33)

//取值:
print(ptr.pointee) //11
print(ptr.successor().pointee) //22
print(ptr.successor().successor().pointee) //33

print(ptr.pointee) //11
print((ptr + 1).pointee) //22
print((ptr + 2).pointee) //33

print(ptr[0]) //11
print(ptr[1]) //22
print(ptr[2]) //33

//销毁
ptr.deinitialize(count: 3)
ptr.deallocate()

指针销毁的注意点:必须将所有初始化的全部释放,否则会造成内存泄漏

class Person {
    var age: Int
    var name: String
    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
    deinit {
        print(name, "deinit")
    }
}

var ptr = UnsafeMutablePointer<Person>.allocate(capacity: 3)
ptr.initialize(to: Person(age: 10, name: "Jack"))
(ptr + 1).initialize(to: Person(age: 11, name: "Rose"))
(ptr + 2).initialize(to: Person(age: 12, name: "Kate"))
//销毁
ptr.deinitialize(count: 3)
//输出:必须写3,才能将3个初始化全部释放,比如写2只能释放2个,这样可能会造成内存泄漏
//Jack deinit
//Rose deinit
//Kate deinit
ptr.deallocate()

指针之间的转换

  • 指针之间转换的方式1:assumingMemoryBound
var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
ptr.assumingMemoryBound(to: Int.self).pointee = 11
var ptr2 = ptr.assumingMemoryBound(to: Int.self)
//ptr2为UnsafeMutablePointer<Int>
print(ptr2.pointee) //输出为11

//ptr为UnsafeMutableRawPointer,因为它属于不确定指针类型,所以ptr + 8 就代表向后偏移8个字节
(ptr + 8).assumingMemoryBound(to: Double.self).pointee = 22.0
var ptr3 = (ptr + 8).assumingMemoryBound(to: Double.self)
//ptr3类型为UnsafeMutablePointer<Double>
print(ptr3.pointee) //输出为22.0

ptr.deallocate()
ptr2.deallocate()
ptr3.deallocate()
  • 指针之间转换的方式2:unsafeBitCast,类似于C++中的reinterpret_cast
var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
unsafeBitCast(ptr, to: UnsafeMutablePointer<Int>.self).pointee = 11
//ptr2为UnsafeMutablePointer<Int>类型
var ptr2 = unsafeBitCast(ptr, to: UnsafeMutablePointer<Int>.self)
print(ptr2.pointee) //11
print(unsafeBitCast(ptr, to: UnsafeMutablePointer<Int>.self).pointee) //11

unsafeBitCast(ptr + 8, to: UnsafeMutablePointer<Double>.self).pointee = 22.0
//ptr3类型为UnsafeMutablePointer<Double>
var ptr3 = unsafeBitCast(ptr + 8, to: UnsafeMutablePointer<Double>.self)
print(ptr3.pointee) //22.0
print(unsafeBitCast(ptr + 8, to: UnsafeMutablePointer<Double>.self).pointee) //22.0

ptr.deallocate()
ptr2.deallocate()
ptr3.deallocate()
  • 一般的类型转换,会根据数据类型变化改变原来的内存数据格式,如下:
// age存储的字节:0x00 00 00 00 00 00 00 0A
var age = 10

//age2存储的字节:0x01 AB 67,age2位浮点数,存储使用底层的科学计数法
var age2 = Double(age)
print(age)
print(age2)
  • unsafeBitCast是忽略数据类型的强制转换,不会因为数据类型的变化而改变原来的内存数据
//age3存储的字节:0x00 00 00 00 00 00 00 0A
var age3 = 10
// 由于unsafeBitCast不会根据数据类型变化改变原来的内存数据格式,所以age4存储的字节:0x00 00 00 00 00 00 00 0A与age3相同
var age4 = unsafeBitCast(age3, to: Double.self)
print(Mems.ptr(ofVal: &age3)) //age3内存地址0x0000000100559c58
print(Mems.ptr(ofVal: &age4)) //age4内存地址0x0000000100559c60
//age3和age4存储的8个字节一样 0x000000000000000a 存储的都是Int,只是解析的时候age4以浮点数的方式解析
  • 通过age3和age4的内存地址看存储的具体数据会发现是一样的,都是:0x000000000000000a,也就是整数10:

  • unsafeBitCast指向堆空间地址:
class Person { }
var person = Person()
//personObjectAddress存储的就是堆空间地址
var personObjectAddress = unsafeBitCast(person, to:UInt.self)
print(UnsafeRawPointer(bitPattern: personObjectAddress)!) 

下面的代码与上面的代码作用相同:

class Person { }
var person = Person()
var ptr = unsafeBitCast(person, to: UnsafeRawPointer.self)
print(ptr)

 

 

 

 

 

 

 

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值