1

我想为设置页面创建一个通用视图控制器。现在设置来自 JSON,但稍后可能会切换实现,这就是我想要协议的原因。例如,LanguageSetting协议是空的,但通过使用它,我仍然可以为将来保留类型安全,而不必满足于特定的实现(例如 JSON 解码)。

// Protocols

protocol Query {
    associatedtype Result
    func handleResult(with data: Data) -> Result
}

protocol Setting {
    var name: String { get }
    var icon: URL? { get }
}

protocol LanguageSetting: Setting {
}

protocol CountrySetting: Setting {
}

// Implementations

struct LanguageSettingQuery: Query {
    func handleResult(with data: Data) -> [LanguageSetting] {
        return try! JSONDecoder().decode([JSONLanguageSetting].self, from: data)
    }
}

struct CountrySettingQuery: Query {
    func handleResult(with data: Data) -> [CountrySetting] {
        return try! JSONDecoder().decode([JSONCountrySetting].self, from: data)
    }
}

struct JSONLanguageSetting: LanguageSetting, Decodable {
    var name: String
    var icon: URL?
}

struct JSONCountrySetting: CountrySetting, Decodable {
    var name: String
    var icon: URL?
}

// A generic settings view controller
class LocaleViewController<LocaleQuery: Query>: UIViewController where
LocaleQuery.Result: Sequence, LocaleQuery.Result.Element: Setting {
    private var settingItems = [Setting]()

    init(query: LocaleQuery) {
        settingItems = query.handleResult(with: Data()) as! [Setting]
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

let localeVC = LocaleViewController(query: LanguageSettingQuery())

上面是我创建的一个非常简单的实现Playgrounds。问题是最后一行:

let localeVC = LocaleViewController(query: LanguageSettingQuery())

抛出错误:

不支持使用 'LanguageSetting' 作为符合协议 'Setting' 的具体类型

关于如何解决这个问题的任何想法?

附带说明:

为什么这里需要向下转换?泛型类型约束不足以确保这一点吗?

settingItems = query.handleResult(with: Data()) as! [Setting]
4

1 回答 1

0

错误

协议定义类型;但是,它们与可以定义的其他三种类型(类、结构和枚举)不同,因为它们不能符合其他协议或要求协议实现某些 API。您在此处收到的错误指出了这一点:LanguageSetting协议类型不(也不能)符合泛型类中Setting泛型 where 子句所要求的协议。请注意,协议继承并不等同于一致性。由于您正在从 JSON 中创建实例,因此请替换where ... , LocaleQuery.Result.Element: SettingLocaleViewController<LocaleQuery: Query>JSONLanguageSettingLanguageSettingQuery

func handleResult(with data: Data) -> [LanguageSetting] {
    return try! JSONDecoder().decode([JSONLanguageSetting].self, from: data)
}

func handleResult(with data: Data) -> [JSONLanguageSetting] {
    return try! JSONDecoder().decode([JSONLanguageSetting].self, from: data)
}

错误将得到解决。编译器根据方法的返回类型推断出关联Result类型[LanguageSetting]在前者中,而[JSONLanguageSetting]在后者中handle(with:)

为什么你必须投

编译器强制您进行强制转换,因为它不能保证泛型类型LocaleQueryArray<Setting>. 虽然Array<Setting>确实符合Sequence其元素为Setting类型的协议,但如此定义的 where 子句不够具体,无法保证query.handleData(with: Data())返回数组。它只知道它返回“某种符合Query 和Sequence 的类型,其Element 类型是Setting 协议类型”。这很可能是自定义类型。考虑以下:

struct CustomIterator<T>: IteratorProtocol where T: Setting {
    typealias Element = T
    func next() -> T? { nil }
}

class MyWeirdType<S>: Sequence where S: Setting {
    typealias Iterator = CustomIterator<S>
    typealias Element = S
    func makeIterator() -> CustomIterator<S> { CustomIterator<S>() }
}

class WeirdQuery<Q>: Query where Q: Setting {
    typealias Result = MyWeirdType<Q>
    func handleResult(with data: Data) -> MyWeirdType<Q> { MyWeirdType<Q>() }
}

let localeVC = LocaleViewController(query: WeirdQuery<JSONLanguageSetting>())

这将使程序崩溃,因为它将尝试在无法完成的初始化程序中MyWeirdType<JSONLanguageSetting>进行强制转换。如果您期待一个数组,请尝试以下操作:Array<Setting>LocaleViewController

class LocaleViewController<LocaleQuery: Query>: UIViewController where
LocaleQuery.Result == Array<Setting> {
    private var settingItems = [Setting]()

    init(query: LocaleQuery) {

        settingItems = query.handleResult(with: Data()) // Knows that the return is [Setting]

        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

LocaleViewController由于 where 子句比现在更具体,因此注释变得不那么通用了。

于 2020-06-04T15:30:24.937 回答