Swift поражает, но еще не зрело, поэтому существуют некоторые ограничения для компилятора, и среди них есть общие протоколы. Общие протоколы не могут использоваться как регулярные аннотации типов из-за соображений безопасности. Я нашел обходное решение на посту Гектора Матоса. Generic Protocols & Their ShortcomingsКак скомпилировать несколько стираемых модулей в swift?
Основная идея - стирание типа использования для преобразования общего протокола в общий класс, и это круто. Но я застрял при применении этой технологии к более сложным сценариям.
Предположим, что существует абстрактный источник, который производит данные, и абстрактную процедуру, которая обрабатывает эти данные и конвейер, который объединяет источник и процедуру, типы данных которой сопоставляются.
protocol Source {
associatedtype DataType
func newData() -> DataType
}
protocol Procedure {
associatedtype DataType
func process(data: DataType)
}
protocol Pipeline {
func exec() // The execution may differ
}
И я хочу, код клиента, чтобы быть простым, как:
class Client {
private let pipeline: Pipeline
init(pipeline: Pipeline) {
self.pipeline = pipeline
}
func work() {
pipeline.exec()
}
}
// Assume there are two implementation of Source and Procedure,
// SourceImpl and ProcedureImpl, whose DataType are identical.
// And also an implementation of Pipeline -- PipelineImpl
Client(pipeline: PipelineImpl(source: SourceImpl(), procedure: ProcedureImpl())).work()
Реализация Источник и порядок прост, так как они находятся на дне зависимых пакетов:
class SourceImpl: Source {
func newData() -> Int { return 1 }
}
class ProcedureImpl: Procedure {
func process(data: Int) { print(data) }
}
PITA появляется при внедрении трубопровода
// A concrete Pipeline need to store the Source and Procedure, and they're generic protocols, so a type erasure is needed
class AnySource<T>: Source {
private let _newData:() -> T
required init<S: Source>(_ source: S) where S.DataType == T {
_newData = source.newData
}
func newData() -> T { return _newData() }
}
class AnyProcedure<T>: Procedure {
// Similar to above.
}
class PipelineImpl<T>: Pipeline {
private let source: AnySource<T>
private let procedure: AnySource<T>
required init<S: Source, P: Procedure>(source: S, procedure: P) where S.DataType == T, P.DataType == T {
self.source = AnySource(source)
self.procedure = AnyProcedure(procedure)
}
func exec() {
procedure.process(data: source.newData())
}
}
Ух, На самом деле это работает! Я что, шучу? №
Я не удовлетворен этим, потому что initializer
из PipelineImpl
довольно общий, поэтому я хочу, чтобы он был в протоколе (я не прав с этой навязчивой идеей?). И это приводит к двум конца:
Протокол
Pipeline
будет общим.initializer
содержит предложение where, которое относится кplaceholder T
, поэтому мне нужно переместитьplaceholder T
в протокол какassociated type
. Затем протокол превращается в общий, что означает, что я не могу использовать его непосредственно в моем клиентском коде - может потребоваться стирание другого типа.Хотя я могу хлопотно писать другой тип стирание для протокола
Pipeline
, я не знаю, как бороться сinitializer function
, посколькуAnyPipeline<T>
класса должен реализовывать инициализатору относительно к протоколу, но это только thunk class на самом деле, который не должен реализовывать сам инициализатор.Хранить протокол
Pipeline
не общий. При написанииinitializer
какinit<S: Source, P: Procedure>(source: S, procedure: P) where S.DataType == P.DataType
я могу предотвратить протокол является универсальным. Это означает, что протокол только утверждает, что «Источник и процедура должны иметь один и тот же тип данных, и мне все равно, что это такое». Это имеет смысл, но я не смог реализовать конкретный класс, подтверждающий этот протокол
class PipelineImpl<T>: Protocol { private let source: AnySource<T> private let procedure: AnyProcedure<T> init<S: Source, P: Procedure>(source: S, procedure: P) where S.DataType == P.DataType { self.source = AnySource(source) // doesn't compile, because S is nothing to do with T self.procedure = AnyProcedure(procedure) // doesn't compile as well } // If I add S.DataType == T, P.DataType == T condition to where clasue, // the initializer won't confirm to the protocol and the compiler will complain as well }
Итак, как я мог бы справиться с этим?
Спасибо за чтение allll
.
Хороший вопрос, но может существовать некоторый различный заранее и после работы, чтобы сделать в различных 'функций exec'. Тем не менее, подход Хеймиша по-прежнему работает в этом случае, я думаю. Благодаря! –
@NandiinBao А, я вижу - я отредактировал свой ответ на счет для этого. С удовольствием помогу :) – Hamish