掌握Swift协议

用日常术语来说,我们谈论的协议是指用于控制事件的设置过程或规则系统。每当您启动一个event时,都需要遵守协议。


综合定义

swift协议与event协议没有什么不同,
让我们解开定义语句的关键元素,看看它们如何帮助理解Apple的官方协议定义:“ 协议定义了适合特定任务或功能的方法,属性和其他要求的蓝图。”

1、Perform or Respond

protocol OpenProtocol {
    var debugDescription: String { get }
    func doSomething()
}

协议可能要求:

  • 实例方法是 the actions that the adopting entities must all be able to perform.
  • 本质上是计算属性,disguised getters.因此它们属于actions that all adopting entities must be able to respond to.

注意:可以将协议定义为符合现有协议,在这种情况下,父协议的所有要求实际上都是子协议的要求,并且对子协议的遵守保证了对父协议的遵守。这称为协议继承,就像类一样,在我们谈论协议组成的情况下也可以组成协议。

protocol OpenProtocol1: OpenProtocol { // inheritance
   func doTheFirstThing()
}
protocol OpenProtocol3: OpenProtocol { // inheritance
   func doTheSecondThing()
}
protocol ComposedProtocol: OpenProtocol, OpenProtocol3 { 
  // composition
}

2、No result-guaranteed
符合协议的对象承诺它将实现该协议的所有非可选方法,但从不对结果做出任何承诺。
此外,符合协议的对象承诺会提供协议的所有计算属性,但绝不会对返回的值做出任何承诺。
3.对象集
定义协议后,它始终会限制entitiesie 的种类。types可以符合它。
没有任何规范,协议是开放的,任何类型(类,结构,枚举等)都可以符合该协议:

protocol OpenProtocol { 
   // can be adopted by any type
   var debugDescription: String { get }
   func doSomething()
}

协议可以受类限制,以指示仅类类型可以符合该协议。请注意,class此处使用的关键字只是typealias for AnyObject。

protocol ClassBoundedProtocol: class { 
   // can only be adopted by classes
   var debugDescription: String { get }
   func doSomething()
}

协议可以绑定到特定的类,以指示只有对该特定类进行子类化的类才能符合该协议。

protocol ErrorPresenter: UIViewController { 
   // can only be adopted by uiviewcontroller types 
   func hideAllErrors()
   func show(error: Error)
   func hide(error: Error)
}

值得一提的是,协议是在类型级别而不是实例级别采用的。后者使对协议的静态需求成为合理,因此我们可以通过以下方式定义协议:

protocol StaticRequirementContainingProtocol: OpenProtocol {
  // MARK: - Static Requirements
  static var typeName: String { get }
  static func isTypeNameEquals(to comparingToTypeName: String) -> Bool
}

值得一提的是,在处理静态需求时,我们需要使用安全性,type accessors例如type(of: T)当我们希望对static protocol requirements任何符合类型的给定对象执行调用时。

func validateType(aProtocol: StaticRequirementContainingProtocol) -> Bool {
  type(of: aProtocol).isTypeNameEquals(to: "")
}

命名约定

完整的Swift命名指南可在此处找到。

  • 协议名称为UpperCamelCase。
  • 描述协议的东西是什么是应为名词(如Collection)。
  • 描述一个协议能力应该使用后缀命名,或(例如,)。 able ible ing Equatable ProgressReporting
  • 如果 an 与协议角色紧密绑定,请通过在协议名称后面附加来避免冲突。(例如) existing type Protocol CarEngineProtocol

不透明类型

当需要隐藏类型标识时,函数可以返回不透明。不透明类型定义为some Protocol其中Protocol是我们希望在其后隐藏类型标识的协议。返回不透明的函数使用以下签名定义:

func object(forKey key: String) -> some OpenProtocol

上面的关键成就是在调用站点返回的变量只是一个OpenProtocol对象。但是,编译器和实现类完全了解返回的对象的基础具体类型。
不透明类型可以看作是通用类型的反向。泛型类型映射到的类型由调用站点指定,并且函数实现基于可接受类型的抽象覆盖范围。对于具有不透明返回类型的函数,这些角色相反。一个不透明的类型让函数实现挑选其返回值的类型。

不透明类型的某些可能应用可以是public contracts或APIs允许模块,框架之间的相互通信。公共接口(公共合同)可能包含诸如传递命令或请求数据之类的操作;这些操作可以限制返回some Protocol。隐藏类型信息在APIs构成各种模块之间边界的处很有用,因为返回值的基础类型可以保留private。

public class AGivenModule {
  private struct AStruct: OpenProtocol { //private type
    var debugDescription: String
    func doSomething() {}
  }
  private struct AnotherStruct: OpenProtocol { //private type
     var debugDescription: String
     func doSomething() {}
  }
private lazy var aProperty = AStruct(debugDescription: "Debug")
private lazy var anotherProperty = AnotherStruct(debugDescription: "Other")
  // Public API
  public func object(forKey key: String) -> some OpenProtocol {
     if aProperty.debugDescription==key {
        return aProperty
     } else if anotherProperty.debugDescription==key {
        return anotherProperty
     }
    return AStruct(debugDescription: "")
  }
}

协议要求

Properties 属性

可以将属性要求指定为get only或。您没有属性要求,因为您只能设置您应该能够读取的内容。 both get and setset only 可以在实例或类型级别上定义属性。后者是通过使用static限定符来实现的。在class 不能在协议要求使用,因为该协议定义不它配合到特定类。

protocol PropertiesProtocol: class {
   var aGetOnlyProperty: String { get }
   var aGetAndSetProperty: String { get set }
   static var aTypeLevelProperty: String { get set }
   // class var aClassLevelProperty: String { set } // Not possible
   // var aSetOnlyProperty: String { set } // Not possible
}

方法

可能需要实例和类型方法。这些协议方法不能在协议定义中提供默认参数。可变参数和通用参数都是允许的。协议方法不能返回不透明类型。同样,根据类型属性的要求,唯一允许指示类型方法的关键字是static.
突变方法在非类有界协议中是允许的,并且当该方法可能修改采用对象的任何属性的值时,应使用该方法。

protocol SomeCache {
   static func setTime(lifeTime: TimeInterval) // type method
   func registerForMemoryWarning() // intance method
   mutating func clear() // mutating instance method,
                   // only allowed in non-class bounded protocol
   func savedObject<T>() -> T? // Generic method
   func store(value: Any...) // Method with variadic parameters
}

初始化器

可能不符合要求的初始化程序要求应在 没有func关键字的情况下进行定义,并且必须命名为init。

protocol SomeCollectionProcol {
   init() // non failable init
}
protocol SomeCollectionProcol {
   init?(collectionLiteral: String) // failable init
}

尽管Swift允许在协议中使用初始化程序,但我个人认为应尽量避免使用它们。我的观点是,从呼叫站点的角度来看,抽象在理想情况下应为后存在工具,而不是前存在工具。在将由抽象要求驱动的任何逻辑应用于它们之前,必须存在符合条件的对象。通过提供init需求,我们暗含地说我们可以使用协议需求来创建符合条件的对象。鉴于 init是类型而不是实例的事实 方法,这意味着我们要么预先知道类型,要么通过类型访问器检索其基础类型。

从前面的陈述中,出现以下问题:

  • 如果我们知道底层的具体类型,为什么还要麻烦地将其视为背后的抽象,protocol type,特别是如果它是一个内部存储和使用的对象呢?
  • 如果我们已经有了一个符合标准的对象,为什么只为了创建另一个符合标准的对象而烦恼获取它的类型呢?
  • 知道抽象不应该依赖细节;我们不是通过提供一个接收具体类型详细信息作为其某些参数的init来突破dependency inversion principle吗?

当且仅当您可以提供上述问题的有效答案,然后您才能考虑在协议定义中包含初始化程序要求。

注意:您将需要使用类型访问器而不是具体的类型名称来创建协议实例。

协议操作

1、 is 算子
该is运算符用于检查是否符合Protocol。它与以下语法一起使用:

let doesConformToProtocol = anObject is SomeCache

结果为布尔值,如果符合则评估为true,否则anObject为false。SomeCache

2、as 算子
该as运营商用于铸造一个给定的对象为Protocol对象。可以使用强制地进行铸造as!(非failable)或正常as?(failable)。

if let cache = anObject as? SomeCache {
   cache.registerForMemoryWarning() // non optional
}
// or
(anObject as? SomeCache)?.registerForMemoryWarning()
// or
let cache = anObject as? SomeCache
cache?.registerForMemoryWarning()

注意:尽管as?可以在没有任何事先检查的情况下随时as!使用,但是只有在保证结果成功的情况下,才可以使用。理想情况下,应通过对is操作员的事先检查来保护它,该检查必须已被评估为true。

if anObject is SomeCache { 
   registerForMemoryWarning(anObject)
}
fun registerForMemory(_ anObject: AnyObject) { 
  let cache = anObject as! SomeCache 
  cache.registerForMemoryWarning()
}

3、& 算子
当实例属性或方法参数应该符合多个时Protocols,一种选择是用一个新的来表示其类型,该新的Protocol可以使用协议组合来定义。假设有两个协议PrototolA,ProtocolB;我们可以构建第三个协议,ProtocolC如下所示:

protocol ProtocolA {}
protocol ProtocolB {}
protocol ProtocolC: ProtocolA, ProtocolB {} // protocol composition
class A {
  var aProperty: ProtocolC
}
func accept(protocol: ProtocolC) {}

但是,只有在具有经验意义时,协议组合才有意义ProtocolC。每当后者不具有任何本质意义或没有建立之间的一定的相关性PrototolA和ProtocolB,它是errosnoues到使用组合物。对于我们来说&,是最能表明一个事实的事实,即给定对象必须同时符合多种协议和/或属于某种具体的类别类型,这才是我们的最佳选择。

class A {
  var aProperty: ProtocolA & ProtocolB
}
func accept(protocol: ProtocolA & ProtocolB) {}

&操作者可提供多种类型之间被施加的是至少一个所述 操作数是一个Protocol和不超过一个操作数是一个类类型的。当需要减少冗长程度时,可以通过使用typealias:

typealias PropertyType = ProtocolA & ProtocolB & ProtocolB & AClass

协议一致性

重要的是要提到a Protocol并没有在采用类型上强加属性的存储机制。协议get only 要求可以实现为stored (let和var)或computed属性。类似地,get and set 要求 可以被实现为存储属性(VAR只)或作为计算的属性提供分别提供两个一组和一个get。
即使协议方法定义中不允许使用默认参数,符合类型也可以使用提供默认参数的签名来实现所需的方法。虽然编译器将验证是否符合协议,在调用点的任何这些的协议对象的方法,一个 仍然需要通过所有参数。用默认参数实现协议方法的唯一好处是,当在对象上调用该方法时,可以忽略那些参数,这被视为其具体类型的表示,而不是协议对象。
为了说明前两段中的要点,让我们考虑SomeCache定义如下的协议:

protocol SomeCache {
  var lifetime: String { get } // get only property
  var storageCount: Double { get } // get only property
  var settableProperty: String { get set } // set and get property
  var anotherSettableProperty: String { get set } // set and get property
  func invalidate(keepingStoredData shouldKeepStoreData: Bool)
}

并考虑一个符合它的结构:

struct MyStruct: SomeCache {
  let storage = NSMutableSet()
  private(set) let lifeTime: TimeInterval = 1000 // get only
          // property requirement implemented as stored let property
 
  var storageCount: Int { storage.count } // get only property
                               // implemented as computed property
  var settableProperty: String { // get and set
          // property requirement implemented 
          // as computed properties providing getter and setter
     get { "" }
     set { /* perform the set operation */ }
  }
  var anotherSettableProperty: String = "Test" // get and set 
          // property requirement implemented as stored var property

  // method requirement implemented with default parameter
  func invalidate(keepingStoredData shouldKeepStoreData: Bool = true) { }
}
let concreteTypeObject: MyStruct = MyStruct()
concreteTypeObject.invalidate() // can be called considering default param
let protocolObject: SomeCache = MyStruct()
protocolObject.invalidate(keepingStoredData: false) // cannot be called considering default param

默认实现 Default Implementation

提供给定要求(属性或方法)的默认实现,即 任何采用类型的类型都必须使用扩展名。如果您提供所有要求的实现,则任何合格类型都可以采用该协议,而无需支付任何额外的代码费用,只需将类型标记为符合协议即可。所需方法或属性的特定于类型的实现取代了默认实现。

protocol ProtocolWithExtensionProvingSomeRequirement {
   var property: String { get }
   var anotherProperty: Double { get }
}
extension ProtocolWithExtensionProvingSomeRequirement {
   var property: String { "A given string" }
}
struct StructNotProvingOwnImplementation: ProtocolWithExtensionProvingSomeRequirement { 
   // needs to provide implementation for anotherProperty
   var anotherProperty: Double { 0.0 }
}
struct StructProvidingOwnImplementation: ProtocolWithExtensionProvingSomeRequirement { 
  // needs to provide implementation for anotherProperty
   var property: String { "This will be used instead " } // will be returned instead of the default implementation return value
   var anotherProperty: Double { 0.0 }
}
protocol ProtocolWithExtensionProvidingAllsRequirements {
   var property: String { get }
}
extension ProtocolWithExtensionProvidingAllsRequirements {
   var property: String { "A given string" }
}
struct StructProviding: ProtocolWithExtensionProvidingAllsRequirements { 
  // no need to provide  any implementation
}

当您发现自己编写默认实现时,特别是没有编码或使用诸如此类的伪代码的方法要求,print,通常是不正确的接口隔离的征兆。的确,如单词所规定的那样,默认实现是实现而不是不实现。后者意味着并非所有预期的符合类型都不需要该方法,因此,您应该考虑将协议分成许多并使用协议组成为需要提供所组成的所有方法和属性的符合类型定义合适的协议。协议。
注意:我们将讨论Objc Swift协议中的可选方法,这些方法可以帮助解决上述问题。

符合第三方类型

只有当一个类型拥有协议时,即在当前模块中对其进行定义时,才可以将其标记为符合协议。但是,对于不属于Swift,Objc,第三方库,框架或外部模块一部分的类型,必须使用type extension完成一致性。后者将负责提供未 拥有类型的原始实现尚未提供的所有要求。
让我们假设String在Swift中定义的类型。如果我们希望将字符串对象作为DisplayableError对象抛出,则必须使用如下扩展名

protocol DisplayableError: Error {
  var errorInfo: String { get }
  mutating func invalide()
}
extension String: DisplayableError {
  var errorInfo: String { self }
  func invalide() { self = "" }
}

上面提到的一致性类型是无条件的,因为它将被所有String类型采用。我们说的是什么conditional conformance 时候对协议要求实现的类型有限制。假设协议BoundedSummableCollection定义如下:

extension Array:BoundedSummableCollection where Element: BinaryFloatingPoint {
  var max: Double? {
     guard let maxElement =  (sorted { (lhs, rhs) -> Bool in
        lhs.magnitude > rhs.magnitude
      }).first else 
       return nil
     }
     return Double(maxElement)
  }
  var min: Double? {
     guard let minElement =  (sorted { (lhs, rhs) -> Bool in
        lhs.magnitude < rhs.magnitude
     }).first else {
        return nil
     }
     return Double(minElement)
  }
  func sum() -> Double {
    let elementsSum = reduce(0,+)
    return Double(elementsSum)
  }
}

注意:我们可以使用以下默认实现来实现上述目的:

extension BoundedSummableCollection where Self == Array<Double> { }     
  // constraint is a type, use '=='
or
extension BoundedSummableCollection where Element is Numberic {} 
   // constraint is a protocol, use 'is'

注意:如示例中突出显示的那样,当约束是具体 类型时,我们使用;当约束是协议时,我们必须使用。 == is
在这些扩展中,我们可以分别self用作Array <Double>类型的对象或符合的对象Numeric,该对象 允许执行所需的所有操作,以类似地实现使用条件符合性所做的事情。仅仅因为给定类型或其扩展名可以提供所有协议r要求,并不能使其符合协议;协议的一致性只能通过明确的采用来保证,绝不暗示。为了“激活”一致性,我们需要明确声明Array符合BoundedSummableCollection。

extension Array: BoundedSummableCollection where Double == Int {}

提供初始化要求

初始化程序要求既可以指定为初始化程序,也可以作为便捷初始化程序来实现,required除最终类外,强制需要修饰符,因为不能将其归类。

protocol SomeCollectionProcol {
   init?(collectionLiteral: String) // failable init
}
class ACollectionClass: SomeCollectionProcol {
  required init?(collectionLiteral: String) { }
}