• Swift15-自动引用计数


    自动引用计数的工作机制、自动引用计数实践、类实例之间的循环强引用、解决实例之间的循环强引用、闭包引起的循环强引用、解决闭包引起的循环强引用。

    Swift使用自动引用计数(ARC)机制来跟踪和管理应用程序的内存。通常情况下,Swift内存管理机制会一直起作用,无须人工考虑内存的管理。ARC会在类的实例不再被使用时,自动释放其占用的内存。

    然而在少数情况下,为了能帮助管理内存,ARC需要更多的代码之间关系的信息。本章描述了这些情况,并且示范怎样才能使ARC来管理应用程序的所有内存。

    注意:引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。

    自动引用计数的工作机制

    当每次创建一个类的新的实例的时候,ARC会分配一块内存来存储该实例信息。内存中会包含实例的类型信息,以及这个实例所有相关的存储型属性的值。

    此外,当实例不再被使用时,ARC释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的实例,不会一直占用内存空间。

    当ARC收回和释放了正在被使用中的实例,该实例的属性和方法将不再被访问和调用。实际上,如果试图访问这个实例,应用程序很可能会崩溃。

    为了确保使用中的实例不会被销毁,ARC会跟踪和计算每一个实例正在被多少属性、常量和变量所引用。哪怕实例的引用数为1,ARC都不会销毁这个实例。

    为了使上述成为可能,无论将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢地保持住,只要强引用还在,实例是不允许被销毁的。

    自动引用计数实践:

    类实例之间的循环强引用:

    ARC会跟踪实例的引用数量,并且会在实例不再需要时销毁它。

    然而,我们可能会写出一个类实例的强引用数永远不能变成0的代码。如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在。这就是所谓的循环强引用。

    可以通过定义类之间的关系为弱引用或无主引用,以替代强引用,从而解决循环强引用的问题

    class A{

      var a:B?

    }

    class B{

      var b:A?

    }

    var ta:A?=A()

    var tb:B?=B()

    ta!.a=tb

    tb!.b=ta

    //这时当ta和tb不再使用时,它们的强引用计数都是2

    ta=nil

    tb=nil

    //ta和tb两个实例关联后产生一个循环强引用。因此,当断开ta和tb变量所持有的强引用时,引用计数并不会降为0,实例也不会被ARC销毁。

    //即将ta赋值为空,对ta所引用的实例强引用计数减1为1,该实例不会销毁,所以该实例的a属性不会变为空就不会对ta所引用的实例作一个强引用计数减1操作。

    //将两个变量设为nil时,没有任何一个析构函数被调用。循环强引用会一直阻止两个实例的销毁,造成了内存泄露。

    解决实例之间的循环强引用:

    Swift提供了两种方法来解决在使用类的属性时所遇到的循环强引用问题:弱引用和无主引用。

    弱引用和无主引用允许循环引用中的一个实例引用而另外一个实例不保持强引用。这样实例能够互相引用而不产生循环强引用。

    当其它的实例有更短的生命周期时,使用弱引用,也就是说,当其它实例析构在先时使用弱引用。

    弱引用

    弱引用不会对其引用的实例保持强引用,因而不会阻止ARC销毁被引用的实例。这阻止了引用变为循环强引用。声明属性或者变量时,在前面加上weak关键字表明这是一个弱引用。

    因为弱引用不会保持所引用的实例,即使弱引用存在,实例也可能被销毁。因此,ARC会在引用的实例被销毁后自动将其(弱引用)赋值为nil。并且因为弱引用可以允许它们的值在运行时被赋值为nil,所以它们会被定义为可选类型变量,而不是常量。

    可以像其它可选值一样,检查弱引用的值是否存在,不访问已销毁的实例的引用。

    注意:当ARC设置弱引用为nil时,属性观察不会被触发。

    class A{

      var a:B?

    }

    class B{

      weak var b:A?

    }

    var ta:A?=A()  //创建一个A实例,强引用计数为1

    var tb:B?=B()  //创建一个B实例,强引用计数为1

    ta.a=tb  //将ta所引用实例的a属性引用到tb,B实例强引用计数为2

    tb.b=ta  //将tb所引用实例的b属性引用到ta,A实例强引用计数仍为1,因为属性b是弱引用

    ta=nil  //将ta赋值为空,A实例强引用计数减1为0,A实例销毁。所以A实例的a属性变为空,B实例强引用计数减1,并且B实例的b属性自动变为nil空

    tb=nil   //将tb赋值为空,B实例强引用计数减1为0,B实例销毁

    在ARC中,一旦值的最后一个强引用被移除,就会被立即销毁。

    无主引用:

    和弱引用类似,无主引用不会牢牢保持住引用的实例。和弱引用不同的是,无主引用在其它实例有相同或者更长的生命周期时使用(弱引用用于其它实例有较短的生命周期)可以在声明属性或者变量时,在前面加上关键字unowned表示这是一个无主引用。

    无主引用通常都被期望拥有值。(非可选类型,不能为空,所以它所在实例的生命周期比它所引用实例的生命周期较短)。不过ARC无法在实例被销毁后将无主引用设为nil,因为非可选类型的变量不允许被赋值为nil。

    重要:使用无主引用,必须确保引用始终指向一个未销毁的实例。如果试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。

    class A{

      var a:B?

    }

    class B{

      unowned var b:A

      init(_ m:A){

        b=A

      }

    }

    var ta=A?

    ta=A()

    ta.a=B(ta)

    ta=nil

    //ta是A类型的可选类型,为ta赋值一个实例,该实例强引用为1

    //ta引用实例后,可以通过ta将该实例被一个B类型实例的b无主引用,并将ta的a强引用B(ta)

    //此时ta所指实例的强引用数为1,还有一个无主引用;B(ta)实例的强引用数为1

    //将ta赋值为空,ta所指实例的强引用计数变为0所以被销毁(之前的循环强引用问题就是将可选变量赋值为nil时并未对其所指实例销毁,而实例的指向另一实例的属性就还存在)。其后,也没有指向B(ta)实例的强引用,该实例也随之被销毁了。

    class A{

      var a:B?

    }

    class B{

      unowned var b:A

      //它的b属性不是可选类型,所以必须有一个原始值或构造器给b赋值

      init(_ b:A){ self.b=b}

    }

    var ta:A?

    var tb:B?

    ta=A()  //创建A实例,强引用计数为1

    tb=B(ta)  //创建B实例,B实例强引用计数为1。而且B的构造器将b属性无主引用到A,不会增加A的强引用计数 

    ta.a=tb  //将A实例的a属性引用到B实例,增加B实例的强引用计数到2

    tb=nil  //解除tb对B实例的强引用,B实例强引用计数变为1.此时B实例的b属性是可访问的

    ta=nil  //解除ta对A实例的强引用,A实例的强引用计数变为0被销毁。A实例的a属性为空,a属性指向的B实例强引用计数减1为0,B实例销毁

    //先将带有无主引用类型属性实例赋予的变量tb赋值为空,tb声明周期结束(但其引用的实例生命周期未结束),不能通过tb访问其属性。之后将ta赋值为空,销毁ta引用的A实例,原tb引用的B实例也被销毁。

    无主引用以及隐式解析可选属性

    当循环强引用的两方A/B的两个属性a/b都为可选类型时,使用弱引用来解决。

    当循环强引用的两方A/B的两个属性a/b一个是可选类型另一个是非可选类型时,使用无主引用解决。

    还有一种场景:两方A/B的属性a/b都必须有值,且初始化完成后永远不会为nil。

    这时需要一个类使用无主属性,而另一个类使用隐式解析可选属性。

    这使两个属性在初始化完成后能被直接访问(不需要可选展开),同时避免了循环引用。

    class A{

      var a:B!  //隐式解析可选类型与可选类型相似,但不需要在访问时加上感叹号就能访问。所以在初始化后不能为nil空

      init(){

        self.a=B(self)  //将self传入B的构造器中构造一个实例,并将自身的隐式解析可选类型指向该实例,增加了实例m的强引用计数

      }

    }

    class B{

      unowned var b:A

      init(_ n:A){  //根据传入的A实例,将自身的无主引用类型b指向该实例,不增加该实例n的强引用计数

        self.b=n

      }

    }

    var c=A()  //创建A实例,A实例强引用计数为1,A的构造器创建了一个B实例,该B实例强引用计数为1;B实例的构造器中将B实例的无主引用b引用到A实例,A实例强引用计数为1

    //c的生命周期结束,A实例强引用计数减1为0,A被销毁;在A的销毁过程中,A实例的a属性变为空,所以B实例强引用计数减1为0,B实例销毁

    以上的意义在于可以通过一条语句同时创建A和B的实例,而不产生循环强引用。并且A的a属性能被直接访问,而不需要通过感叹号来展开它的可选值。

    闭包引起的循环强引用:

    循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时。这个闭包体中可能访问了实例的某个属性self.someProperty,或者闭包中调用了实例的某个方法self.someMethod()。这两种情况都导致了闭包“捕获”self,从而产生了循环强引用。

    循环强引用的产生,是因为闭包和类相似,都是引用类型当把一个闭包赋值给某个属性时,是将这个闭包的引用赋值给了属性

    给类属性赋值一个闭包,闭包中使用了调用该闭包的此类实例时,就有一个循环强引用。类实例强引用计数2,分别是构造实例赋值的变量/常量和它的某个属性的闭包中的变量/常量。

    闭包中访问实例,如用self点语法,因为闭包是引用类型,所以闭包会对self所指对象加一个强引用。

    Swift提供了一种方法来解决这个问题,称之为闭包捕获列表

    class A{

      let text:String?

      lazy var name:Void->String={

        if let text=self.text{  //闭包是引用类型,闭包中访问的是对原对象的一个引用。访问self,即对self对象加一个强引用

          return "(text)"

        }else{

          return "(text),(text)"

        }

      }

      init(....

      deinit(...

    }

    闭包中可以多次使用self,但只引用同一个实例对象,即只会对强引用计数加1。

    解决闭包引起的循环强引用:

    定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。

    定义捕获列表:

    捕获列表中的每一项都由一对元素组成,一个元素是weak或unowned关键字,另一个元素是类实例的引用(例如self)或初始化过的变量(如a=self.a)。这些项在方括号中用逗号分开。

    如果闭包有参数列表和返回类型,把捕获列表放在它们前面

    lazy var someClosure:(Int,String)->String={

      [unowned self, weak delegate=self.delegate!](index:Int, stringToProcess:String)->String in

      //闭包的函数体

    }

    如果闭包没有指明参数列表或者返回类型,即它们会通过上下文推断,那么可以把捕获列表和关键字in放在闭包最开始的地方:

    lazy var someClosure:Void->String={

      [unowned self, weak delegate=self.delegate!] in

      //闭包函数体

    }

    弱引用和无主引用:

    在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为无主引用

    相反,在被捕获的引用可能会变为nil时,将闭包内的捕获定义为弱引用。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动设置为nil。这使我们可以在闭包体内检查它们是否存在。

  • 相关阅读:
    POJ_1485_dp
    POJ_1376_bfs
    [noi1994]海盗
    [noi1755]Trie
    [luogu3733]八纵八横
    [noi1774]array
    [noi1773]function
    [noi1754]SA
    [noi1779]D
    [bzoj4873]寿司餐厅
  • 原文地址:https://www.cnblogs.com/cjj-ggboy/p/12853745.html
Copyright © 2020-2023  润新知