Swift里你可能不知道的事儿(3)——处理closure和类对象之间的reference cycle

Swift里你可能不知道的事儿(3)——处理closure和类对象之间的reference cycle,第1张

概述泊学高清视频 泊阅文档 我们来了解一下这类reference cycle是如何发生的,以及对应的解决方法。 什么是Closure和类对象间的reference cycle 首先,我们定义一个类,用来表示HTML DOM元素: class HTMLElment { let name: String let text: String? init(name: Strin

泊学高清视频
泊阅文档
我们来了解一下这类reference cycle是如何发生的,以及对应的解决方法。

什么是Closure和类对象间的reference cycle

首先,我们定义一个类,用来表示HTML DOM元素:

class HTMLElment {    let name: String    let text: String?        init(name: String,text: String? = nil) {        self.name = name        self.text = text    }        deinit {        print("\(self.name) is being deinitialized")    }}

其中name表示HTML标签的名字,text表示标签之间的文本内容,由于不是所有HTML标签之间都有文本,因此,它是一个String?。接下来,我们可以像这样定一个HTMLElement对象。

var h1: HTMLElment? = HTMLElment(name: "h1",text: "Title")

如果,我们希望可以把HTMLElement代表的对象渲染出来,例如:<h1>Title</h1>。为了可以在未来定制这个渲染 *** 作,我们决定给HTMLElement添加一个Closure member,它不接受任何参数,返回我们希望渲染的字符串:

class HTMLElment {    let name: String    let text: String?    var asHTML: VoID -> String = { // WRONG Syntax!!!        if let text = self.text {            return "<\(self.name)>\(self.text)</\(self.name)>"        }        else {            return "<\(self.name)>"        }    }    // Omit for simplicity...}

当我们这样编写asHTML的时候,Swift会告诉我们发生了一些语法错误:

这是由于,Swift无法确认当我们在Closure中使用self时,它已经被完整的初始化过了。如果我们需要这种初始化约束,我们可以把asHTML定义为lazy。

class HTMLElment {    // Omit for simplicity    lazy var asHTML: VoID -> String = {        // Omit for simplicity...    }    // Omit for simplicity...}

**“lazy可以确保一个成员只在类对象被完整初始化过之后,才能使用。”
——特别提示**

定义了asHTML之后,我们就可以观察h1的构建和释放过程了。首先,我们看使用asHTML之前:

var h1: HTMLElment? = HTMLElment(name: "h1",text: "Title")h1 = nil

在Playground结果里,我们可以看到h1先被创建,而后被销毁的过程(因为HTMLElement的deinit方法被调用了)。

而当我们在让h1等于nil前,使用asHTML的话,情况就不同了:

var h1: HTMLElment? = HTMLElment(name: "h1",text: "Title")h1.asHTMLh1 = nil

这时我们就发现,HTMLElement的deinit不再被调用了。

根据我们之前的经验,一定是在某处发生了reference cycle。为了能够搞清楚这个问题,我们先来看一下在h1等于nil之前,相关对象之间的关系:

h1是我们定义的strong reference。Closure作为一个引用类型,它有自己的对象,因此asHTML也是一个strong reference。由于asHTML“捕获”了HTMLElement的self,因此HTMLElement的引用计数是2。当h1为nil时,asHTML对closure的引用和closure对self的“捕获”就形成了一个reference cycle。

**“尽管在closure内部,使用了多次selfclosure对self的捕获仅发生1次(引用计数只加1)。”
——特别提示**

用capture List解决reference cycle

本质上来说,closure作为一个引用类型,解决reference cycle的方式和解决类对象之间的reference cycle是一样的,如果引起reference cycle的"捕获"不能为nil,就把它定义为uNowned,否则,定义为weak。而指定“捕获”方式的地方,叫做closure的capture List。我们把asHTML修改成下面这样:

class HTMLElment {    let name: String    let text: String?    lazy var asHTML: VoID -> String = {        // text        // Capture List        [uNowned self] in        if let text = self.text {            return "<\(self.name)>\(self.text)</\(self.name)>"        }        else {            return "<\(self.name)>"        }    }    // Omit for simplicity...}

我们使用一对 [] 表示closure的capture List,由于“捕获”到的self不能为nil(否则closure也不存在了),因此我们把它定义为uNowned self。在我们这样做之后,当h1为nil时,对象之间的关系就变成了这样:

由于HTMLElement没有了strong reference,因此它会被ARC释放掉,进而asHTML引用的closure也会变成“孤魂野鬼”,ARC当然也不会放过它。因此,closure和类对象间的循环引用问题就解决了。

在这里,关于closure capture List,我们要多说两点:

如果closure带有完整的类型描述,capture List必须写在参数列表前面;

如果我们要在capture List里添加多个成员,用逗号把它们分隔开;

class HTMLElment {

let name: String   let text: String?   lazy var asHTML: VoID -> String = {       // text       // Capture List       [uNowned self /*,other capture member*/] () -> String in       if let text = self.text {           return "<\(self.name)>\(self.text)</\(self.name)>"       }       else {           return "<\(self.name)>"       }   }   // Omit for simplicity...

**“当一个类中存在访问数据成员的closure member时,务必要谨慎处理它有可能带来的reference cycle问题。”——特别提示**

总结

以上是内存溢出为你收集整理的Swift里你可能不知道的事儿(3)——处理closure和类对象之间的reference cycle全部内容,希望文章能够帮你解决Swift里你可能不知道的事儿(3)——处理closure和类对象之间的reference cycle所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1078772.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-27
下一篇 2022-05-27

发表评论

登录后才能评论

评论列表(0条)

保存