在Swift中创建不可变的委托

最初发布于 swiftrocks.com

为了防止诸如委托之类的属性发生引用循环,通常使用weak关键字:

 weak var delegate: HomeViewDelegate? 

不幸的是,由于它可以保护您避免丢失引用,因此weak关键字会强制使用var和可选类型,如果您要构建的UIView类的东西在没有委托的情况下不能使用,那么这可能会非常麻烦。突然减少变更代表:

 class HomeView: UIView { 
weak var delegate: HomeViewDelegate?

func renderView() {
guard let delegate = delegate else {
//Timmy: this should never happen!
return
}
let category = delegate.currentlySelectedCategory()
categoryView.render(category: category)
delegate.homeViewDidUpdate()
}
}

但是, weak不是打破参考周期的唯一方法。 就像在捕获列表中一样,关键字unowned可以在属性中使用以创建非强引用:

 unowned let delegate: HomeViewModelDelegate 

weak引用不同, unowned引用应始终具有价值。 这样,您不仅可以在声明非可选类型时使用它们,而且还可以通过let来使用它们,从而恢复不变性并确保您的对象不会以意外的方式起作用:

 class HomeView: UIView { 
private unowned let delegate: HomeViewDelegate

init(delegate: HomeViewDelegate) {
self.delegate = delegate
}
  func renderView() { 
let category = delegate.currentlySelectedCategory()
categoryView.render(category: category)
delegate.homeViewDidUpdate()
}
}

使用weak ,没有任何事情可以阻止HomeView在没有委托的情况下使用,并且由于关键字的可选要求,需要guard措施以解包从委托检索的值。 另一方面, unowned可让您打破参考周期,同时仍然像强大一样使用参考。

但是,请注意, unowned属性将与捕获列表关键字一样工作。 如果您尝试访问已取消分配的未拥有引用,则您的应用将崩溃。

如果保证您的对象永远不会超过其无主引用(例如,像ViewModel ),则使用unowned可以极大地提高代码质量和性能。

但是, unowned可能会使您的应用崩溃,就像隐式展开的可选属性一样。 您不应该对所有事物都使用weak吗?

大多数人都依赖于weak ,而由于其潜在的崩溃,尤其是在捕获列表上,他们都对weak unowned 。 这是使用Swift的安全方法,但不是Apple的预期做法。

与隐式展开的可选选项(我认为这只是解决体系结构问题的一种懒惰方法)不同, unowned引用比weak引用有显着优势:它们具有更好的性能,允许不变性,并且由于不能手动将其设置为nil ,因此您的代码不会意外地遵循意外路径。 unowned引用绝对是安全的-仅当您滥用关键字时才会出现问题。

根据Apple的说法,当对象不能超过其引用时,应始终使用未拥有的引用:

仅当确定引用始终引用尚未取消分配的实例时,才使用无主引用。

如果在释放该实例后尝试访问一个未拥有的引用的值,则会收到运行时错误。

如果捕获的引用永远不会为零,则应始终将其捕获为未拥有的引用,而不是弱引用。

在任何其他情况下,您应继续使用weak引用。

还有什么?

下次创建委托或@escaping闭包时,请考虑其上下文,并查看它是否有可能超过其引用。 如果不是这样,您可能会发现未unowned属性是提高代码质量的有趣工具。

参考文献和优秀读物

Apple Docs:ARC和关键字