SwiftUI 掌握Swift協(xié)議protocal

掌握Swift協(xié)議

用日常術(shù)語來說,我們談?wù)摰膮f(xié)議是指用于控制事件的設(shè)置過程或規(guī)則系統(tǒng)。每當(dāng)您啟動(dòng)一個(gè)event時(shí),都需要遵守協(xié)議。


綜合定義

swift協(xié)議與event協(xié)議沒有什么不同,
讓我們解開定義語句的關(guān)鍵元素,看看它們?nèi)绾螏椭斫釧pple的官方協(xié)議定義:“ 協(xié)議定義了適合特定任務(wù)或功能的方法,屬性和其他要求的藍(lán)圖?!?/p>

1、Perform or Respond

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

協(xié)議可能要求:

  • 實(shí)例方法是 the actions that the adopting entities must all be able to perform.
  • 本質(zhì)上是計(jì)算屬性,disguised getters.因此它們屬于actions that all adopting entities must be able to respond to.

注意:可以將協(xié)議定義為符合現(xiàn)有協(xié)議,在這種情況下,父協(xié)議的所有要求實(shí)際上都是子協(xié)議的要求,并且對(duì)子協(xié)議的遵守保證了對(duì)父協(xié)議的遵守。這稱為協(xié)議繼承,就像類一樣,在我們談?wù)搮f(xié)議組成的情況下也可以組成協(xié)議。

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

2、No result-guaranteed
符合協(xié)議的對(duì)象承諾它將實(shí)現(xiàn)該協(xié)議的所有非可選方法,但從不對(duì)結(jié)果做出任何承諾。
此外,符合協(xié)議的對(duì)象承諾會(huì)提供協(xié)議的所有計(jì)算屬性,但絕不會(huì)對(duì)返回的值做出任何承諾。
3.對(duì)象集
定義協(xié)議后,它始終會(huì)限制entitiesie 的種類。types可以符合它。
沒有任何規(guī)范,協(xié)議是開放的,任何類型(類,結(jié)構(gòu),枚舉等)都可以符合該協(xié)議:

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

協(xié)議可以受類限制,以指示僅類類型可以符合該協(xié)議。請(qǐng)注意,class此處使用的關(guān)鍵字只是typealias for AnyObject。

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

協(xié)議可以綁定到特定的類,以指示只有對(duì)該特定類進(jìn)行子類化的類才能符合該協(xié)議。

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

值得一提的是,協(xié)議是在類型級(jí)別而不是實(shí)例級(jí)別采用的。后者使對(duì)協(xié)議的靜態(tài)需求成為合理,因此我們可以通過以下方式定義協(xié)議:

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

值得一提的是,在處理靜態(tài)需求時(shí),我們需要使用安全性,type accessors例如type(of: T)當(dāng)我們希望對(duì)static protocol requirements任何符合類型的給定對(duì)象執(zhí)行調(diào)用時(shí)。

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

命名約定

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

  • 協(xié)議名稱為UpperCamelCase。
  • 描述協(xié)議的東西是什么是應(yīng)為名詞(如Collection)。
  • 描述一個(gè)協(xié)議能力應(yīng)該使用后綴命名,或(例如,)。 able ible ing Equatable ProgressReporting
  • 如果 an 與協(xié)議角色緊密綁定,請(qǐng)通過在協(xié)議名稱后面附加來避免沖突。(例如) existing type Protocol CarEngineProtocol

不透明類型

當(dāng)需要隱藏類型標(biāo)識(shí)時(shí),函數(shù)可以返回不透明。不透明類型定義為some Protocol其中Protocol是我們希望在其后隱藏類型標(biāo)識(shí)的協(xié)議。返回不透明的函數(shù)使用以下簽名定義:

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

上面的關(guān)鍵成就是在調(diào)用站點(diǎn)返回的變量只是一個(gè)OpenProtocol對(duì)象。但是,編譯器和實(shí)現(xiàn)類完全了解返回的對(duì)象的基礎(chǔ)具體類型。
不透明類型可以看作是通用類型的反向。泛型類型映射到的類型由調(diào)用站點(diǎn)指定,并且函數(shù)實(shí)現(xiàn)基于可接受類型的抽象覆蓋范圍。對(duì)于具有不透明返回類型的函數(shù),這些角色相反。一個(gè)不透明的類型讓函數(shù)實(shí)現(xiàn)挑選其返回值的類型。

不透明類型的某些可能應(yīng)用可以是public contracts或APIs允許模塊,框架之間的相互通信。公共接口(公共合同)可能包含諸如傳遞命令或請(qǐng)求數(shù)據(jù)之類的操作;這些操作可以限制返回some Protocol。隱藏類型信息在APIs構(gòu)成各種模塊之間邊界的處很有用,因?yàn)榉祷刂档幕A(chǔ)類型可以保留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: "")
  }
}

協(xié)議要求

Properties 屬性

可以將屬性要求指定為get only或。您沒有屬性要求,因?yàn)槟荒茉O(shè)置您應(yīng)該能夠讀取的內(nèi)容。 both get and setset only 可以在實(shí)例或類型級(jí)別上定義屬性。后者是通過使用static限定符來實(shí)現(xiàn)的。在class 不能在協(xié)議要求使用,因?yàn)樵搮f(xié)議定義不它配合到特定類。

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
}

方法

可能需要實(shí)例和類型方法。這些協(xié)議方法不能在協(xié)議定義中提供默認(rèn)參數(shù)??勺儏?shù)和通用參數(shù)都是允許的。協(xié)議方法不能返回不透明類型。同樣,根據(jù)類型屬性的要求,唯一允許指示類型方法的關(guān)鍵字是static.
突變方法在非類有界協(xié)議中是允許的,并且當(dāng)該方法可能修改采用對(duì)象的任何屬性的值時(shí),應(yīng)使用該方法。

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
}

初始化器

可能不符合要求的初始化程序要求應(yīng)在 沒有func關(guān)鍵字的情況下進(jìn)行定義,并且必須命名為init。

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

盡管Swift允許在協(xié)議中使用初始化程序,但我個(gè)人認(rèn)為應(yīng)盡量避免使用它們。我的觀點(diǎn)是,從呼叫站點(diǎn)的角度來看,抽象在理想情況下應(yīng)為后存在工具,而不是前存在工具。在將由抽象要求驅(qū)動(dòng)的任何邏輯應(yīng)用于它們之前,必須存在符合條件的對(duì)象。通過提供init需求,我們暗含地說我們可以使用協(xié)議需求來創(chuàng)建符合條件的對(duì)象。鑒于 init是類型而不是實(shí)例的事實(shí) 方法,這意味著我們要么預(yù)先知道類型,要么通過類型訪問器檢索其基礎(chǔ)類型。

從前面的陳述中,出現(xiàn)以下問題:

  • 如果我們知道底層的具體類型,為什么還要麻煩地將其視為背后的抽象,protocol type,特別是如果它是一個(gè)內(nèi)部存儲(chǔ)和使用的對(duì)象呢?
  • 如果我們已經(jīng)有了一個(gè)符合標(biāo)準(zhǔn)的對(duì)象,為什么只為了創(chuàng)建另一個(gè)符合標(biāo)準(zhǔn)的對(duì)象而煩惱獲取它的類型呢?
  • 知道抽象不應(yīng)該依賴細(xì)節(jié);我們不是通過提供一個(gè)接收具體類型詳細(xì)信息作為其某些參數(shù)的init來突破dependency inversion principle嗎?

當(dāng)且僅當(dāng)您可以提供上述問題的有效答案,然后您才能考慮在協(xié)議定義中包含初始化程序要求。

注意:您將需要使用類型訪問器而不是具體的類型名稱來創(chuàng)建協(xié)議實(shí)例。

協(xié)議操作

1、 is 算子
該is運(yùn)算符用于檢查是否符合Protocol。它與以下語法一起使用:

let doesConformToProtocol = anObject is SomeCache

結(jié)果為布爾值,如果符合則評(píng)估為true,否則anObject為false。SomeCache

2、as 算子
該as運(yùn)營商用于鑄造一個(gè)給定的對(duì)象為Protocol對(duì)象??梢允褂脧?qiáng)制地進(jìn)行鑄造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?可以在沒有任何事先檢查的情況下隨時(shí)as!使用,但是只有在保證結(jié)果成功的情況下,才可以使用。理想情況下,應(yīng)通過對(duì)is操作員的事先檢查來保護(hù)它,該檢查必須已被評(píng)估為true。

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

3、& 算子
當(dāng)實(shí)例屬性或方法參數(shù)應(yīng)該符合多個(gè)時(shí)Protocols,一種選擇是用一個(gè)新的來表示其類型,該新的Protocol可以使用協(xié)議組合來定義。假設(shè)有兩個(gè)協(xié)議PrototolA,ProtocolB;我們可以構(gòu)建第三個(gè)協(xié)議,ProtocolC如下所示:

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

但是,只有在具有經(jīng)驗(yàn)意義時(shí),協(xié)議組合才有意義ProtocolC。每當(dāng)后者不具有任何本質(zhì)意義或沒有建立之間的一定的相關(guān)性PrototolA和ProtocolB,它是errosnoues到使用組合物。對(duì)于我們來說&,是最能表明一個(gè)事實(shí)的事實(shí),即給定對(duì)象必須同時(shí)符合多種協(xié)議和/或?qū)儆谀撤N具體的類別類型,這才是我們的最佳選擇。

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

&操作者可提供多種類型之間被施加的是至少一個(gè)所述 操作數(shù)是一個(gè)Protocol和不超過一個(gè)操作數(shù)是一個(gè)類類型的。當(dāng)需要減少冗長程度時(shí),可以通過使用typealias:

typealias PropertyType = ProtocolA & ProtocolB & ProtocolB & AClass

協(xié)議一致性

重要的是要提到a Protocol并沒有在采用類型上強(qiáng)加屬性的存儲(chǔ)機(jī)制。協(xié)議get only 要求可以實(shí)現(xiàn)為stored (let和var)或computed屬性。類似地,get and set 要求 可以被實(shí)現(xiàn)為存儲(chǔ)屬性(VAR只)或作為計(jì)算的屬性提供分別提供兩個(gè)一組和一個(gè)get。
即使協(xié)議方法定義中不允許使用默認(rèn)參數(shù),符合類型也可以使用提供默認(rèn)參數(shù)的簽名來實(shí)現(xiàn)所需的方法。雖然編譯器將驗(yàn)證是否符合協(xié)議,在調(diào)用點(diǎn)的任何這些的協(xié)議對(duì)象的方法,一個(gè) 仍然需要通過所有參數(shù)。用默認(rèn)參數(shù)實(shí)現(xiàn)協(xié)議方法的唯一好處是,當(dāng)在對(duì)象上調(diào)用該方法時(shí),可以忽略那些參數(shù),這被視為其具體類型的表示,而不是協(xié)議對(duì)象。
為了說明前兩段中的要點(diǎn),讓我們考慮SomeCache定義如下的協(xié)議:

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)
}

并考慮一個(gè)符合它的結(jié)構(gòu):

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

默認(rèn)實(shí)現(xiàn) Default Implementation

提供給定要求(屬性或方法)的默認(rèn)實(shí)現(xiàn),即 任何采用類型的類型都必須使用擴(kuò)展名。如果您提供所有要求的實(shí)現(xiàn),則任何合格類型都可以采用該協(xié)議,而無需支付任何額外的代碼費(fèi)用,只需將類型標(biāo)記為符合協(xié)議即可。所需方法或?qū)傩缘奶囟ㄓ陬愋偷膶?shí)現(xiàn)取代了默認(rèn)實(shí)現(xiàn)。

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
}

當(dāng)您發(fā)現(xiàn)自己編寫默認(rèn)實(shí)現(xiàn)時(shí),特別是沒有編碼或使用諸如此類的偽代碼的方法要求,print,通常是不正確的接口隔離的征兆。的確,如單詞所規(guī)定的那樣,默認(rèn)實(shí)現(xiàn)是實(shí)現(xiàn)而不是不實(shí)現(xiàn)。后者意味著并非所有預(yù)期的符合類型都不需要該方法,因此,您應(yīng)該考慮將協(xié)議分成許多并使用協(xié)議組成為需要提供所組成的所有方法和屬性的符合類型定義合適的協(xié)議。協(xié)議。
注意:我們將討論Objc Swift協(xié)議中的可選方法,這些方法可以幫助解決上述問題。

符合第三方類型

只有當(dāng)一個(gè)類型擁有協(xié)議時(shí),即在當(dāng)前模塊中對(duì)其進(jìn)行定義時(shí),才可以將其標(biāo)記為符合協(xié)議。但是,對(duì)于不屬于Swift,Objc,第三方庫,框架或外部模塊一部分的類型,必須使用type extension完成一致性。后者將負(fù)責(zé)提供未 擁有類型的原始實(shí)現(xiàn)尚未提供的所有要求。
讓我們假設(shè)String在Swift中定義的類型。如果我們希望將字符串對(duì)象作為DisplayableError對(duì)象拋出,則必須使用如下擴(kuò)展名

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

上面提到的一致性類型是無條件的,因?yàn)樗鼘⒈凰蠸tring類型采用。我們說的是什么conditional conformance 時(shí)候?qū)f(xié)議要求實(shí)現(xiàn)的類型有限制。假設(shè)協(xié)議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)
  }
}

注意:我們可以使用以下默認(rèn)實(shí)現(xiàn)來實(shí)現(xiàn)上述目的:

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

注意:如示例中突出顯示的那樣,當(dāng)約束是具體 類型時(shí),我們使用;當(dāng)約束是協(xié)議時(shí),我們必須使用。 == is
在這些擴(kuò)展中,我們可以分別self用作Array <Double>類型的對(duì)象或符合的對(duì)象Numeric,該對(duì)象 允許執(zhí)行所需的所有操作,以類似地實(shí)現(xiàn)使用條件符合性所做的事情。僅僅因?yàn)榻o定類型或其擴(kuò)展名可以提供所有協(xié)議r要求,并不能使其符合協(xié)議;協(xié)議的一致性只能通過明確的采用來保證,絕不暗示。為了“激活”一致性,我們需要明確聲明Array符合BoundedSummableCollection。

extension Array: BoundedSummableCollection where Double == Int {}

提供初始化要求

初始化程序要求既可以指定為初始化程序,也可以作為便捷初始化程序來實(shí)現(xiàn),required除最終類外,強(qiáng)制需要修飾符,因?yàn)椴荒軐⑵錃w類。

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

推薦

基礎(chǔ)文章推薦

經(jīng)典教程推薦

上新

技術(shù)源碼推薦

推薦文章

CoreData篇

Combine篇

TextField篇

JSON文件篇


一篇文章系列

技術(shù)交流

QQ:3365059189
SwiftUI技術(shù)交流QQ群:518696470

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容