Подтвердить что ты не робот

Возможно ли удовлетворить протокол Swift и добавить аргументы по умолчанию?

Если у вас такой протокол:

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.

4b9b3361

Ответ 1

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

Ответ 2

С 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

Ответ 3

Если кто-то все еще ищет ответ на этот вопрос, эта ссылка помогла мне:

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

Ответ 4

Вы можете добавить typealiases к вашему протоколу, чтобы отображать возможные типы аргументов в вашей синей настройке протокола .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"

Это так близко, что я могу заставить вас моделировать поведение, которое вы описываете в вопросе и в комментариях ниже. Я оставлю альтернативные решения ниже, если они могут помочь кому-то другому с подобной мыслью, но менее жестким конструктивным ограничениям.


Другая опция: не совсем то поведение, о котором вы просите, но вы можете использовать анонимное закрытие в качестве единственного параметра для вашей функции протокола, где это закрытие не принимает аргументов, а возвращает массив объектов 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"

Другой альтернативой было бы отделить чертежи функции 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"

Обратите внимание, однако, что экземпляры вашего класса будут отображать обе функции 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", "!")

Парадигменный параметр принимает ноль или более значений заданного типа. Вы используете переменный параметр, чтобы указать, что параметр может быть после вызова функции изменялось количество входных значений. Напишите переменные параметры, вставив три символа периода (...) после имени типа параметров.