2015-12-06 2 views
4

У меня есть протокол с typealias:Использование протокола с typealias как свойство

protocol Archivable { 
    typealias DataType 

    func save(data: DataType, withNewName newName: String) throws 
    func load(fromFileName fileName: String) throws -> DataType 
} 

и класс, который соответствует этому протоколу:

class Archiver: Archivable { 
    typealias DataType = Int 

    func save(data: DataType, withNewName newName: String) throws { 
     //saving 
    } 

    func load(fromFileName fileName: String) throws -> DataType { 
     //loading 
    } 
} 

, и я хотел бы использовать Archivable как свойство в другом классе, как:

class TestClass { 

    let arciver: Archivable = Archiver() //error here: Protocol 'Archivable' can only be used as a generic constraint because it has Self or associated type requiments 
} 

но он терпит неудачу с

Протокол долгосрочного хранение »может быть использована только в качестве общего ограничения, поскольку он имеет Самость или связанное requiments типа

Моя цель состоит в том, что TestClass должен видеть только Archiver, как Archiveable, так что если я хочу, чтобы изменить я просто должен создать новый класс, который соответствует Archivable, поскольку он задан как свойство в TestClass, но я не знаю, возможно ли это, и если да, то как.

И я бы хотел избежать использования AnyObject вместо DataType.

+3

чтения: [ссылка] (http://krakendev.io/blog/generic-protocols-and-their-shortcomings) –

+0

@RMenke спасибо за ссылку, так что в основном этот подход не работает: \ –

+0

На данный момент генерики только частично реализованы. Одна из целей Swift 3 - сделать его полностью родовым. Создание всего родового - это последняя тенденция, но она просто не играет хорошо с тем, как работает Swift. Каждый тип должен быть известен во время компиляции, а генерики в Swift просто не могут этого сделать в каждом случае. Я лично стараюсь избегать дженериков вообще. –

ответ

3

В зависимости от того, что вы на самом деле пытается это сделать, это может работать с использованием стирания типа. Если вы следуете инструкциям по ссылке R Menke, размещенной в комментариях, вы можете добиться того, что вы пытаетесь сделать. Поскольку ваше свойство в TestClass кажется пустым, я собираюсь предположить, что вы уже знаете тип DataType во время компиляции. Прежде всего, необходимо установить тип стерта Archivable класс следующим образом:

class AnyArchiver<T>: Archivable { 
    private let _save: ((T, String) throws -> Void) 
    private let _load: (String throws -> T) 

    init<U: Archivable where U.DataType == T>(_ archiver: U) { 
     _save = archiver.save 
     _load = archiver.load 
    } 

    func save(data: T, withNewName newName: String) throws { 
     try _save(data, newName) 
    } 

    func load(fromFileName fileName: String) throws -> T { 
     return try _load(fileName) 
    } 
} 

Многое, как Свифт AnySequence, вы будете в состоянии обернуть Archiver в этом классе в вашем TestClass так:

class TestClass { 
    let archiver = AnyArchiver(Archiver()) 
} 

Через вывод типа Swift будет вводить TestClass 'архиватор пусть константа как AnyArchiver<Int>. Делая это таким образом будет убедиться, что вы не должны создавать десяток протоколов, чтобы определить, что DataType, как StringArchiver, ArrayArchiver, IntArchiver и т.д. Вместо этого, вы можете выбрать, чтобы определение переменные с обобщениями, как это:

let intArchiver: AnyArchiver<Int> 
let stringArchiver: AnyArchiver<String> 
let modelArchiver: AnyArchiver<Model> 

, а не дублировать код, как это:

protocol IntArchivable: Archivable { 
    func save(data: Int, withNewName newName: String) throws 
    func load(fromFileName fileName: String) throws -> Int 
} 
protocol StringArchivable: Archivable { 
    func save(data: String, withNewName newName: String) throws 
    func load(fromFileName fileName: String) throws -> String 
} 
protocol ModelArchivable: Archivable { 
    func save(data: Model, withNewName newName: String) throws 
    func load(fromFileName fileName: String) throws -> Model 
} 

let intArchiver: IntArchivable 
let stringArchiver: StringArchivable 
let modelArchiver: ModelArchivable 

Я написал пост по этому поводу, что goes into even more detail в случае, если возникнут какие-либо проблемы с этим подходом. Надеюсь, это поможет!

+0

Я читал вам сообщение об общих протоколах, и мне это понравилось. Вы хорошо объясняете вещи. Но меня смущает одно - отсутствие слов «generic protocol» в книге «Быстрый язык программирования», потому что в Swift отсутствуют общие протоколы :) – mixel

+0

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

1

При попытке объявить и присвоить archiver:

let archiver: Archivable = Archiver() 

он должен иметь конкретный тип. Archivable не является конкретным типом, потому что это протокол с соответствующим типом.

Из «Swift языка программирования (Swift 2)» Книга:

Ассоциированного типа дает имя заполнителя (или псевдоним) к типу, который используются в качестве части протокола. Фактический тип, используемый для этого связанного типа , не указывается до тех пор, пока протокол не будет принят.

Таким образом, вы должны объявить протокол, который наследуется от Archivable и задает соответствующий тип:

protocol IntArchivable: Archivable { 
    func save(data: Int, withNewName newName: String) throws 

    func load(fromFileName fileName: String) throws -> Int 
} 

И тогда вы можете принять этот протокол:

class Archiver: IntArchivable { 
    func save(data: Int, withNewName newName: String) throws { 
     //saving 
    } 

    func load(fromFileName fileName: String) throws -> Int { 
     //loading 
    } 
} 

Есть не действительно общие протоколы Swift сейчас, поэтому вы не можете объявить archiver следующим образом:

let archiver: Archivable<Int> = Archiver() 

Но дело в том, что вам не нужно это делать, и я объясню, почему.

Из «Swift языка программирования (Swift 2)» Книга:

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

Так в основном, когда вы хотите объявить archiver, как Archivable<Int> вы имеете в виду, что вы не хотите, чтобы некоторые кусков кода с помощью archiver знать о его конкретном классе и иметь доступ к другим своим методам, свойствам и т.д. Очевидно, что этот фрагмент кода должен быть обернут отдельным классом, методом или функцией, и archiver должен быть передан там как параметр, и этот класс, метод или функция будут носить общий характер.

В вашем случае TestClass может быть общим, если вы передаете archivable с помощью параметра инициализации:

class TestClass<T, A: Archivable where A.DataType == T> { 
    private let archivable: A 

    init(archivable: A) { 
     self.archivable = archivable 
    } 

    func test(data: T) { 
     try? archivable.save(data, withNewName: "Hello") 
    } 
} 

или может иметь общий метод, который принимает archivable в качестве параметра:

class TestClass {   
    func test<T, A: Archivable where A.DataType == T>(data: T, archivable: A) { 
     try? archivable.save(data, withNewName: "Hello") 
    } 
} 
+0

"При попытке объявить и присвоить архиватор: ' пусть архиватор: = Archiver долгосрочного хранения() ' он должен иметь конкретный тип." Это не совсем правильно. Эта конкретная строка кода совершенно легальна. Проблема кроется в другом месте. –

+0

@ VinceO'Sullivan Эта строка кода не является законной и дает ошибку компилятора. Протокол «Архивируемый» может использоваться только в качестве общего ограничения, поскольку он имеет Self или связанные требования типа. Вопрос заключается в этой ошибке. – mixel

+0

Ошибка компиляции заключается не в том, что эта строка является незаконной, а из-за того, как был определен класс и/или протокол. См. Мой ответ ниже. Эта строка компилируется просто отлично. –

0

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

typealias DataType = Int 

protocol Archivable { 
    var data: DataType { get set } 

    func save(data: DataType, withNewName newName: String) throws 
    func load(fromFileName fileName: String) throws -> DataType 
} 

class Archiver: Archivable { 
    var data:DataType = 0 

    func save(data: DataType, withNewName newName: String) throws { 
     //saving 
    } 

    func load(fromFileName fileName: String) throws -> DataType { 
     return data 
    } 
} 

class TestClass { 
    let arciver: Archivable = Archiver() 
} 
+0

Вы просто разместили 'typealias' в глобальной области? Это делает его не общим –

+0

Да, я это сделал. Да. Неизмененная линия, которую вы сказали, была незаконной, теперь законна. –

+1

Решим все проблемы вроде этого .... Он компилируется отлично, но он не делает того, что нам нужно. –

Смежные вопросы