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

qeeaahzv  于 2022-11-06  发布在  Swift
关注(0)|答案(4)|浏览(203)

如果你有这样的协议:

protocol Messaging {
    func sendMessage(message: String)
}

有什么办法可以满足它在这样一个类中:

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

这将是一个很好的选择,因为通过添加默认参数可以满足协议的签名结果。有什么方法可以让它在Swift 2中工作吗?
这是一个简化的例子。为了便于讨论,我们假设协议是固定的。解决方案只能更新Messager类。我的目标是能够像这样调用sendMessage()

let m: Messaging = Messager()
m.sendMessage("")

我发现实现这一点(并使编译器满意)的唯一方法是使用如下重载:

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

    func sendMessage(message: String, count: Int = 1) {}
}

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

pvcm50d1

pvcm50d11#

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

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)

然而,这会导致一个问题,如果某个类不符合协议,它不会导致编译错误,而是导致无限循环。
一个稍微好一点的解决方案(在我看来)是将默认参数封装在第二个函数中,如下所示:

import Foundation

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

extension TestProtocol
{
    // omit the second parameter here
    func testFunction(a: Int) -> String {
        return testFunction(a: a, b: nil) // <-- and use the default parameter here
    }
}

class TestClass: TestProtocol
{
   func testFunction(a: Int, b: Int?) -> String 
   {
       return "testFunction(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)
}
print(Date())
let t = TestClass()
testit(testProtocol: t)

通过这种方式,编译器将在类不符合协议时发出通知,并且也不会以无限循环结束。

sirbozc5

sirbozc52#

如果有人还在寻找这个问题的答案,这个链接帮了我的忙:
https://oleb.net/blog/2016/05/default-arguments-in-protocols/
基本上,原始函数定义包括您需要的所有参数:

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

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

extension Messaging {
    func sendMessage(message: String, count: Int = 1) {
        sendMessage(message, count)
    }
}
1l5u6lss

1l5u6lss3#

使用Swift 2,您现在可以像这样扩展您的协议,并为其给予一个默认实现

protocol Messageable {
    func sendMessage(message: String)
}

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

我还在学习这方面的知识,所以我不能100%确定这将如何与您的发送消息的例子,但我相信这是你正在寻找的。
在Swift 2中,你可以用协议做很多新的酷事情。
观看Apple的演示,非常精彩:
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

pkln4tw6

pkln4tw64#

您可以在协议中添加类型别名,以表示协议蓝图函数.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"

这是我能让你模拟你在问题和下面的评论中描述的行为的最接近的方法。我将在下面留下替代的解决方案,以防它们能帮助其他有类似想法但设计约束不那么严格的人。
另一个选项:这并不完全符合您的要求,但您可以使用匿名闭包作为protocol-blueprinted函数的单个参数,其中该闭包不接受任何参数,但返回一个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"

另一种选择是必须在您的协议Messaging中分离函数sendMessage(..)的蓝图,一个带有附加参数count,另一个不带有附加参数count。此后,您可以添加默认通过扩展X1 M10 N1 X协议实现这些功能的 * 两者 *。您的类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"

但是请注意,类的示例将把sendMessage函数都列为可用的类方法;其中一个是您的函数,另一个是伪默认实现。

  • 在编辑不同类型参数之前的旧答案(我将其留在此处,因为在所有参数都是相同类型的情况下,它可能是一个替代方案)*

最后,您可以使用variadic parameters

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", "!")

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

相关问题