[翻译]Swift编程语言——自动引用计数

自动引用计数

Swift使用自动引用计数(Automatic Reference Counting (ARC))来跟踪和管理app的内存使用。多数情况下,这意味着内存管理由Swift处理,不需要思考如何管理内存。当实例不再被使用了,ARC自动释放它们使用的内存。

然而,少数情况下,ARC需要知道更多的代码片段之间的关系来为你管理内存。本章描述了这些情况并且展示如何让ARC管理你的app所有的内存。

NOTE
引用计数只对类实力适用。结构体和枚举是值类型的,不是引用类型的,不能用引用存储和传递。

ARC如何工作

每当创建一个类的实例,ARC给这个实例分配一块内存用来存储数据。这些内存会保存实例的信息,包括实例的所有存储属性。

此外,当一个实例不再被需要时,ARC会释放被这个实例使用的内存,所以那块内存可以被用作其他用途。这就确保了类实例在不需要时不会一直占据内存。

然而,如果ARC重新分配了一个仍然在使用的实例,那么那个类的实例属性和方法就不能被访问了。事实上,如果你视图访问这个实例,你的app会崩溃。

为了确保实例仍然在使用的时候不消失,ARC记录了当前有多少个属性、常量、和变量对每个类有引用。当一个实例没有一个有效的引用时,ARC将会再分配这个实例。

为了做到这些,每当将一个类实例赋值给一个属性、常量或者变量,这些属性、常量或者变量就创建了一个对这个实例的强引用(strong reference to the instance)。这个引用被叫做“强”引用,因为这个引用强持有那个类,当这个强引用存在时,不允许再分配。

ARC实战

这里有一个例子展示自动引用计数是如何工作的。这个例子开始定义了一个类叫做Person,它定义了一个存储常量属性叫做name:

​class​ ​Person​ {
​    ​let​ ​name​: ​String
​    ​init​(​name​: ​String​) {
​        ​self​.​name​ = ​name
​        ​println​(​"​\(​name​)​ is being initialized"​)
​    }
​    ​deinit​ {
​        ​println​(​"​\(​name​)​ is being deinitialized"​)
​    }
​}

Person有一个构造方法设置了实例的name属性然后打印了一条信息表示初始化是这样进行的。Person同时还有一个析构方法当类被释放时打印了一条信息。

接下来定义了三个变量的Person?类型,用来在后来的代码中给一个Person的新实例设置三个引用。因为这个三个变量是可选类型(Person?,不是Person),它们可以自动被nil初始化,而不是被一个Person的实例引用初始化。

var​ ​reference1​: ​Person​?
​var​ ​reference2​: ​Person​?
​var​ ​reference3​: ​Person​?

可以创建一个Person实例并将它复制给三个变量:

reference1​ = ​Person​(​name​: ​"John Appleseed"​)
​// prints "John Appleseed is being initialized"

在调用Person的构造方法时,信息“John Appleseed is being initialized”被打印。这样做是为了确保初始化过程执行了。

因为那个新的Person实例被赋值给了变量reference1,现在这里有了一个从reference1到Person的强引用。因为这里有至少一个的强引用,ARC就确保那个Person一直保存在内存中不被回收。

如果把相同的Person引用复制给另外两个变量,两个对那个实例的强引用被创建出来:

​reference2​ = ​reference1
​reference3​ = ​reference1

这里有三个强引用指向了这个唯一的Person实例。

如果通过赋值nil给变量的方式破坏掉其中两个强引用(包括最初的那个引用),唯一的一个强引用被保留下来,那个Person的实例就不会被回收:

​reference1​ = ​nil
​reference2​ = ​nil

ARC不会回收那个Person的实例,直到第三个也是最后一个强引用被破坏掉,此时不再需要的这个实例被清除掉:

​reference3​ = ​nil
​// prints "John Appleseed is being deinitialized"

类实例之间的强引用环

就像上面的例子,ARC可以跟踪你创建的新的Person实例的引用次数,可以在你不需要Person实例的时候释放它。

然而,可能会写出这样的代码,导致一个类的实例的引用永远不能达到对其的强引用为0的情况。在两个类实例相互持有对对方的强引用的时候就会出现,每个实例都让对方保持是活跃的。这就被称作强引用环(译者:strong reference cycle)。

解决强引用环可以通过定义类之间的弱引用或者无主引用替代强引用。这个过程在 解决类实例之间的强引用环 (Pesolving Stong Reference Cycles Between Class Instance )有描述。然而,在学习如果解决一个强引用环之前,有必要知道这个环是如何形成的。

这里有一个例子展示了如何意外创建了一个强引用换。这个例子定义了两个类分别叫做Person和Apartment(译者:公寓),模拟了一幢公寓和其中的居民:

​class​ ​Person​ {
​    ​let​ ​name​: ​String
​    ​init​(​name​: ​String​) { ​self​.​name​ = ​name​ }
​    ​var​ ​apartment​: ​Apartment​?
​    ​deinit​ { ​println​(​"​\(​name​)​ is being deinitialized"​) }
​}
​
​class​ ​Apartment​ {
​    ​let​ ​number​: ​Int
​    ​init​(​number​: ​Int​) { ​self​.​number​ = ​number​ }
​    ​var​ ​tenant​: ​Person​?
​    ​deinit​ { ​println​(​"Apartment #​\(​number​)​ is being deinitialized"​) }
​}

每个Person实例有一个String类型的name属性和一个可选的apartment属性(初始值是nil)。apartment属性是一个可选类型,因为一个人可能不是一直都会有一个公寓。

类似的,每个Apartment实例有一个Int类型的number属性和一个可选的tenant(译者:房客)属性(初始值是nil)。tenant属性是一个可选类型,因为一个公寓可能不是一直都会有房客。

这两个类都定义了一个析构方法,里面都是打印了类实例被销毁的信息。这样做便于查看Person和Apartment类实例是否按照预期被释放回收。

接下来的代码片段定义了两个变量可选类型分别叫做john和number73,它们两个接下来会被分别设置一个特定的Apartment和Person实例。这两个变量因为是可选类型,所以被初始化为nil:

​var​ ​john​: ​Person​?
​var​ ​number73​: ​Apartment​?

现在可以创建一个特定的Person实例和一个Apartment实例,然后赋值给john和number73变量:

​john​ = ​Person​(​name​: ​"John Appleseed"​)
​number73​ = ​Apartment​(​number​: ​73​)

下面展示了在接受了这两个实例的复制后的强引用是什么样的。john变量现在有一个强引用到一个新的Person实例,number73变量有一个强引用到一个新的Apartment实例:

现在可以将两个实例连接起来,这样一个人有了一套公寓,公寓有了一个房客。这里用叹号(!)拆包和访问了john和number73可选变量中存储的实例,所以这些实例的属性可以被设置:

john​!.​apartment​ = ​number73
​number73​!.​tenant​ = ​john

这里展示了再将两个实例连接起来后强引用是什么样子的:

很不幸,连接这两个实例创建了它们之间的强引用环。Person实例持有一个对Apartment实例的强引用,同时Apartment实例持有一个对Person实例的强引用。因此,当你破坏掉john和number73持有的强引用时,它们的引用记录不会掉到0,所以不会被ARC重新分配:

​john​ = ​nil
​number73​ = ​nil

在将两个变量都设置为nil时,两个类中没有任何一个析构被调用。强引用环阻止了Person和Apartment实例被回收,造成了app内存短缺。

这里展示了在设置john和number73变量都为nil后强引用的样子:

存在于Person实例和Apartment实例之间的强引用保留了下来而且不能被破坏掉。

解决类实例之间的强引用环

当处理类类型的属性时,Swift提供两种方式来解决强引用环:弱引用和无主引用。

弱引用和无主引用使得处于一个引用环中的一个实例可以引用另外一个实例而不会一直持有对其的强引用。实例间可以相互引用而不会产生强引用环。

在一个引用的生命周期内,如果它会成为nil,就使用弱引用。相反,如果知道一个引用在被初始化后就不会变成nil,就使用无主引用。

弱引用

弱引用是一个这样的引用:它不会一直保持对它引用的实例的强引用,并且不会阻止ARC释放引用的实例。这个行为阻止了引用成为强引用环的一部分。在一个属性或者变量声明前写上weak关键字就声明了一个弱引用。

如果一个引用在其声明周期内可能出现“没有值”的情况那么使用弱引用可以避免引用环出现。如果引用始终有值,使用一个无主引用,就像 无主引用(Unowned Reference)。在上面的Apartment例子中,在公寓的生命周期内,出现“没有房客”状态是合理的,所以那个例子中适合使用弱引用来破坏引用环。

NOTE
弱引用必须声明是变量,表示它的值在运行时可以修改。一个弱引用不能被声明为常量。

因为弱引用允许“没有值”,所以必须给把个弱引用定义为可选类型。在Swift中可选类型是表示“没有值”的首选方式。

因为一个弱引用不持续持有它所引用实例的强引用,这使得即使在一个弱引用仍然引用到这个实例时,这个实例可能被释放回收。因此,ARC自动会将一个弱引用设置为nil,当其引用的实例被回收时。需要检查弱引用的值是否存在,就像其他可选值一样,不会出现弱引用指向一个并不存在实例的引用的情况。

下面的例子是上面Person和Apartment的等价版本,但是有一处重要的不同。这一次,Apartment的tenant属性被声明为弱引用:

class​ ​Person​ {
​    ​let​ ​name​: ​String
​    ​init​(​name​: ​String​) { ​self​.​name​ = ​name​ }
​    ​var​ ​apartment​: ​Apartment​?
​    ​deinit​ { ​println​(​"​\(​name​)​ is being deinitialized"​) }
​}
​
​class​ ​Apartment​ {
​    ​let​ ​number​: ​Int
​    ​init​(​number​: ​Int​) { ​self​.​number​ = ​number​ }
​    ​weak​ ​var​ ​tenant​: ​Person​?
​    ​deinit​ { ​println​(​"Apartment #​\(​number​)​ is being deinitialized"​) }
​}

两个变量john和number73的强引用和两个实例之间的连接像前面一样被创建了:

​var​ ​john​: ​Person​?
​var​ ​number73​: ​Apartment​?
​
​john​ = ​Person​(​name​: ​"John Appleseed"​)
​number73​ = ​Apartment​(​number​: ​73​)
​
​john​!.​apartment​ = ​number73
​number73​!.​tenant​ = ​john

这里展示了已经连接在一起的两个实例的引用:

Person实例仍然有一个到Apartment实例的强引用,但是现在Apartment实例现在有的是一个到Person实例的弱引用。这意味着当你破坏john变量持有的引用,就不再有一个到Person实例的强引用了:

因为这里不再有一个对Person实例的强引用了,它会被回收:

john​ = ​nil
​// prints "John Appleseed is being deinitialized"

唯一一个保留下了的对Apartment实例的强引用来自number73变量。如果破坏了这个强引用,就不再有对Apartment实例的强引用了:

因为这里不再有对Apartment实例的引用了,所以它会被回收:

​number73​ = ​nil
​// prints "Apartment #73 is being deinitialized"

上面最后面两个代码段展示了在john和number73变量被设置为nil之后,Person和Apartment实例的析构方法被调用打印出了它们各自的析构信息。

无主引用(Unowned Reference)

像弱引用,一个无主引用(unowned reference)也不保持它所引用的实例的强引用。但是,不同于弱引用,无主引用被赋予的都是有值的内容。因此,一个无主引用通常被作为一个不可选类型被定义。在一个属性或者变量定义前书写unowned关键字就表明了它们是无主引用。

因为无主引用时不可选择类型,所以每次使用时无需拆包一个无主引用。一个无主引用可以直接访问。但是,当需要回收一个实例时,ARC不会设置指向这个实例的无主引用为nil,因为一个不可选的变量不能被设置为nil。

NOTE
如果在实例被释放后试图访问指向这个实例的无主引用,会触发一个运行时错误。只有当确信引用始终指向一个实例才能使用无主引用。
在一个无主引用指向的实例被释放后试图访问这个引用一定会导致app崩溃。这种情况没有例外。尽管会阻止这种情况发生,但是如果发生了app必然会崩溃。

下面例子定义了两个类叫做Customer和CreditCard,它们模拟了一个银行的用户和用户可能拥有的信用卡。这两个类相互存储了对方的实例作为属性。这种关系有了产生一个强引用环的可能。

Customer和CreditCard的关系与Apartment和Person(上面采用了弱引用的例子)的关系有少许不同。这个模型中,一个用户可能有也可能没有信用卡,但是一个信用卡总会有一个拥有着。为了表现这个,Customer类有一个可选的card属性,而CreditCard类有一个不可选的customer属性。

更进一步,一个新的CreditCard类实例只能通过传入一个number数字和一个customer实例定制创建。这就确保了在CreditCard实例的创建阶段一个CreditCard实例总会有一个customer实例。

因为一个信用卡总会有一个拥有者,可以定义它的customer属性为一个无主引用,用来避免出现强引用环:

class​ ​Customer​ {
​    ​let​ ​name​: ​String
​    ​var​ ​card​: ​CreditCard​?
​    ​init​(​name​: ​String​) {
​        ​self​.​name​ = ​name
​    }
​    ​deinit​ { ​println​(​"​\(​name​)​ is being deinitialized"​) }
​}
​
​class​ ​CreditCard​ {
​    ​let​ ​number​: ​UInt64
​    ​unowned​ ​let​ ​customer​: ​Customer
​    ​init​(​number​: ​UInt64​,​customer​: ​Customer​) {
​        ​self​.​number​ = ​number
​        ​self​.​customer​ = ​customer
​    }
​    ​deinit​ { ​println​(​"Card #​\(​number​)​ is being deinitialized"​) }
​}

NOTE
CreditCard类的number属性用UInt64而不是Int定义,确保number属性的容量能够存储得下一个16位的卡号,无论是在32位还是64位系统上。

下面的代码片段定义了一个可选Customer变量叫做john,它将被用来存储一个特定用户的引用。通过使用选择类型定义,这个变量有默认的nil:

​var​ ​john​: ​Customer​?

现在可以创建一个Customer实例了,然后使用它来初始化CreditCard类实例,将这个CreditCard类实例赋值给用户的card属性:

john​ = ​Customer​(​name​: ​"John Appleseed"​)
​john​!.​card​ = ​CreditCard​(​number​: ​1234_5678_9012_3456​,​customer​: ​john​!)

这里展示了将两个类连接后引用的情况:

Customer实例有一个现在有一个强引用到CreditCard类实例,而CreditCard实例有一个无主引用到Customer实例。

因为无主引用customer,当破坏掉john变量持有的强引用后,就不再有对于Customer实例的强引用了:

因为不再有到Customer实例的强引用了,所以Customer类实例被回收了。这些发生后,就不再存在到CreditCard实例的强引用了,所以CreditCard实例也被回收了:

john​ = ​nil
​// prints "John Appleseed is being deinitialized"
​// prints "Card #1234567890123456 is being deinitialized"

上面最后的代码片段展示了Customer实例和CreditCard实例的析构方法在john变量被设置为nil后都执行了,打印出了析构信息。

无主引用和静默拆包可变属性

上面的弱引用和无主引用的例子涉及到了两个很普通的需要破除强引用环的场景。

Person和Apartment的例子展示了两个可以为nil的属性有构成强引用环的可能。这种情况最适合采用弱引用。

Customer和CreditCard的例子展示了一个属性可以为nil但另外一个属性却不可以,它们有构成强引用环的可能。这种情况最适合采用无主引用。

但是,这里有第三种情况,两个属性都始终有值,一旦初始化完成,两个属性都不会成为nil。这种情况,可以将一个类中的无主属性和另一个类中的隐式解包可选属性结合起来。

这样使得一旦初始化完成了,两个属性都可以被直接访问(无需可选类型解包),同时可以避免引用环出现。这一节会展示具体的操作。

下面例子定义了两个类叫做Country和City,它们每个都相互将对方类实例作为自己的属性存储。在这个数据模型中,每个国家必须要有一个首都而每个城市必须属于一个国家。为了表现这些,Country类有一个capitalCity属性而City类有一个country属性:

class​ ​Country​ {
​    ​let​ ​name​: ​String
​    ​let​ ​capitalCity​: ​City​!
​    ​init​(​name​: ​String​,​capitalName​: ​String​) {
​        ​self​.​name​ = ​name
​        ​self​.​capitalCity​ = ​City​(​name​: ​capitalName​,​country​: ​self​)
​    }
​}
​
​class​ ​City​ {
​    ​let​ ​name​: ​String
​    ​unowned​ ​let​ ​country​: ​Country
​    ​init​(​name​: ​String​,​country​: ​Country​) {
​        ​self​.​name​ = ​name
​        ​self​.​country​ = ​country
​    }
​}

为了实现两个类之间的关系,City的构造方法接受一个Country实例作为参数,并且将其作为country属性存储。

City的构造方法在Country构造方法内被调用。但是,Country类构造方法不能将selft传递给City的构造方法,直到一个新的Country类实力被完整的初始化后,就像 初始化的两个阶段(Two-Phase Initialization)描述的一样。

为了达到这个要求,将Country的capitalCity属性定义为了一个静默拆包的可选类型属性, 具体就是在类型说明后添加叹号(City!)。这意味着capitalCity属性有一个默认值nil,像其他可选类型一样,而且可以无需拆包直接访问,就像 静默拆包可选类型(Implicitly Unwrapped Optionals)描述的一样。

因为capitalCity有一个默认的nil值,所以一旦在构造方法中Country属性的name属性被设置了,这个Country实例就被认为已经完全初始化了。这意味着在name属性被设置过后,可以引用或者分配默认的self属性了。因此,在Country构造方法要设置它的capitalCity属性时,Country构造方法可以将self作为参数传递给City构造方法。

所有这些意味着可以在一条语句中创建一个Country和一个City实例,避免产生一个强引用环,capitalCity可以被直接访问,不需要使用叹号做可选值的解包:

​var​ ​country​ = ​Country​(​name​: ​"Canada"​,​capitalName​: ​"Ottawa"​)
​println​(​"​\(​country​.​name​)​'s capital city is called ​\(​country​.​capitalCity​.​name​)​"​)
​// prints "Canada's capital city is called Ottawa"

上面的例子中,使用静默解包可选类型意味着类初始两个阶段的所有要求都满足。一旦完成初始化,capitalCity属性可以像非可选类型值一样访问,同时避免了出现强引用环。

闭包的强引用环

上面讲到了当两个类实例相互持有对对方的强引用,就会产生一个强引用环。也讲到了如何使用弱引用和无主引用破坏这些强引用环。

在将一个闭包复制给一个类实例的属性时,也会产生强引用环,闭包体捕获了那个实例。因为闭包体访问了那个类的属性(比如self.someProperty),所以这种捕获会发生。或者闭包调用了那个类实例的方法(比如slef.someMethod())也会触发捕获。以上任何一种情况都会触发闭包“捕获”self,产生一个强引用环。

这种强引用环产生是因为闭包,类,都是引用类型的。当把一个闭包赋值给一个属性,也会将一个引用复制给闭包。本质上,这和上面的问题一样——两个强引用使得对方都是活跃的。不同的是不是两个类实例而是一个类实例和一个闭包。

Swift提供了一个简洁的解决这个问题的方案。叫做闭包捕获列表(closure capture list)。然而,在学会如果破坏一个强引用环之前,理解这种引用环是如何形成的也是很有帮助的。

下面例子展示了当时用闭包时引用了self怎么会造成一个强引用环。这个例子定义了一个雷叫做HTMLElement,提供了一个HTML文档独立元素的简单模型:

class​ ​HTMLElement​ {
​    
​    ​let​ ​name​: ​String
​    ​let​ ​text​: ​String​?
​    
​    ​lazy​ ​var​ ​asHTML​: () -> ​String​ = {
​        ​if​ ​let​ ​text​ = ​self​.​text​ {
​            ​return​ ​"<​\(​self​.​name​)​>​\(​text​)​</​\(​self​.​name​)​>"
​        } ​else​ {
​            ​return​ ​"<​\(​self​.​name​)​ />"
​        }
​    }
​    
​    ​init​(​name​: ​String​,​text​: ​String​? = ​nil​) {
​        ​self​.​name​ = ​name
​        ​self​.​text​ = ​text
​    }
​    
​    ​deinit​ {
​        ​println​(​"​\(​name​)​ is being deinitialized"​)
​    }
​    
​}

HTMLElement类定义了一个name属性,表示元素的名称,比如“p”表示段落元素,“br”表示新行元素。HTMLElement同时也定义了一个可选的text属性,可以设置一个字符串给它表现这个字符串在那个HTML元素中被渲染。

在这两个简单属性基础上,HTMLElement类定义了一个惰性属性叫做asHTML。这个属性引用了一个闭包,闭包会将name和text组合为一个HTML字符串片段。asHTML属性是一个() -> String类型的(一个没有参数有一个String返回值的方法)。

默认的,asHTML属性被分配了一个闭包,这个闭包返回表示一个HTML标签的字符串。这个标签包含了一个可选的text值(如果存在这样的值)。以段落元素为例,闭包将会返回“

some text

”或者“

”,这取决于text属性是否是nil。

asHTML属性的命名和使用有些像一个实例方法。然而,因为asHTML是一个闭包属性而不是实例方法,所以如果你想改变特定HTML元素的渲染,可以用一个定制的闭包替换asHTML属性的默认值。

NOTE
asHTML属性被定义为了一个惰性属性,因为它只在元素实际需要被作为HTML标签渲染时才会需要这个属性。asHTML是一个惰性属性的实质是可以在默认的闭包中引用self,因为惰性属性直到在初始化完成后而且self可以访问了才会被访问。

HTMLElement类提供了一个构造方法,这个构造方法带一个name参数和一个text参数(如果需要的话)。这个类同样定义了一个析构方法,在一个HTMLElement实例被回收时,它会打印信息。

这里展示了如何使用HTMLElement类来创建和打印一个新的实例:
var​ ​paragraph​: ​HTMLElement​? = ​HTMLElement​(​name​: ​”p”​,​text​: ​”hello,world”​)
​println​(​paragraph​!.​asHTML​())
​// prints “

hello,world

NOTE
上面定义的paragraph变量是一个可选HTMLElement类型,所以下面它可以被设置为nil,来证实一个强引用环的存在。

很不幸,上面定义的HTMLElement类,创建了一个在HTMLElement实例和它的asHTML属性默认值(一个闭包)之间的强引用环。下面展示了这个强引用环:

实例的asHTML属性保持了一个到闭包的强引用。同时,因为闭包在闭包体内引用了self(引用self.name和self.text),闭包捕获了self,这意味着闭包持有了一个到HTMLElement实例的强引用。两者之间形成了一个强引用环。(更多的关于捕获值的信息)参见 捕获值 Capturing Values。

NOTE
尽管闭包多次引用了self,但它只捕获了HTMLElement实例的一个强引用。

如果设置paragraph变量为nil破坏了它到HTMLElement实例的一个强引用,HTMLElement实例和闭包都没有被回收,因为一个强引用环出现了:
​paragraph​ = ​nil

注意,HTMLElemnet的析构方法没有打印信息,表示HTMLElement实例没有被释放。

解决闭包的强引用环

通过在闭包定义中添加捕获列表可以解决闭包和一个类实例之间的强引用环。一个捕获列表定义了在闭包体内捕获到一个或多个引用类型后使用它们的规则。以两个类实例之间的强引用环为例,定义相互捕获的引用为一个弱引用或者无主引用而不是强引用。弱引用还是无主引用的选择依赖不同代码之间的关系。

NOTE
在闭包中引用selft的成员时,Swift需要采用self.somProperty或者self.someMethod(而不是someProperty或者someMethod)。这样做即便意外捕获了self,也不会忘记。

定义一个捕获列表

每个捕获列表的项是一对weak或者unowned关键字和一个类引用(比如self或者someInstance)。这一对内容需要写在一对方括号之间,每一对之间用逗号分隔。

如果有捕获列表那么将捕获列表放置在一个闭包的参数列表和返回值前:

lazy​ ​var​ ​someClosure​: (​Int​,​String​) -> ​String​ = {
​    [​unowned​ ​self​] (​index​: ​Int​,​stringToProcess​: ​String​) -> ​String​ ​in
​    ​// closure body goes here
​}

如果一个闭包没有指定参数列表或者返回类型(因为通过上下文可以推断出来),那么将捕获列表放置在闭包的最开始,后面跟一个in关键字:

​lazy​ ​var​ ​someClosure​: () -> ​String​ = {
​    [​unowned​ ​self​] ​in
​    ​// closure body goes here
​}

弱引用和无主引用

当闭包和它捕获的引用会一直引用对方时,在闭包内定义一个无主引用,闭包和实例会被一同回收。

相反,当被捕获的引用可能会成为nil时,定义一个弱引用。无哦引用始终是一个可选类型,当弱引用引用的实例被释放,弱引用会自动变成nil。这就需要在闭包体内检查弱引用是否存在。

NOTE
如果被捕获的引用不会变成nil,它适合被作为无主引用捕获而不是作为弱引用。

前面的HTMLElement例子中,适合采用捕获无主引用来解决强引用环。下面展示了如何改写HTMLElement来避免强引用环出现:

class​ ​HTMLElement​ {
​    
​    ​let​ ​name​: ​String
​    ​let​ ​text​: ​String​?
​    
​    ​lazy​ ​var​ ​asHTML​: () -> ​String​ = {
​        [​unowned​ ​self​] ​in
​        ​if​ ​let​ ​text​ = ​self​.​text​ {
​            ​return​ ​"<​\(​self​.​name​)​>​\(​text​)​</​\(​self​.​name​)​>"
​        } ​else​ {
​            ​return​ ​"<​\(​self​.​name​)​ />"
​        }
​    }
​    
​    ​init​(​name​: ​String​,​text​: ​String​? = ​nil​) {
​        ​self​.​name​ = ​name
​        ​self​.​text​ = ​text
​    }
​    
​    ​deinit​ {
​        ​println​(​"​\(​name​)​ is being deinitialized"​)
​    }
​    
​}

这个版本的HTMLElement实现除了在asHTML闭包中添加的捕获列表之外和前面的实现完全相同。例子中,捕获列表是[unowned self],它的意思是“捕获self作为一个无主引用而不是强引用”。

下面可以向之前一样创建和打印一个HTMLElement实例:

​var​ ​paragraph​: ​HTMLElement​? = ​HTMLElement​(​name​: ​"p"​,​text​: ​"hello,world"​)
​println​(​paragraph​!.​asHTML​())
​// prints "<p>hello,world</p>"

这里展示了采用捕获列表后的引用情况:

这次,被闭包捕获的self是一个无主引用,不会一直持有对它捕获的HTMLElement实例的应用。如果设置paragraph变量的强引用为nil,HTMLElement实例被释放,因为可以看到它的析构方法中的打印信息:

​paragraph​ = ​nil
​// prints "p is being deinitialized"

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘贴.待开发的功能:1.支持自动生成约束2.开发设置页面3.做一个浏览器插件,支持不需要下载整个工程,可即时操作当前蓝湖浏览页面4.支持Flutter语言模板生成5.支持更多平台,如Sketch等6.支持用户自定义语言模板
现实生活中,我们听到的声音都是时间连续的,我们称为这种信号叫模拟信号。模拟信号需要进行数字化以后才能在计算机中使用。目前我们在计算机上进行音频播放都需要依赖于音频文件。那么音频文件如何生成的呢?音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号的过程,我们人耳所能听到的声音频率范围为(20Hz~20KHz),因此音频文件格式的最大带宽是20KHZ。根据奈奎斯特的理论,音频文件的采样率一般在40~50KHZ之间。奈奎斯特采样定律,又称香农采样定律。...............
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿遍又亿遍,久久不能离开!看着小仙紫姐姐的蹦迪视频,除了一键三连还能做什么?突发奇想,能不能把舞蹈视频转成代码舞呢?说干就干,今天就手把手教大家如何把跳舞视频转成代码舞,跟着仙女姐姐一起蹦起来~视频来源:【紫颜】见过仙女蹦迪吗 【千盏】一、核心功能设计总体来说,我们需要分为以下几步完成:从B站上把小姐姐的视频下载下来对视频进行截取GIF,把截取的GIF通过ASCII Animator进行ASCII字符转换把转换的字符gif根据每
【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至2022年4月底。我已经将这篇博客的内容写为论文,上传至arxiv:https://arxiv.org/pdf/2204.10160.pdf欢迎大家指出我论文中的问题,特别是语法与用词问题在github上,我也上传了完整的项目:https://github.com/Whiffe/Custom-ava-dataset_Custom-Spatio-Temporally-Action-Video-Dataset关于自定义ava数据集,也是后台
因为我既对接过session、cookie,也对接过JWT,今年因为工作需要也对接了gtoken的2个版本,对这方面的理解还算深入。尤其是看到官方文档评论区又小伙伴表示看不懂,所以做了这期视频内容出来:视频在这里:本期内容对应B站的开源视频因为涉及的知识点比较多,视频内容比较长。如果你觉得看视频浪费时间,可以直接阅读源码:goframe v2版本集成gtokengoframe v1版本集成gtokengoframe v2版本集成jwtgoframe v2版本session登录官方调用示例文档jwt和sess
【Android App】实战项目之仿微信的私信和群聊App(附源码和演示视频 超详细必看)
用Android Studio的VideoView组件实现简单的本地视频播放器。本文将讲解如何使用Android视频播放器VideoView组件来播放本地视频和网络视频,实现起来还是比较简单的。VideoView组件的作用与ImageView类似,只是ImageView用于显示图片,VideoView用于播放视频。...
采用MATLAB对正弦信号,语音信号进行生成、采样和内插恢复,利用MATLAB工具箱对混杂噪声的音频信号进行滤波
随着移动互联网、云端存储等技术的快速发展,包含丰富信息的音频数据呈现几何级速率增长。这些海量数据在为人工分析带来困难的同时,也为音频认知、创新学习研究提供了数据基础。在本节中,我们通过构建生成模型来生成音频序列文件,从而进一步加深对序列数据处理问题的了解。
基于yolov5+deepsort+slowfast算法的视频实时行为检测。1. yolov5实现目标检测,确定目标坐标 2. deepsort实现目标跟踪,持续标注目标坐标 3. slowfast实现动作识别,并给出置信率 4. 用框持续框住目标,并将动作类别以及置信度显示在框上
数字电子钟设计本文主要完成数字电子钟的以下功能1、计时功能(24小时)2、秒表功能(一个按键实现开始暂停,另一个按键实现清零功能)3、闹钟功能(设置闹钟以及到时响10秒)4、校时功能5、其他功能(清零、加速、星期、八位数码管显示等)前排提示:前面几篇文章介绍过的内容就不详细介绍了,可以看我专栏的前几篇文章。PS.工程文件放在最后面总体设计本次设计主要是在前一篇文章 数字电子钟基本功能的实现 的基础上改编而成的,主要结构不变,分频器将50MHz分为较低的频率备用;dig_select
1.进入官网下载OBS stdioOpen Broadcaster Software | OBS (obsproject.com)2.下载一个插件,拓展OBS的虚拟摄像头功能链接:OBS 虚拟摄像头插件.zip_免费高速下载|百度网盘-分享无限制 (baidu.com)提取码:6656--来自百度网盘超级会员V1的分享**注意**该插件必须下载但OBS的根目录(应该是自动匹配了的)3.打开OBS,选中虚拟摄像头选择启用在底部添加一段视频录制选择下面,进行录制.
Meta公司在9月29日首次推出一款人工智能系统模型:Make-A-Video,可以从给定的文字提示生成短视频。基于**文本到图像生成技术的最新进展**,该技术旨在实现文本到视频的生成,可以仅用几个单词或几行文本生成异想天开、独一无二的视频,将无限的想象力带入生活
音频信号叠加噪声及滤波一、前言二、信号分析及加噪三、滤波去噪四、总结一、前言之前一直对硬件上的内容比较关注,但是可能是因为硬件方面的东西可能真的是比较杂,而且需要渗透的东西太多了,所以学习进展比较缓慢。因为也很少有单纯的硬件学习研究,总是会伴随着各种理论需要硬件做支撑,所以还是想要慢慢接触理论学习。但是之前总找不到切入点,不知道从哪里开始,就一直拖着。最近稍微接触了一点信号处理,就用这个当作切入点,开始接触理论学习。二、信号分析及加噪信号处理选用了matlab做工具,选了一个最简单的语音信号处理方
腾讯云 TRTC 实时音视频服务体验,从认识 TRTC 到 TRTC 的开发实践,Demo 演示& IM 服务搭建。
音乐音频分类技术能够基于音乐内容为音乐添加类别标签,在音乐资源的高效组织、检索和推荐等相关方面的研究和应用具有重要意义。传统的音乐分类方法大量使用了人工设计的声学特征,特征的设计需要音乐领域的知识,不同分类任务的特征往往并不通用。深度学习的出现给更好地解决音乐分类问题提供了新的思路,本文对基于深度学习的音乐音频分类方法进行了研究。首先将音乐的音频信号转换成声谱作为统一表示,避免了手工选取特征存在的问题,然后基于一维卷积构建了一种音乐分类模型。
C++知识精讲16 | 井字棋游戏(配资源+视频)【赋源码,双人对战】
本文主要讲解如何在Java中,使用FFmpeg进行视频的帧读取,并最终合并成Gif动态图。
在本篇博文中,我们谈及了 Swift 中 some、any 关键字以及主关联类型(primary associated types)的前世今生,并由浅及深用简明的示例向大家讲解了它们之间的奥秘玄机。