委托Swift

在讨论委托时,我经常看到与通知,观察和回调(关闭或阻止)的比较。 解决许多常见问题时,这些概念有所不同。 本文介绍了委派概念,如何实现适当的委派,最佳实践,并将委派模式与其他方法进行了比较。

我更喜欢将委托作为“四人帮”一书中描述的概念与可可/可可接触中的委托模式分开。

委托概念

委托(动词)-委托(任务或责任)给另一个人。

新牛津美国词典

在编程术语中,该引用翻译为委托(任务或责任)另一个对象。

委托使用对象组合来允许自定义和代码重用,并且它是继承的替代方法。 委托对象与其委托对象之间的关系类似于子类如何将方法转发给其超类。

突出的区别是继承是静态的,而组合是动态的。 使用委托可以在运行时更改对象的行为。

在这里,我有一个示例对象层次结构。 ShapeView是提供笔触和填充颜色的抽象类。 RectangleViewEllipseView通过重写draw(_:)方法实现绘图逻辑。

在Swift中,在扩展中实现协议很方便。

协议声明

  @protocol 委托 < NSObject > 
  协议 委托AnyObject 

协议定义了适合特定任务或功能的方法,属性和其他要求的蓝图。

Swift编程语言

协议非常适合委派模式,因为它们可以对需求进行适当的控制,而控制量不超过需求的数量。 这样,委托对象不知道委托的实现细节。

Delegate协议继承了NSObject协议,以便为可选方法提供自省功能。

在Swift中, Delegate声明为仅类协议,以将其用作weak引用。

替代声明使用的是非正式协议,通常是NSObject类别,但是这种方法被认为是旧方法,并且在现代框架中未使用。

属性和内存管理

  @propertynonatomicweakid < 代表 >代表; 
   var委托: 委托

Objective-C中的属性声明为id

  • id是指向任何对象的指针,委托不限于NSObject子类;
  • Delegate继承了NSObject协议以提供必要的运行时方法。

为了防止强引用循环,将委托属性声明为weak 。 在Swift中,这是通过使用仅类协议来提供的。

为什么我们要声明委托为weak引用? 考虑MVC模式,其中控制器是视图的委托。 这两个实例相互引用。 这样可以创建强大的参考周期,从而使视图和控制器保持活动状态。 我们需要weak引用来解决此问题。

选装件

代表可以有可选方法。 在Objective-C中,一组可选方法用@optional关键字标记。

在调用可选方法之前,我们必须确保该方法已实现。 在Objective-C中,我们可以使用respondsToSelector:因为我们继承了NSObject协议。

Swift具有用于与Objective-C互操作性的optional修饰符。 我们必须对协议和可选方法都使用@objc 。 这也要求参数在Objective-C中可以表示。

总体声明看起来很麻烦。 使用可选链接在Swift中调用可选方法很方便。

更迅速的是可以使用协议扩展将可选方法替换为默认的空实现。

命名约定

委托协议通常与类声明紧密联系在一起,没有类就没有委托。 Swift或Objective-C都不支持嵌套协议声明。 因此,委托协议的名称必须以类名称开头。

方法名称以类名称(无前缀)开头,并且第一个参数必须是委派对象本身。

当我们想要提供默认值或处理多个委托对象时,这可以进行引用,并且很有用。

方法名称应包括辅助动词(应该,将会,已经,确实)以标识发生或即将发生的事件。

在代表更改即将发生的事件或提供其他信息的情况下,例外情况很常见。

委托和数据源

数据源类似于委托,除了它不是对用户界面的委托控制,而是数据的委托控制。

Objective-C编程中的概念

Objective-C编程中概念》指南有些过时,如今,委托显然已用于不仅在用户界面中处理事件。

数据源遵循与委托相同的约定。 除非委托中的所有方法都是可选的,否则数据源包含必需的方法(不提供数据的数据源不是很有用)。

此数据源中的某些对象非常UICollectionViewCell ,例如UICollectionViewCellUIViewController 。 使用此模式可以使使用者懒惰地查询提供程序并重用分配的实例。

将对象分为事件处理和提供数据也是合乎逻辑的。

委托与继承

对具有委托的类进行子类化可以揭示一些问题。 看一下UITableView继承层次结构。

这种设计实际上是不正确的。 考虑下面的代码。

UITableView期望其委托为UITableViewDelegate类型。 但是,我们可以提供UIScrollViewDelegate子类型。 这打破了可替换性原则:返回值可以是子类型,参数不能是超类型:

您可以阅读Mike Ash上有关主题的Liskov替代原理,协方差和反方差的更多信息。

此代码的编译和工作有两个原因:

  • 之所以编译,是因为代码是从Objective-C导入的。 斯威夫特(Swift)不允许这种覆盖。
  • 它不会在运行时中断,因为委托中的所有方法都是可选的,并且实现检查实例是否实现了方法。 数据源中的必需方法不是这种情况。

Clang具有-Wincompatible-property-type诊断标志,导致在Objective-C中发出警告:

 属性类型A与从C继承的类型B不兼容 

Swift有一个诊断错误override_property_type_mismatch

 类型为“ B”的属性“ A”不能覆盖类型为“ C”的属性 

那么,当我们需要向委托添加方法时该怎么办?

方法是创建一个单独的委托。 UITableView实际上具有用于特定功能的多个委托:

另外,如果您不是子类,则可以继承协议以实现关注点分离。 URLSession是一个很好的例子。 delegate具有URLSessionDelegate类型,它是丰富层次结构的基类,包括数据,下载和流任务的委托。

查看控制器演示

当涉及视图控制器演示时,代表很有用。 编辑视图控制器可以通知委托有关其意图的完成或取消。

呈现视图控制器将自身设置为呈现视图控制器的委托。 收到回调后,它可以反映更改并关闭显示的控制器。 这是传达变更的一种好方法,它不依赖于具体的表示。

相似图案

虽然委托概念描述了如何用组合替换继承,但Cocoa和Cocoa Touch中的委托模式主要用作事件处理机制。

目标行动

Target-action在Cocoa和Cocoa Touch中有非常特殊的用途-处理来自NSControlUIControl事件。

与代表相比如何? Target-action更严格,它的API要求Action(选择器)具有特定的签名。 也没有办法更改或阻止即将发生的事件。

目标动作由基类提供。 它支持多个目标,并且更具动态性。 例如,当没有提供目标时,控件可以在运行时使用响应者链确定目标。

通知事项

通知是通信机制。 它不会在实例之间创建显式连接,而是通过共享实例(通知中心)起作用。 通知允许一个到多个连接,并且不提供任何方式来更改或阻止即将发生的事件。

封闭作为替代

对于授权,关闭可能是最接近的选择。 我们可以用闭包替换ShapeView的委托。

闭包是一对一的关系,但在不影响Objective-C运行时的情况下将一对多的关系更简单。

因为闭包可以具有返回值,所以它们可以更改或阻止即将发生的事件。 这也使闭包替代了数据源。

闭包为必需的和可选的例程提供了更好的支持。 对于协议,我们必须使用optional@objc属性。 这限制了使用Swift类型的能力,要求对象实现协议为Objective-C类,并使用Objective-C消息传递。 对于闭包,必需和可选之间的区别是非可选和可选类型之间的区别。

可以内联方式提供瓶盖。 这可以带来使代码“内联”的好处,但是当需要实现许多闭包时,可能导致功能范围混乱。

整体闭包是委托模式的更快速替代方案。