如何在Swift协议中定义可选方法?


359

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


1
我终于激励自己更新我的答案,您可以重新考虑接受它。
akashivskyy

@akashivskyy我接受您的回答,因为它可以更好地展示所有可用的选项以及它们的优缺点。
塞尔文,

Answers:


503

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该类的类才能符合此类协议。没有结构,没有枚举,没有关联的类型。

  • 您必须始终通过可选调用或检查符合类型是否实现了可选方法来检查是否实现了可选方法。如果您经常调用可选方法,这可能会引入很多样板。


17
快速看一下使用扩展的可选方法的改进方法(下)
Daniel Kanaan

1
以及如何测试实例中是否支持可选协议方法?respondsToSelector
devios1 2016年

3
只是不要这样做!由于某种原因,Swift不支持协议中的可选方法。
fpg1503

2
此方法不支持参数中的可选参数。即您无法做到这一点optional func doSomething(param: Int?)
SoftDesigner 2016年

4
当然,几年后的今天,这个答案基本上是错误的。今天,在Swift中,您只需为默认实现添加扩展,这是Swift的基本方面。(如下面所有现代的答案所示。)如今,添加objc标志是错误的。
Fattie

394

在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/


2
这可能是在Swift中最干净的方法。太糟糕了,它在Swift 2.0之前不起作用。
2015年

13
@MattQuiros我发现实际上您确实需要在协议定义中声明该函数,否则,在符合协议的类中,no-op扩展功能不会被覆盖。
伊恩·皮尔斯

3
@IanPearce是正确的,这似乎是设计使然。在WWDC的“面向协议的编程”演讲(408)中,他们谈到了主协议中的方法,即为符合类型的“定制点”。必需的定制点不会在扩展中接收到定义。一个可选点。协议中通常不应该自定义的方法在扩展名中完全声明/定义,以禁止符合标准的类型进行自定义,除非您专门将其转换dynamicType以表明您想要该符合者的自定义实现。
matthias

4
@FranklinYu您可以执行此操作,但是实际上您在不需要使用“抛出”的情况下污染了API设计。我更喜欢“微协议”的想法。例如,每种方法都确认一个协议,然后您可以检查:对象是否为协议
Darko

3
@Antoine我认为您应该将扩展名中的功能公开,因为协议中的功能根据定义是公开的。当协议在其模块之外使用时,您的解决方案将无法工作。
Vadim Eisenberg

39

由于有一些关于如何使用的答案 可选修饰符@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


1
做得好!这应该是正确的答案,因为它可以解决问题而无需使用Objective-C运行时。
卢卡斯

真是太好了!
tania_S

来自迅速的文档-“扩展提供的具有默认实现的协议要求与可选协议要求是不同的。尽管一致类型不必提供它们各自的实现,但是具有默认实现的要求可以在没有可选链接的情况下被调用。”
麦斯奥斯

34

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

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

3
使用此解决方案,您将无法从任何协议功能中进行自我访问。在某些情况下,这可能会导致问题!
乔治·格林

1
@GeorgeGreen您可以访问自己。将函数变量标记为lazy,并在闭包内部使用捕获列表
Zag 2014年

只有类,协议,方法和属性才能使用@objc。如果在@ objc协议方法定义中使用Enum参数,则注定要失败。
khunshan 2014年

1
@khunshan,此方法不需要标记@objc,您指的是什么?
Zag 2015年

它有关swum和objc之间不能使用枚举的主题的信息,for其他语句可以使用@objc关键字进行桥接。
khunshan

34

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

设置协议:

@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?()

2
这应该是正确的答案。简单,干净和说明性。
梯队

我同意,这个答案简短,甜美,直截了当。谢谢!
LuAndre '16

31

Swift 3.0中

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

这样可以节省您的时间。


6
@objc现在为什么所有成员都需要提供任何来源的信息?
DevAndArtist

@Gautam Sareriya:您认为这种方法最好还是创建空扩展方法?
eonist

我尝试使用带有required标志的方法,但有错误:required只能在'init'声明中使用。

27
  • 您需要optional在每种方法之前添加关键字。
  • 但是请注意,要使此功能起作用,必须使用@objc属性标记您的协议。
  • 这进一步暗示该协议将适用于类而不适用于结构。

较小的校正(太小而无法编辑!),但应为“可选”而不是“ @可选”
Ali Beadle

5
这还要求您将实现协议的类标记为@objc,而不仅仅是协议。
Johnathon Sullinger

13

具有协议继承的纯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()
    }
}

11

为了说明Antoine回答的机理:

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"

8

我认为在询问如何实现可选协议方法之前,您应该问为什么要实现一种可选协议。

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

有关更多信息,请参见https://useyourloaf.com/blog/swift-optional-protocol-methods/,其中对此进行了很好的概述。


这个。这是坚持“I”原则,在SOLID软件开发原则
埃里克

7

最初的问题与主题略有出入,但它建立在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?
}

我还在博客上写了如何在Swift协议中执行可选属性,如果Swift 2发行版中的内容有所变化,我将不断对其进行更新。


5

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

@objc protocol InterViewDelegate:class {

    @objc optional func optfunc()  //    This is optional
    func requiredfunc()//     This is required 

}

3

这是一个非常简单的示例,仅用于快速类,而不用于结构或枚举。请注意,协议方法是可选的,有两个级别的可选链接在起作用。同样,采用该协议的类在其声明中也需要@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!)
Unheilig 2015年

2
如果我们编写的协议具有这样的非可选方法: protocol CollectionOfDataDelegate{ func indexDidChange(index: Int) } 那么您将在不带问号的情况下调用它: delegate?.indexDidChange(index!) 在协议中为方法设置可选要求时,将遵循该协议的Type可能不会实现该方法,因此?使用来检查实现,如果没有,程序将不会崩溃。@Unheilig
祝福之源

weak var delegate : CollectionOfDataDelegate?(确保参考力弱吗?)
阿尔菲·汉森

@BlessingLopes可以在delegate?用法中添加用法说明吗?该信息将来确实应该属于其他人。我想对此表示赞同,但实际上应该是答案。
Johnathon Sullinger

3

如果您想快速完成此操作,最好的方法是在返回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()
    }
}

那么您可以实现协议而无需定义每个函数


2

您可以通过两种方法在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具有一个称为扩展的功能,该功能使我们能够为那些我们希望是可选的方法提供默认实现。


0

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

struct MyAwesomeStruct {
    var myWonderfulFunction : Optional<(Int) -> Int> = nil
}

let squareCalculator =
    MyAwesomeStruct(myWonderfulFunction: { input in return input * input })
let thisShouldBeFour = squareCalculator.myWonderfulFunction!(2)

-1

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


-2

Optional Protocol快速定义,您应在该协议内的声明@objc之前Protocolattribute/ method声明之前使用关键字。下面是协议的可选属性的示例。

@objc protocol Protocol {

  @objc optional var name:String?

}

class MyClass: Protocol {

   // No error

}

2
尽管这可以回答问题,但最好添加一些有关此回答如何有助于解决问题的说明。请阅读如何写一个好的答案以了解更多信息。
Roshana Pitigala,

-23

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


编译器错误:“可选”属性只能应用于@objc协议的成员。
Selvin 2014年

3
@optional甚至都不是正确的关键字。它是optional,您必须使用属性声明类协议@objc
Johnathon Sullinger
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.