背景:
我正在构建一个带有服务层的应用程序,该服务层应返回相似的模型(例如,所有具有text: String
属性,在协议TextModel
中定义)。该服务拥有一个存储库,用于查找和返回符合TextModel
的具体类型的模型。存储库需要保留内部工作的具体类型信息。我希望保持服务免受模型的具体类型的影响,因此我不必为每种模型类型重复它。编译器不允许我这样做......
问题:
我在Playground中简化的以下代码将无法编译:
enum Result<T> {
case success(T)
case error
}
// Model Layer
protocol TextModel {
var text: String { get }
}
struct Person: TextModel {
let text: String
}
// Service Layer
class TextModelService {
let repository: TextModelRepositoryType
init(repository: TextModelRepositoryType) {
self.repository = repository
}
func find(completion: @escaping (Result<TextModel>) -> ()) {
repository.find(completion: completion)
}
}
// Repository Layer
protocol TextModelRepositoryType {
func find(completion: @escaping (Result<TextModel>) -> ())
}
protocol PersonRepositoryType {
func findPerson(completion: @escaping (Result<Person>) -> ())
}
class PersonRepository: PersonRepositoryType, TextModelRepositoryType {
func find(completion: @escaping (Result<TextModel>) -> ()) {
// ERROR HERE: "Cannot convert value of type '(Result<TextModel>) -> ()' to expected argument type '(Result<Person>) -> ()'"
findPerson(completion: completion)
}
func findPerson(completion: @escaping (Result<Person>) -> ()) {
let person = Person(text: "Adam")
completion(.success(person))
}
}
let repository = PersonRepository()
let service = TextModelService(repository: repository)
service.find { result in
// result should be a Result<Person>
}
我不明白为什么编译器声明它'Cannot convert value of type '(Result<TextModel>) -> ()' to expected argument type '(Result<Person>) -> ()'
当Person
肯定符合TextModel
...
有趣的是,当我删除Result
类型并在完成时只传递一个未包装的类型(例如(TextModel?) -> ()
)时,编译器没有任何疑虑。
我是否在Swift编译器中遇到了限制?或者我错过了什么?
因为Swift具有不变的泛型类型,即使T<A>
可以转换为T<B>
,A
也无法转换为B
。
这种情况下的解决方法是自己创建转换方法:
enum Result<T> {
case success(T)
case error
func cast<R>() -> Result<R>? {
switch self {
case .error: return .error
case .success(let t) where t is R: return .success(t as! R)
default: return nil
}
}
}
extension Result where T : TextModel {
func convertToTextModel() -> Result<TextModel> {
switch self {
case .error: return .error
case .success(let t): return .success(t)
}
}
}
然后在find
中调用这些转换方法:
func find(completion: @escaping (Result<TextModel>) -> ()) {
findPerson(completion: { completion($0.convertToTextModel()) })
}
在来电方面:
let repository = PersonRepository()
let service = TextModelService(repository: repository)
service.find { result in
if let person: Result<Person> = result.cast() {
// ...
}
}