【问题标题】:How to define optional methods in Swift protocol?如何在 Swift 协议中定义可选方法?
【发布时间】:2014-07-24 19:50:53
【问题描述】:

在 Swift 中可以吗?如果没有,那么有解决方法吗?

【问题讨论】:

    标签: ios swift optional-parameters swift-protocols swift-extensions


    【解决方案1】:

    1。使用默认实现(首选)。

    protocol MyProtocol {
        func doSomething()
    }
    
    extension MyProtocol {
        func doSomething() {
            /* return a default value or just leave empty */
        }
    }
    
    struct MyStruct: MyProtocol {
        /* no compile error */
    }
    

    优势

    • 不涉及 Objective-C 运行时(嗯,至少不明确)。这意味着您可以使结构、枚举和非NSObject 类符合它。此外,这意味着您可以利用强大的泛型系统。

    • 在遇到符合此类协议的类型时,您始终可以确保满足所有要求。它始终是具体实现或默认实现。这就是“接口”或“合同”在其他语言中的行为方式。

    缺点

    • 对于非Void 的要求,您需要有一个合理的默认值,这并不总是可行的。但是,当你遇到这个问题时,要么是这样的需求真的没有默认实现,要么是你在 API 设计时犯了错误。

    • 您无法区分默认实现和根本没有实现,至少在没有使用特殊返回值解决该问题的情况下。考虑以下示例:

      protocol SomeParserDelegate {
          func validate(value: Any) -> Bool
      }
      

      如果你提供一个只返回true 的默认实现——乍一看没问题。现在,考虑下面的伪代码:

      final class SomeParser {
          func parse(data: Data) -> [Any] {
              if /* delegate.validate(value:) is not implemented */ {
                  /* parse very fast without validating */
              } else {
                  /* parse and validate every value */
              }
          }
      }
      

      没有办法实现这样的优化——你不知道你的委托是否实现了一个方法。

      尽管有许多不同的方法可以解决这个问题(使用可选闭包、不同操作的不同委托对象等等),但该示例清楚地说明了问题。


    2。使用@objc optional

    @objc protocol MyProtocol {
        @objc optional func doSomething()
    }
    
    class MyClass: NSObject, MyProtocol {
        /* no compile error */
    }
    

    优势

    • 不需要默认实现。您只需声明一个可选方法或变量即可。

    缺点

    • 它要求所有符合标准的类型都与 Objective-C 兼容,从而严重限制了协议的功能。这意味着,只有继承自 NSObject 的类才能符合此类协议。没有结构,没有枚举,没有关联的类型。

    • 您必须始终检查是否实现了可选方法,方法是可选地调用或检查符合的类型是否实现了它。如果您经常调用可选方法,这可能会引入大量样板。

    【讨论】:

    • 因为“可选要求”(它本身就是一个矛盾的说法)违背了合同的想法+阻止了静态调度。 Swift 核心团队的一位成员(我不记得具体是谁)说 Swift 只有“可选协议要求”,因为它是 Obj-C 互操作所必需的。此外,在 swift-evolution 的早期,原生“可选要求”的想法很快就被抛弃了。
    【解决方案2】:

    在 Swift 2 及更高版本中,可以添加协议的默认实现。这在协议中创建了一种新的可选方法。

    protocol MyProtocol {
        func doSomethingNonOptionalMethod()
        func doSomethingOptionalMethod()
    }
    
    extension MyProtocol {
        func doSomethingOptionalMethod(){ 
            // leaving this empty 
        }
    }
    

    这不是创建可选协议方法的好方法,但可以让您在协议回调中使用结构体。

    我在这里写了一个小总结: https://www.avanderlee.com/swift-2-0/optional-protocol-methods/

    【讨论】:

    • 这可能是 Swift 中最简洁的方式。可惜它在 Swift 2.0 之前不起作用。
    • @MattQuiros 我发现您实际上确实需要在协议定义中声明该函数,否则在符合协议的类中不会覆盖无操作扩展函数。
    • @IanPearce 是正确的,这似乎是设计使然。在 WWDC 的“面向协议的编程”演讲(408)中,他们讨论了主协议中的方法是提供给符合类型的“定制点”。所需的定制点没有在扩展中收到定义;一个可选点。协议上通常不应自定义的方法在扩展中完全声明/定义,以禁止符合类型的自定义除非您专门转换为其 dynamicType 以表明您想要符合者的自定义实现。跨度>
    • @FranklinYu 你可以这样做,但是你会在实际上不需要的地方用“抛出”来污染你的 API 设计。我更喜欢“微协议”的想法。例如。每种方法都确认一个协议,然后您可以检查:如果对象是协议
    • @Antoine 我认为您应该将扩展中的功能公开,因为根据定义,协议中的功能是公开的。当协议将在其模块之外使用时,您的解决方案将不起作用。
    【解决方案3】:

    这是委托模式的具体示例。

    设置您的协议:

    @objc protocol MyProtocol:class
    {
        func requiredMethod()
        optional func optionalMethod()
    }
    
    class MyClass: NSObject
    {
        weak var delegate:MyProtocol?
    
        func callDelegate()
        {
            delegate?.requiredMethod()
            delegate?.optionalMethod?()
        }
    }
    

    将委托设置为一个类并实现协议。看到可选方法不需要实现。

    class AnotherClass: NSObject, MyProtocol
    {
        init()
        {
            super.init()
    
            let myInstance = MyClass()
            myInstance.delegate = self
        }
    
        func requiredMethod()
        {
        }
    }
    

    一个重要的事情是可选方法是可选的,需要一个“?”打电话的时候。提到第二个问号。

    delegate?.optionalMethod?()
    

    【讨论】:

    • 这应该是正确的答案。简单、干净且易于解释。
    【解决方案4】:

    由于有一些关于如何使用可选修饰符@objc属性定义可选需求协议的答案,我将给出一个关于如何使用协议扩展定义的示例任择议定书。

    下面的代码是 Swift 3.*.

    /// Protocol has empty default implementation of the following methods making them optional to implement:
    /// `cancel()`
    protocol Cancelable {
    
        /// default implementation is empty.
        func cancel()
    }
    
    extension Cancelable {
    
        func cancel() {}
    }
    
    class Plane: Cancelable {
      //Since cancel() have default implementation, that is optional to class Plane
    }
    
    let plane = Plane()
    plane.cancel()
    // Print out *United Airlines can't cancelable*
    

    请注意,Objective-C 代码不能调用协议扩展方法,更糟糕的是 Swift 团队不会修复它。 https://bugs.swift.org/browse/SR-492

    【讨论】:

    • 干得好!这应该是正确的答案,因为它在不涉及 Objective-C 运行时的情况下解决了问题。
    • 来自 swift 文档-“扩展提供的具有默认实现的协议要求与可选协议要求不同。虽然符合类型不必提供它们自己的实现,但可以调用具有默认实现的要求没有可选的链接。”
    【解决方案5】:

    此处涉及将协议标记为“@objc”的其他答案在使用特定于 swift 的类型时不起作用。

    struct Info {
        var height: Int
        var weight: Int
    } 
    
    @objc protocol Health {
        func isInfoHealthy(info: Info) -> Bool
    } 
    //Error "Method cannot be marked @objc because the type of the parameter cannot be represented in Objective-C"
    

    为了声明与 swift 兼容的可选协议,请将函数声明为变量而不是 func。

    protocol Health {
        var isInfoHealthy: (Info) -> (Bool)? { get set }
    }
    

    然后实现协议如下

    class Human: Health {
        var isInfoHealthy: (Info) -> (Bool)? = { info in
            if info.weight < 200 && info.height > 72 {
                return true
            }
            return false
        }
        //Or leave out the implementation and declare it as:  
        //var isInfoHealthy: (Info) -> (Bool)?
    }
    

    然后您可以使用“?”检查功能是否已经实现

    func returnEntity() -> Health {
        return Human()
    }
    
    var anEntity: Health = returnEntity()
    
    var isHealthy = anEntity.isInfoHealthy(Info(height: 75, weight: 150))? 
    //"isHealthy" is true
    

    【讨论】:

    • 使用此解决方案,您无法从任何协议函数中访问 self。在某些情况下,这可能会导致问题!
    • @GeorgeGreen 您可以访问自己。将函数变量标记为惰性并使用capture list inside the closure
    • 只有类、协议、方法和属性可以使用@objc。如果你在 @objc 协议方法定义中使用了 Enum 参数,你就完蛋了。
    • @khunshan,这个方法不需要标记@objc,你指的是什么?
    • 它是关于在 swift 和 objc 之间不能使用枚举的主题的信息,其他语句可以用 @objc 关键字桥接。
    【解决方案6】:

    Swift 3.0

    @objc protocol CounterDataSource {
        @objc optional func increment(forCount count: Int) -> Int
        @objc optional var fixedIncrement: Int { get }
    }
    

    这将节省您的时间。

    【讨论】:

    • 现在所有成员都需要@objc 的任何来源?
    • @Gautam Sareriya:您认为这种方法最好还是创建空扩展方法?
    • 我尝试使用带有required 标志的方法,但出现错误:required 只能用于'init' 声明。
    【解决方案7】:
    • 您需要在每个方法之前添加optional关键字。
    • 但是请注意,要使其正常工作,您的协议必须标有 @objc 属性。
    • 这进一步暗示此协议将适用于类,但不适用于结构。

    【讨论】:

    • 小修正(太小无法编辑!)但它应该是“可选”而不是“@可选”
    • 这也要求你将实现协议的类标记为@objc,而不仅仅是协议。
    【解决方案8】:

    具有协议继承的纯 Swift 方法:

    //Required methods
    protocol MyProtocol {
        func foo()
    }
    
    //Optional methods
    protocol MyExtendedProtocol: MyProtocol {
        func bar()
    }
    
    class MyClass {
        var delegate: MyProtocol
        func myMethod() {
            (delegate as? MyExtendedProtocol).bar()
        }
    }
    

    【讨论】:

      【解决方案9】:

      为了说明安托万回答的机制:

      protocol SomeProtocol {
          func aMethod()
      }
      
      extension SomeProtocol {
          func aMethod() {
              print("extensionImplementation")
          }
      }
      
      class protocolImplementingObject: SomeProtocol {
      
      }
      
      class protocolImplementingMethodOverridingObject: SomeProtocol {
          func aMethod() {
              print("classImplementation")
          }
      }
      
      let noOverride = protocolImplementingObject()
      let override = protocolImplementingMethodOverridingObject()
      
      noOverride.aMethod() //prints "extensionImplementation"
      override.aMethod() //prints "classImplementation"
      

      【讨论】:

        【解决方案10】:

        您可以通过两种方式在 swift 协议中创建可选方法。

        1 - 第一个选项是使用@objc 属性标记您的协议。虽然这意味着它只能被类采用,但这确实意味着您将单个方法标记为可选,如下所示:

        @objc protocol MyProtocol {
            @objc optional func optionalMethod()
        }
        

        2 - 更快捷的方式:此选项更好。编写什么都不做的可选方法的默认实现,像这样。

        protocol MyProtocol {
            func optionalMethod()
            func notOptionalMethod()
        }
        
        extension MyProtocol {
        
            func optionalMethod() {
                //this is a empty implementation to allow this method to be optional
            }
        }
        

        Swift 有一个称为扩展的功能,它允许我们为那些我们希望成为可选的方法提供默认实现。

        【讨论】:

          【解决方案11】:

          我认为在问如何你可以实现一个可选的协议方法之前,你应该问为什么你应该实现一个。

          如果我们将 swift 协议视为经典面向对象编程中的接口,可选方法没有多大意义,也许更好的解决方案是创建默认实现,或者将协议分离为一组协议(它们之间可能有一些继承关系)来表示协议中可能的方法组合。

          如需进一步阅读,请参阅https://useyourloaf.com/blog/swift-optional-protocol-methods/,它对此事提供了很好的概述。

          【讨论】:

          【解决方案12】:

          与最初的问题略有偏离,但它建立在 Antoine 的想法之上,我认为它可能会对某人有所帮助。

          您还可以将计算属性设置为具有协议扩展的结构的可选属性。

          您可以将协议上的属性设为可选

          protocol SomeProtocol {
              var required: String { get }
              var optional: String? { get }
          }
          

          在协议扩展中实现虚拟计算属性

          extension SomeProtocol {
              var optional: String? { return nil }
          }
          

          现在您可以使用实现或不实现可选属性的结构

          struct ConformsWithoutOptional {
              let required: String
          }
          
          struct ConformsWithOptional {
              let required: String
              let optional: String?
          }
          

          我还写了optional properties in Swift protocols on my blog 的操作方法,如果 Swift 2 版本发生变化,我会不断更新。

          【讨论】:

            【解决方案13】:

            如何创建可选和必需的委托方法。

            @objc protocol InterViewDelegate:class {
            
                @objc optional func optfunc()  //    This is optional
                func requiredfunc()//     This is required 
            
            }
            

            【讨论】:

              【解决方案14】:

              这是一个非常简单的示例,仅适用于 swift 类,不适用于结构或枚举。 请注意,协议方法是可选的,有两个级别的可选链接在起作用。 采用该协议的类也需要在其声明中添加@objc 属性。

              @objc protocol CollectionOfDataDelegate{
                 optional func indexDidChange(index: Int)
              }
              
              
              @objc class RootView: CollectionOfDataDelegate{
              
                  var data = CollectionOfData()
              
                 init(){
                    data.delegate = self
                    data.indexIsNow()
                 }
              
                func indexDidChange(index: Int) {
                    println("The index is currently: \(index)")
                }
              
              }
              
              class CollectionOfData{
                  var index : Int?
                  weak var delegate : CollectionOfDataDelegate?
              
                 func indexIsNow(){
                    index = 23
                    delegate?.indexDidChange?(index!)
                  }
              
               }
              

              【讨论】:

              • 您能否再描述一下“两个级别的可选操作”部分,即:delegate?.indexDidChange?(index!)
              • 如果我们将协议编写为具有这样的非可选方法:protocol CollectionOfDataDelegate{ func indexDidChange(index: Int) },那么您将在不带问号的情况下调用它:delegate?.indexDidChange(index!) 当您在协议,符合它的类型可能不会实现该方法,因此? 用于检查实现,如果没有则程序不会崩溃。 @Unheilig
              • weak var delegate : CollectionOfDataDelegate?(确保弱引用?)
              • @BlessingLopes 您能否将您对delegate? 用法的解释添加到您的答案中?这些信息应该真正属于未来的其他人。我想对此表示赞同,但该信息确实应该在答案中。
              【解决方案15】:

              如果您想以纯 Swift 的方式执行此操作,最好的方法是在您返回 Swift 类型(例如带有 Swift 类型的 struct)时提供默认的具体实现

              示例:

              struct magicDatas {
                  var damagePoints : Int?
                  var manaPoints : Int?
              }
              
              protocol magicCastDelegate {
                  func castFire() -> magicDatas
                  func castIce() -> magicDatas
              }
              
              extension magicCastDelegate {
                  func castFire() -> magicDatas {
                      return magicDatas()
                  }
              
                  func castIce() -> magicDatas {
                      return magicDatas()
                  }
              }
              

              那么你可以在不定义每个func

              的情况下实现协议

              【讨论】:

                【解决方案16】:

                一种选择是将它们存储为可选函数变量:

                struct MyAwesomeStruct {
                    var myWonderfulFunction : Optional<(Int) -> Int> = nil
                }
                
                let squareCalculator =
                    MyAwesomeStruct(myWonderfulFunction: { input in return input * input })
                let thisShouldBeFour = squareCalculator.myWonderfulFunction!(2)
                

                【讨论】:

                  【解决方案17】:

                  我们先来了解一下区别

                  第一个例子——如果你写UITableViewDataSource那么你需要强制写两个方法——这是默认协议的快捷方式

                  第二个例子 - 如果你写UITableViewDelegate 并且知道它没有显示红色错误,请添加所有委托方法。这取决于您要使用哪种方法。我们可以叫optional method

                  让我们通过一个例子来理解这一点

                  First Swift Way 默认协议方法

                  class ContactModel{
                      var firstname: String?
                      var lastname: String?
                  }
                  
                  protocol ContactDataSource: AnyObject{
                      func contactConfiguration(contact: ContactModel)
                  }
                  
                  class ViewController: ContactDataSource{
                      func contactConfiguration(contact: ContactModel) {
                          print(contact)
                      }
                  }
                  

                  第二种方法 - 可选协议

                  @objc
                  class UserModel: NSObject{
                      var firstname: String = ""
                  }
                  
                  @objc protocol UserDataSource{
                      func contactConfiguration(user: UserModel)
                     @objc optional func userInfo(user: UserModel)
                  }
                  
                  class ViewController: UserDataSource{
                      func contactConfiguration(user: UserModel) {
                          print(user)
                      }
                  }
                  

                  注意:如果您可以在可选协议中看到,我还没有编写 userInfo 方法,所以由您决定。这意味着有和没有向类添加方法 它工作正常。 - 作为协议中的可选方法调用

                  你必须声明类和协议 @objc 属性,它只适用于 Class 而不是 struct!

                  第三种方法 - 使用扩展的可选协议

                  注意:你可以使用 Struct 或 Class

                  class UserModel{
                      var firstname: String = ""
                  }
                  

                  struct UserModel{
                      var firstname: String = ""
                  }
                  

                  protocol UserDataSource{
                      func contactConfiguration(user: UserModel)
                  }
                  
                  extension UserDataSource{
                      func userInfo(user: UserModel){}
                  }
                  
                  class myview: UserDataSource{
                      func contactConfiguration(user: UserModel) {
                          print(user)
                      }
                  }
                  

                  【讨论】:

                    【解决方案18】:

                    在协议中定义函数并为该协议创建扩展,然后为要用作可选的函数创建空实现。

                    【讨论】:

                      【解决方案19】:

                      要在swift中定义OptionalProtocol,你应该在Protocol声明之前使用@objc关键字,并在该协议中使用attribute/method声明。 下面是一个协议的可选属性示例。

                      @objc protocol Protocol {
                      
                        @objc optional var name:String?
                      
                      }
                      
                      class MyClass: Protocol {
                      
                         // No error
                      
                      }
                      

                      【讨论】:

                      • 虽然这可能会回答问题,但最好添加一些关于此答案如何有助于解决问题的描述。请阅读How do I write a good answer了解更多信息。
                      【解决方案20】:

                      @optional放在方法或属性前面。

                      【讨论】:

                      • 编译器错误:“可选”属性只能应用于 @objc 协议的成员。
                      • @optional 甚至不是正确的关键字。它是optional,你必须用@objc属性声明类协议。
                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2017-01-17
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2019-07-09
                      • 1970-01-01
                      相关资源
                      最近更新 更多