Swift捕获列表:弱链接,强链接和无主链接有什么区别?


囚犯约瑟夫·赖特 -强大的俘虏插图

“已捕获”值的列表位于闭包参数列表的前面,可以通过三种不同方式从范围“捕获”值:使用链接“强”,“弱”或“无主”。 我们经常使用它,主要是为了避免强参考循环(“强参考循环”或“保留循环”)。
新手开发人员可能很难决定采用哪种方法,因此您可以花很多时间在“强”和“弱”之间或“弱”和“无主”之间进行选择,但是随着时间的推移,您将意识到正确的选择-只有一个。

首先,创建一个简单的类:

class Singer { func playSong() { print("Shake it off!") } } 

然后,我们编写一个函数,该函数创建Singer类的实例并返回一个闭包,该闭包调用Singer类的playSong()方法:

 func sing() -> () -> Void { let taylor = Singer() let singing = { taylor.playSong() return } return singing } 

最后,我们可以在任何地方调用sing()以获得播放playSong()的结果

 let singFunction = sing() singFunction() 


结果,将显示“ Shake it off!”行。

强力捕捉


除非您明确指定捕获方法,否则Swift将使用“强”捕获。 这意味着闭包捕获使用的外部值,并且永远不会释放它们。

让我们再次看一下sing()函数

 func sing() -> () -> Void { let taylor = Singer() let singing = { taylor.playSong() return } return singing } 

taylor常量是在函数内部定义的,因此在正常情况下,一旦函数完成工作,就可以释放其位置。 但是,此常量在闭包内部使用,这意味着只要闭包本身存在,即使在函数结束之后,Swift也会自动确保其存在。
这是一个“强大”的动作。 如果Swift允许释放taylor ,则调用闭包将是不安全的-其taylor.playSong()方法不再有效。

“弱”捕获(弱捕获)


Swift允许我们创建一个“ 捕获列表 ”来确定如何捕获所使用的值。 “强”捕获的替代方法是“弱”捕获,其应用会导致以下后果:

1.闭包不保留“弱”捕获值,因此可以释放它们并将其设置为nil

2.作为第一段的结果,Swift中“弱”捕获的值始终是可选的
我们使用“弱”捕获来修改示例,然后立即看到差异。

 func sing() -> () -> Void { let taylor = Singer() let singing = { [weak taylor] in taylor?.playSong() return } return singing } 

[弱泰勒] -这是我们的“ 捕获列表 ”,它是闭包语法的特殊部分,其中我们给出了有关如何捕获值的说明。 这里我们说taylor应该被“弱”地捕获,所以我们需要使用taylor?.PlaySong() -现在它是可选的 ,因为可以随时将其设置为nil

如果现在执行此代码,您将看到调用singFunction()不再产生消息。 这是因为taylor仅存在于sing()内部,并且此函数返回的闭包并未将taylor牢固地保持在其内部。

现在尝试将taylor?.PlaySong()更改为taylor!.PlaySong() 。 这将导致泰勒在封盖内被强制拆箱,并因此导致致命错误(拆箱包含nil的内含物)

“无主”捕获(无主捕获)


“弱者”捕获的替代方法是“无主”。

 func sing() -> () -> Void { let taylor = Singer() let singing = { [unowned taylor] in taylor.playSong() return } return singing } 

该代码将以类似于可选部署的可选方式的异常结束,后者的可选方式要高一些-无名的taylor说:“我确定taylor会在电路关闭时一直存在,因此我不需要将其保存在内存中。” 实际上, taylor将几乎立即被释放,并且此代码将崩溃。

因此,非常小心地使用无主物品

常见问题


开发人员在闭包中使用价值捕获时面临四个问题:

1.在闭包采用参数的情况下,捕获列表的位置存在困难


这是在研究闭包开始时可能遇到的一个常见问题,但是幸运的是,在这种情况下,Swift可以为我们提供帮助。

当同时使用捕获列表和闭包参数时,捕获列表放在方括号中,然后是闭包参数,然后是in关键字,标记闭包“ body”的开始。

 writeToLog { [weak self] user, message in self?.addToLog("\(user) triggered event: \(message)") } 

尝试在闭包参数之后放置捕获列表将导致编译错误。

2.出现强链接循环,导致内存泄漏


当实体A具有实体B时,反之亦然,您会遇到一种称为“保留周期”的情况。

作为示例,请考虑以下代码:

 class House { var ownerDetails: (() -> Void)? func printDetails() { print("This is a great house.") } deinit { print("I'm being demolished!") } } 

我们定义了House类,该类包含一个属性(关闭),一个方法和一个反初始化器,该反初始化器将在该类的实例被破坏时显示一条消息。

现在创建一个与上一个类相似的Owner类,不同之处在于其闭包属性包含有关房屋的信息。

 class Owner { var houseDetails: (() -> Void)? func printDetails() { print("I own a house.") } deinit { print("I'm dying!") } } 

现在,在do块中创建这些类的实例。 我们不需要catch块,但是使用do块会在之后立即破坏实例。

 print("Creating a house and an owner") do { let house = House() let owner = Owner() } print("Done") 

结果,将显示以下消息:“创建房屋和所有者”,“我快死了!”,“我正在被拆毁!”,然后“完成”-一切正常。

现在创建一个强链接循环。

 print("Creating a house and an owner") do { let house = House() let owner = Owner() house.ownerDetails = owner.printDetails owner.houseDetails = house.printDetails } print("Done") 

现在将显示消息“创建房屋和所有者”,然后显示“完成”。 反初始化器将不会被调用。

这是由于房屋具有指向所有者的财产,而所有者具有指向房屋的财产这一事实而发生的。 因此,它们都不能安全释放。 在实际情况下,这会导致内存泄漏,从而导致性能下降,甚至导致应用程序崩溃。

为了解决这种情况,我们需要创建一个新的闭包并在一种或两种情况下使用“弱”捕获,如下所示:

 print("Creating a house and an owner") do { let house = House() let owner = Owner() house.ownerDetails = { [weak owner] in owner?.printDetails() } owner.houseDetails = { [weak house] in house?.printDetails() } } print("Done") 

无需声明两个捕获的值,只需在一个地方就可以完成声明-这将使Swift可以在必要时销毁两个类。

在实际项目中,很少出现这种明显的强链接周期的情况,但这更说明了在有能力的开发中使用“弱”捕获的重要性。

3.强链接的无意使用,通常在捕获多个值时


Swift默认情况下使用强大的抓地力,这可能导致意外行为。
考虑以下代码:

 func sing() -> () -> Void { let taylor = Singer() let adele = Singer() let singing = { [unowned taylor, adele] in taylor.playSong() adele.playSong() return } return singing } 

现在,闭包捕获了两个值,并且以相同的方式使用它们。 但是,只有taylor被捕获为未拥有-adele被强烈捕获,因为必须为每个捕获的值使用unowned关键字。

如果您故意这样做,那么一切都很好,但是如果您希望两个值都被“ 无主 ”捕获,则需要满足以下条件:

 [unowned taylor, unowned adele] 

4.复制闭包并共享捕获的值


开发人员偶然发现的最后一种情况是如何复制错误,因为他们捕获的数据可用于所有错误副本。
考虑一个简单的闭包示例,该示例捕获闭包外部声明的整数变量numberOfLinesLogged ,以便我们可以增加其值并在每次调用该闭包时将其打印出来:

 var numberOfLinesLogged = 0 let logger1 = { numberOfLinesLogged += 1 print("Lines logged: \(numberOfLinesLogged)") } logger1() 

这将显示消息“记录的行数:1”。
现在,我们将创建一个闭合的副本,该副本将与第一个闭合一起共享捕获的值。 因此,如果我们调用原始的闭包或其副本,我们将看到变量的价值不断增长。

 let logger2 = logger1 logger2() logger1() logger2() 

这将打印出消息“记录的行数:1” ...“记录的行数:4”,因为logger1logger2指向相同的捕获的numberOfLinesLogged变量。

何时使用“强”捕获,“弱”和“无主”


现在,我们了解了一切工作原理,让我们尝试总结一下:

1.如果您确定执行关闭操作时捕获的值永远不会为零 ,则可以使用“未拥有的捕获” 。 这是一种罕见的情况,即使使用闭包内部的弱捕获值使用“弱”捕获也会导致其他困难。

2.如果遇到强链接循环的情况(实体A拥有实体B,实体B拥有实体A),则在其中一种情况下,您需要使用“弱捕获” 。 必须考虑到两个实体中的哪个实体将首先被释放,因此如果视图控制器A表示视图控制器B,则视图控制器B可能包含一个返回到“ A”的“弱”链接。

3.如果排除了出现强链接周期的可能性,则可以使用“强”捕获( “强捕获” )。 例如,执行动画不会在包含动画的闭包内部阻止自身,因此您可以使用强绑定。

4.如果不确定,请从“弱”绑定开始,然后仅在必要时进行更改。

可选-Swift官方指南:
短路
自动链接计数

Source: https://habr.com/ru/post/zh-CN444336/


All Articles