Допустим, у меня есть протокол:
public protocol Printable {
typealias T
func Print(val:T)
}
А вот и реализация
class Printer<T> : Printable {
func Print(val: T) {
println(val)
}
}
Я ожидал, что я должен иметь возможность использовать Printable
переменную для вывода таких значений:
let p:Printable = Printer<Int>()
p.Print(67)
Компилятор жалуется на эту ошибку:
«протокол« Printable »может использоваться только как общее ограничение, потому что он имеет требования типа Self или связанные с ним»
Я делаю что-то неправильно ? Как бы то ни было, чтобы это исправить?
**EDIT :** Adding similar code that works in C#
public interface IPrintable<T>
{
void Print(T val);
}
public class Printer<T> : IPrintable<T>
{
public void Print(T val)
{
Console.WriteLine(val);
}
}
//.... inside Main
.....
IPrintable<int> p = new Printer<int>();
p.Print(67)
РЕДАКТИРОВАТЬ 2: Пример реального мира того, что я хочу. Обратите внимание, что это не будет компилироваться, но представляет то, чего я хочу достичь.
protocol Printable
{
func Print()
}
protocol CollectionType<T where T:Printable> : SequenceType
{
.....
/// here goes implementation
.....
}
public class Collection<T where T:Printable> : CollectionType<T>
{
......
}
let col:CollectionType<Int> = SomeFunctiionThatReturnsIntCollection()
for item in col {
item.Print()
}
Ответы:
Как указывает Томас, вы можете объявить свою переменную, вообще не задавая типа (или явно указав ее как тип
Printer<Int>
. Но вот объяснение того, почему у вас не может быть типаPrintable
протокола.Вы не можете рассматривать протоколы со связанными типами как обычные протоколы и объявлять их как отдельные типы переменных. Чтобы подумать, почему, рассмотрим этот сценарий. Предположим, вы объявили протокол для хранения некоторого произвольного типа и последующего его получения:
// a general protocol that allows for storing and retrieving // a specific type (as defined by a Stored typealias protocol StoringType { typealias Stored init(_ value: Stored) func getStored() -> Stored } // An implementation that stores Ints struct IntStorer: StoringType { typealias Stored = Int private let _stored: Int init(_ value: Int) { _stored = value } func getStored() -> Int { return _stored } } // An implementation that stores Strings struct StringStorer: StoringType { typealias Stored = String private let _stored: String init(_ value: String) { _stored = value } func getStored() -> String { return _stored } } let intStorer = IntStorer(5) intStorer.getStored() // returns 5 let stringStorer = StringStorer("five") stringStorer.getStored() // returns "five"
Хорошо, пока все хорошо.
Теперь основная причина того, что тип переменной будет протоколом, который реализует тип, а не фактическим типом, заключается в том, чтобы вы могли назначать разные типы объектов, которые все соответствуют этому протоколу, одной и той же переменной и получать полиморфные поведение во время выполнения в зависимости от того, чем на самом деле является объект.
Но вы не можете этого сделать, если у протокола есть связанный тип. Как следующий код будет работать на практике?
// as you've seen this won't compile because // StoringType has an associated type. // randomly assign either a string or int storer to someStorer: var someStorer: StoringType = arc4random()%2 == 0 ? intStorer : stringStorer let x = someStorer.getStored()
Какой тип
x
будет в приведенном выше коде ? AnInt
? ИлиString
? В Swift все типы должны быть исправлены во время компиляции. Функция не может динамически переходить от возврата одного типа к другому на основе факторов, определенных во время выполнения.Вместо этого вы можете использовать только
StoredType
как общее ограничение. Предположим, вы хотите распечатать любой сохраненный тип. Вы можете написать такую функцию:func printStoredValue<S: StoringType>(storer: S) { let x = storer.getStored() println(x) } printStoredValue(intStorer) printStoredValue(stringStorer)
Это нормально, потому что во время компиляции компилятор как бы записывает две версии
printStoredValue
: одну дляInt
s и одну дляString
s. В этих двух версиях,x
как известно , относится к определенному типу.источник
p
переменной принтеры разных типов, а затем передадите в них недопустимые типыprint
? Исключение времени выполнения?var someStorer: StoringType<Int>
илиvar someStorer: StoringType<String>
и решить обозначенную вами проблему.Есть еще одно решение, которое не упоминалось по этому вопросу, и оно использует технику, называемую стиранием типа . Чтобы получить абстрактный интерфейс для универсального протокола, создайте класс или структуру, которая обертывает объект или структуру, которая соответствует протоколу. Класс-оболочка, обычно называемый Any {имя протокола}, сам соответствует протоколу и реализует свои функции, перенаправляя все вызовы внутреннему объекту. Попробуйте пример ниже на детской площадке:
import Foundation public protocol Printer { typealias T func print(val:T) } struct AnyPrinter<U>: Printer { typealias T = U private let _print: U -> () init<Base: Printer where Base.T == U>(base : Base) { _print = base.print } func print(val: T) { _print(val) } } struct NSLogger<U>: Printer { typealias T = U func print(val: T) { NSLog("\(val)") } } let nsLogger = NSLogger<Int>() let printer = AnyPrinter(base: nsLogger) printer.print(5) // prints 5
Этот тип
printer
известенAnyPrinter<Int>
и может использоваться для абстрагирования любой возможной реализации протокола принтера. Хотя AnyPrinter не является технически абстрактным, его реализация - это просто переход к реальному реализующему типу и может использоваться для отделения реализующих типов от типов, использующих их.Следует отметить, что
AnyPrinter
не требуется явно сохранять базовый экземпляр. Фактически, мы не можем, поскольку не можем заявить,AnyPrinter
что у нас естьPrinter<T>
собственность. Вместо этого мы получаем указатель функции_print
на базовуюprint
функцию. Вызовbase.print
без его вызова возвращает функцию, в которой base каррирована как собственная переменная и, таким образом, сохраняется для будущих вызовов.Также следует помнить, что это решение, по сути, является еще одним уровнем динамической диспетчеризации, что означает небольшое снижение производительности. Кроме того, для экземпляра стирания типа требуется дополнительная память поверх базового экземпляра. По этим причинам стирание типов не является бесплатной абстракцией.
Очевидно, что есть некоторая работа по настройке стирания типов, но это может быть очень полезно, если требуется абстракция общего протокола. Этот шаблон можно найти в стандартной быстрой библиотеке с такими типами, как
AnySequence
. Дополнительная литература: http://robnapier.net/erasureБОНУС:
Если вы решите, что хотите внедрить одну и ту же реализацию
Printer
везде, вы можете предоставить удобный инициализатор, дляAnyPrinter
которого внедряет этот тип.extension AnyPrinter { convenience init() { let nsLogger = NSLogger<T>() self.init(base: nsLogger) } } let printer = AnyPrinter<Int>() printer.print(10) //prints 10 with NSLog
Это может быть простой и СУХОЙ способ выразить инъекции зависимостей для протоколов, которые вы используете в своем приложении.
источник
fatalError()
), описанный в других руководствах по стиранию типов.Решение вашего обновленного варианта использования:
(кстати,
Printable
это уже стандартный протокол Swift, поэтому вы, вероятно, захотите выбрать другое имя, чтобы избежать путаницы)Чтобы наложить определенные ограничения на разработчиков протокола, вы можете ограничить псевдонимы протокола. Итак, чтобы создать коллекцию протоколов, требующую, чтобы элементы были доступны для печати:
// because of how how collections are structured in the Swift std lib, // you’d first need to create a PrintableGeneratorType, which would be // a constrained version of GeneratorType protocol PrintableGeneratorType: GeneratorType { // require elements to be printable: typealias Element: Printable } // then have the collection require a printable generator protocol PrintableCollectionType: CollectionType { typealias Generator: PrintableGenerator }
Теперь, если вы хотите реализовать коллекцию, которая может содержать только печатаемые элементы:
struct MyPrintableCollection<T: Printable>: PrintableCollectionType { typealias Generator = IndexingGenerator<T> // etc... }
Тем не менее, это, вероятно, не очень полезно, поскольку вы не можете ограничить существующие структуры коллекции Swift подобным образом, а только те, которые вы реализуете.
Вместо этого вам следует создать общие функции, которые ограничивают их ввод коллекциями, содержащими печатаемые элементы.
func printCollection <C: CollectionType where C.Generator.Element: Printable> (source: C) { for x in source { x.print() } }
источник