[Swift]可选链和ARC引用计数

1. 可选链的作用:

    1) 经常会碰到这样的情形,就是在一个类中包含另一个类或结构体的实体,同时该实体中又包含其它实体,如果要访问最内层,则需要通过成员运算符.进行连续访问,这样就形成了一个链条,比如student.department.name等,这就形成了一个连续访问的链条;

    2) 根据实际情况,有时需要将这样的成员属性设置为可选类型?的,因为有些值在某些情况下有而在另一些情况下是不存在的,如果在这种情况下贸然访问最内存的成员可能会导致像C++那样的空指针异常的问题(比如p->member,但是p是NULL或者指向一个不存在的地方,这样member成员自然无法访问到),这种后果是无法估计的,幸好Swift要求所有可选类型在使用的时候必须用!进行拆包,如果nil拆包则会抛出运行时异常并且可以定位到异常的那个可选类型数据上,比如student.department!.name(因为一个学生如果是本科生可以没有部门),但是总是依赖运行时异常抛出将会很麻烦,这个调试带来很大的不便,那设想可不可以在发生nil访问异常的时候不抛出运行时异常而是让程序继续运行,只不过就是返回一个整个访问链返回一个nil即可,就凭这个nil进行一点简单的逻辑判断来排除异常。答案时肯定的,只要将访问链中的!改成?即可,而这样的访问链就是可选链,可选链顾名思义就是由可选变量通过?的方式连续访问形成的链条;

    3) 可选链的作用重申:就是为了防止由于属性、变量为nil时抛出异常(链条中的元素至少要有两个)

    4) 举例:

class A {
    var b_Class: B?
}

class B {
    var c_Class: C?
}

class C {
    func retD() -> D? {
        return D()
    }
}

class D {
    var s: String = "haha"
}

var a_Class: A?

if let s = a_Class?.b_Class?.c_Class?.retD()?.s {
    println("访问到了最内层的s = \(s)")
}
else { // 断了
    println("可选链中有nil,断开了!")
}

a_Class = A()
a_Class!.b_Class = B()
a_Class!.b_Class!.c_Class = C()

if let s = a_Class?.b_Class?.c_Class?.retD()?.s {
    println("访问到了最内层的s = \(s)") // 进入了最内层
}
else {
    println("可选链中有nil,断开了!")
}


2. Swift的引用计数原理:

    1) ARC即Automatic Reference Counting,自动引用计数的意思,即可看出ARC只对引用类型的类的对象进行管理;

    2) 和C++以及OC一样,所有的值类型的数据(结构体、基本类型、集合)都是保存在栈上,其添加和销毁都是由编译器自动产生相关的代码(汇编语言中实现过),而只有类的对象时保存在堆中,由于采用了自动化就不用像C++那样由人手动管理这部分内存的开辟和释放,其背后是采用ARC机制在运行时自动管理堆中的内存;

    3) RC:Reference Counter,即引用计数器,Swift会为每个创建的对象分配一个引用计数器,用于动态实时监控当前有多少引用指向该对象,当一个对象刚被创建时引用计数会为1,没多一个引用指向对象RC就+1,当一个引用脱离对象(将该引用设为nil或将该引用指向另一个不同的对象时)或某引用离开了自己的作用域而被销毁时相关对象的引用计数会-1,当RC为0时就会自动回收该对象的内存空间;

打个生动的比方,一个房间好比一个对象的内存空间,第一个人进入房间的时候打开灯,就好比创建了一个对象,此时引用计数为1,接着有人进入房间RC即不停++,有人离开房间就--,最后一个人离开房间RC就刚好减成0,对象被回收:

class A {
    var v: Int = 5
    deinit {
        println("destroy")
    }
}

var a1: A? = A() // RC = 1
var a2 = a1  // RC = 2
var a3 = a2  // RC = 3

a1 = nil // RC = 2
a2 = nil // RC = 1
a3 = nil // RC = 0同时输出析构信息destroy

var a = A().v // 虽然没有用引用指向该对象,但是用了临时对象来访问其中的数据域v,因此RC也计数并=1,只不过这个1是一个临时的对象引用,该引用就是A()的返回值
var b = 5 // 过了临时引用的作用域,RC = 0,该临时对象回收


3. 引用的类型:

    1) 引用分为三类:强引用、弱引用以及无主引用,其中强弱引用的概念和Shell中的强弱引用概念类似,其实只有强引用才能改变RC的值,后面两种不能改变RC的值但而只能通过引用访问对象的内容;

    2) 其中强引用就是上述以及之前经常碰见过的普通的引用,而弱引用需要用weak关键词修饰,而无主引用需要用unowned关键词来修饰;

    3) ARC引用弱引用以及无主引用最主要用来解决以下问题:

        i) 强引用循环:即一个对象中的某个域指向另一个对象,而另一个对象中的某个域又指向该对象而形成的一种循环引用的关系,即如果要释放一个对象必须要先释放另一个对象而形成死锁,请看下面的例子:

class A {
    var toB: B?
    deinit {
        println("destroy A")
    }
}

class B {
    var toA: A?
    deinit {
        println("destroy B")
    }
}

var a: A? = A()
var b: B? = B()
a!.toB = b // 1
b!.toA = a // 2

a = nil // 3
b = nil // 4

// 最后发现什么都没输出,即a和b都没有被销毁,所以内存泄露了!!!
原因很简单,就是执行完1和2以后,obj_a和obj_b的RC都等于2,执行完3时,仅仅是引用a和obj_a脱钩,但是obj_b中的toA域还连着obj_a,因此obj_a的RC仅仅-1而等于1但不等于0,因此obj_a不会释放,同样由于obj_a没释放,因此obj_a中的toB域仍然连接着obj_b,因此执行完4后obj_b的RC为1而不是0,因此obj_b也不释放,所以就造成内存泄露了!

**使用弱引用解决上述问题(弱引用只能用于可选类型的引用,即引用指向的对象可有可无):只需要在var toA之前加一个weak即可

class A {
    var toB: B?
    deinit {
        println("destroy A")
    }
}

class B {
    weak var toA: A?
    deinit {
        println("destroy B")
    }
}

var a: A? = A()
var b: B? = B()
a!.toB = b // 1
b!.toA = a // 2

a = nil // destroy A  3
b = nil // destroy B  4
// 这样就有输出信息了!
因为弱引用不对指向的对象的RC计数,因此执行完·时obj_b的RC = 2而执行完2时obj_a的RC = 1,因此执行3时obj_a的RC刚好减为1变成0,因此此时obj_a的空间被回收,所以obj_a中的toB域随着obj_a的回收被销毁,因此toB对obj_b的引用作用也消失,即在执行4之前obj_b的RC就已经减为1了,当执行4的时候obj_b的RC刚好减为0,因此obj_b也被成功销毁了!

**小结:解决循环引用的方法就是只给循环引用的一方(不是两方,只是一方)套上弱引用(或者接下来要将的无主引用),最后在释放时先释放被弱引用(或无主引用)指向的那个对象再释放另一个对象即可(顺序不能错!);

**无主引用:弱引用只能指向可选类型,而无主引用只能指向有值类型!如果用错编译会报错!

class A {
    var toB: B?
    deinit {
        println("destroy A")
    }
}

class B {
    unowned var toA: A
    init(obj_a: A) { // 由于toA是有值的,因此必须定义构造器
        toA = obj_a
    }
    deinit {
        println("destroy B")
    }
}

var a: A? = A()
var b: B? = B(obj_a: a!)
a!.toB = b

a = nil // destroy A
b = nil // destroy B

// 或者
a = A()
a!.toB = B(obj_a: a!) // obj_b只有一个obj_a内部的toB指向它,因此销毁a的同时b也被销毁了!
a = nil
// 直接输出destroy A和B
如果不用unowned,在销毁obj_a的同时由于obj_b中toA的指向是必须有值的,这样就会发生未知错误(程序崩溃)!但是如果试图访问已经被销毁的无主引用指向的的对象则程序会抛出运行时异常并定位,这总比直接崩溃要好得多了!

        ii) 由闭包引起的强引用循环:将闭包作为类的存储属性(闭包是一种特殊的引用类型),同时在闭包中访问本对象的属性(闭包中访问本对象属性必须要使用self,同时必须用lazy修饰!否则会报错!),这就相当于一个对象自己的属性指向自己的自循环,类似于以下代码:

class A {
    var toSelf: A?
    deinit {
        println("destroy")
    }
}
var a: A? = A()
a!.toSelf = a
a = nil // 同样不会析构,内存泄露,除非使用weak
下面来看一下具体的例子:

class A {
    var v: Int = 10
    lazy var f: () -> Int = { // 如果不加lazy或不适用self都会编译报错!
        return self.v
    }
    deinit {
        println("destroy")
    }
}

var a: A? = A()
println(a!.f()) // 生成f成员,其中a指向obj,同时obj自身的f中的临时的self也指向obj,因此RC = 2
a = nil // RC = 1,不析构,obj中的临时self还指向着obj,因此内存泄露!
要解决这个问题肯定还是得在闭包体中将self声明为weak或者unowned,最后只要释放外界的引用就可以释放整个对象了,定义方式如下:

{ [weak/unowned 捕获的对象引用] 正常的闭包定义体 },如果闭包的整个参数列表和返回类型都可以省略,则可以这样定义,{ [weak/unowned 捕获的对象引用] in 闭包语句 },

如果使用weak则表示捕获的对象是一个可选类型的,因此在闭包语句中使用该引用必须要用!强制拆包,如果使用unowned则表示捕获的对象是一定有值的,不能使用!拆包:

weak:

class A {
    var v: Int = 10
    lazy var f: () -> Int = { [weak self] in
        return self!.v // 可选类型需要拆包!
    }
    deinit {
        println("destroy")
    }
}

var a: A? = A()
println(a!.f())
a = nil // destroy
unowned:

class A {
    var v: Int = 10
    lazy var f: () -> Int = { [unowned self] in
        return self.v // 必定有值不得拆包!
    }
    deinit {
        println("destroy")
    }
}

var a: A? = A()
println(a!.f())
a = nil // destroy


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值