是否有可能满足Swift协议并添加默认参数?

Dov*_*Dov 39 swift swift2

如果你有这样的协议:

protocol Messaging {
    func sendMessage(message: String)
}
Run Code Online (Sandbox Code Playgroud)

有没有办法在类这样的类中满足它:

class Messager: Messaging {
    func sendMessage(message: String, count: Int = 1) {}
}
Run Code Online (Sandbox Code Playgroud)

这将是很好的,因为通过添加默认参数来满足协议的结果签名.有没有办法让它与Swift 2一起使用?

这是一个简化的例子.让我们说,为了论证,协议是固定的.解决方案只能更新Messager类.我的目标是能够sendMessage()像这样打电话:

let m: Messaging = Messager()
m.sendMessage("")
Run Code Online (Sandbox Code Playgroud)

我发现完成这个(并满足编译器)的唯一方法是重载,如下所示:

class Messager: Messaging {
    func sendMessage(message: String) {
        self.sendMessage(message, count: 1)
    }

    func sendMessage(message: String, count: Int = 1) {}
}
Run Code Online (Sandbox Code Playgroud)

这种方法的问题是我的默认值然后在两个地方指定,我失去了Swift默认参数的主要优点.

hha*_*amm 51

Swift 3中你可以使用扩展来解决这个问题,但它有点难看.希望在下一个快速版本中获得更好的解决方案.

import UIKit

protocol TestProtocol {
    func testFunction(a:Int, b:Int?) -> String
}

extension TestProtocol
{
    func testFunction(a:Int, b:Int? = nil) -> String {
        return testFunction(a:a, b:b)
    }
}

class TestClass: TestProtocol
{
    func testFunction(a:Int, b:Int?) -> String {
        return "a:\(a), b:\(b)"
    }
}

func testit(testProtocol: TestProtocol) {
    print(testProtocol.testFunction(a:10)) // will print a:10, b:nil
    print(testProtocol.testFunction(a:10, b:20)) // will print a:10, b:Optional(20)
}

let t = TestClass()
testit(testProtocol: t)
Run Code Online (Sandbox Code Playgroud)


小智 16

如果有人仍在寻找答案,这个链接帮助我:

https://oleb.net/blog/2016/05/default-arguments-in-protocols/

基本上,原始函数定义包括您需要的所有参数:

protocol Messaging {
    func sendMessage(message: String, count: Int)
}
Run Code Online (Sandbox Code Playgroud)

并且您的扩展提供默认值,使用这些默认值调用原始协议函数:

extension Messaging {
    func sendMessage(message: String, count: Int = 1) {
        sendMessage(message, count)
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 在我脑海中,这创造了一个无限循环:) (7认同)
  • 如果您像我一样在协议函数中将 count 设为可选,那么无限循环不仅在您的脑海中...... ;-) (2认同)
  • 尽管如此,如果符合类型 * 没有 * 提供它自己的实现,那么 * 将 * 是一个无限循环,因为扩展将被分派到。这是此解决方案的一个相当严重的问题,并且在链接的博客文章中通过为扩展方法使用不同的名称来避免(因此它不满足作为默认实现的要求)。 (2认同)

cra*_*777 12

使用Swift 2,您现在可以像这样扩展协议并为其提供默认实现

protocol Messageable {
    func sendMessage(message: String)
}

extension Messageable {
    func sendMessage(message: String, count: Int = 1) {
        // Do your default implementation
    }
}
Run Code Online (Sandbox Code Playgroud)

我还在了解这一点,所以我不能100%确定这对你的发送消息示例有什么用处,但我相信这就是你要找的东西.

你可以用swift 2中的协议做很多新的酷事.

观看苹果的演示文稿非常好

https://developer.apple.com/videos/play/wwdc2015-408/

并阅读这些

http://matthijshollemans.com/2015/07/22/mixins-and-traits-in-swift-2/

http://code.tutsplus.com/tutorials/protocol-oriented-programming-in-swift-2--cms-24979

http://www.raywenderlich.com/109156/introducing-protocol-oriented-programming-in-swift-2


dfr*_*fri 6

您可以向您的协议添加类型别名,以在您的协议蓝图函数中表示可能的不同参数类型.sendMessage。在下面的示例中,我明确指定了第二个参数既没有内部名称也没有外部名称。

由于您有两个类型别名,您可以使用两种不同的类型(Messenger在下面的示例中)将这个蓝图实现为一个,或者简单地将第二个参数(AnotherMessenger在示例中)作为()具有默认值的空元组类型()(您可以想到这个作为具有 void 值的 void 类型)。

protocol Messaging {
    typealias T
    typealias U
    func sendMessage(message: T, _ _ : U)
}

/* Class where you make use of 2nd argument */
class Messager: Messaging {

    func sendMessage(message: String, _ count: Int) {
        print(message + "\(count)")
    }
}

/* Class where you ignore 2nd argument */
class AnotherMessager : Messaging {

    func sendMessage(message: String, _ _ : () = ()) {
        print(message)
    }
}

/* Tests */
var a = Messager()
a.sendMessage("Hello world #", 1)
// prints "Hello World #1"

var b = AnotherMessager()
b.sendMessage("Hello world")
// prints "Hello World"
Run Code Online (Sandbox Code Playgroud)

这是尽可能接近我可以让您模拟您在问题和下面的评论中描述的行为。我将在下面留下替代解决方案,以防它们可以帮助具有类似想法但不那么严格的设计约束的其他人。


另一种选择:不完全是您要求的行为,但您可以使用匿名闭包作为协议蓝图函数的单个参数,其中此关闭不带任何参数但返回一个Any对象数组,您可以在sendMessage如您所愿。

protocol Messaging {
    func sendMessage(@autoclosure messages: ()->[Any])
}

class Messager: Messaging {
    func sendMessage(@autoclosure messages: ()->[Any]) {
        for message in messages() {
            print(message, terminator: "")
        }
    }
}

var a = Messager()
a.sendMessage([String("Hello "), String("World "), String("Number "), Int(1)])
// prints "Hello World Number 1"
Run Code Online (Sandbox Code Playgroud)

另一种选择是必须将sendMessage(..)协议中的功能蓝图分开Messaging,一个有附加参数,一个没有附加参数count。此后,您通过协议扩展为这两个函数添加默认(虚拟)实现Messaging。您的类Messager将遵守Messaging协议,即使sendMessage(..)它根本没有任何实现;如果没有,则使用默认实现。最后只对sendMessage你希望在你的类中使用的函数做一个详细的实现。

protocol Messaging {
    func sendMessage(message: String)
    func sendMessage(message: String, count: Int)
}

/* Extend blueprints with default dummy implementations */
extension Messaging {
    func sendMessage(message: String) { }
    func sendMessage(message: String, count: Int = 1) { }
}

class Messager: Messaging {
    func sendMessage(message: String, count: Int = 1) {
        print(message + "\(count)")
    }
}

var a = Messager()
a.sendMessage("Hello world #")
// prints "Hello World #1"
Run Code Online (Sandbox Code Playgroud)

但是请注意,您的类的实例会将这两个sendMessage函数都列为可用的类方法;其中一个是你的函数,另一个是虚拟的默认实现。


编辑之前关于不同类型参数的旧答案(我将把它留在这里,因为在所有参数都是相同类型的情况下,它可能是一个可能的替代方案)

最后,您可以使用可变参数

protocol Messaging {
    func sendMessage(messages: String...)
}

class Messager: Messaging {
    func sendMessage(messages: String...) {
        for message in messages {
            print(message)
        }
    }
}

var a = Messager()

a.sendMessage("Hello", "World", "!")
Run Code Online (Sandbox Code Playgroud)

可变参数接受零个或多个指定类型的值。您可以使用可变参数来指定在调用函数时可以向参数传递不同数量的输入值。通过在参数的类型名称后插入三个句点字符 (...) 来编写可变参数。