Скажем, я хочу представить некоторые функциональные возможности в качестве публичного API, но часть реализации относится к внутренним протоколам:

public protocol P {
    var foo: Int { get }
}

internal protocol Q {
    init(from: [String])
}

public struct S: P, Q {
    public var foo: Int = 0
    public init() {}    

    internal init(from: [String]) {
        precondition(from.count > 0)
        self.foo = Int(from[0])!
    }
}

Это может быть некоторый объект данных, который может сконструировать только мой собственный модуль (из некоторого представления данных), но который пользователи модуля могут использовать для своих собственных целей.

Скажем, я хочу предоставить какой-то сервис, который принимает такое значение и возвращает новый такой же тип:

public class ProviderOfThings {
    public func map<T: P>(before: T) -> T {
        return T(from: [String(before.foo + 1)])
    }
}

Это не компилируется; в T нет подходящего инициализатора.

Как я могу назвать конструктор, которого я (внутренне) знаю, чтобы быть там в общем виде?

1
Raphael 28 Фев 2017 в 19:59

2 ответа

Лучший ответ

Мы можем привести значение к внутреннему типу протокола и получить доступ к инициализатору оттуда:

public static func map<T: P>(before: T) -> T {
    return type(of: before as! Q).init(
        from: [String(before.foo + 1)]
    ) as! T
}

Мы можем даже обойтись без значения типа (спасибо, Хэмиш!):

public static func make<T: P>() -> T {
    return (T.self as! Q.Type).init(
        from: ["0"]
    ) as! T
}

Если есть (или могут быть) реализации P, которые также не соответствуют Q, приведение к Q должно быть защищено, очевидно:

guard let beforeQ = before as? Q else { ... }
// or
guard before is Q else { ... }

// respectively
guard T.self is Q.Type else { ... }

Другое приведение as! T безопасно: в конце концов мы вызываем инициализатор T. Поэтому, если оно охраняется, это решение не может вызвать ошибки времени выполнения.

Полный код с примерами можно найти здесь.

1
Community 23 Май 2017 в 12:09

Я думаю, что лучшим способом было бы объявить map следующим образом:

public func map<T: P & Q>(before: T) -> T

И сделайте Q общедоступным, возможно, добавив к нему префикс _, если он не должен использоваться внешним кодом.

0
emlai 28 Фев 2017 в 17:03