检查两个对象是否实现了 Swift 协议及其关联类型
Check if two objects implement a Swift protocol and its associated type
我有一个 Swift 3 项目,我在其中声明了一个具有如下关联类型的协议:
protocol ViewModelContainer {
associatedtype ViewModelType
var viewModel: ViewModelType! { get set }
}
我想检查两个对象是否实现 ViewModelContainer
及其关联类型 ViewModelType
以 'generic' 方式进行分配。
理想情况下,我想做这样的事情:
if let container = container as? ViewModelContainer, let model = model as? container.ViewModelType {
container.viewModel = model
}
但我无法将 container
转换为 ViewModelContainer
:
Protocol 'ViewModelContainer' can only be used as a generic constraint because it has Self or associated type requirements
我目前的解决方法是直接回退到特定的 类 及其关联类型,但这使我的代码非常冗长且容易出错:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? MediaPlaySelectionViewController, let vm = sender as? MediaPlaySelectionViewModel {
vc.viewModel = vm
}
if let vc = segue.destination as? SearchResultsViewController, let vm = sender as? SearchResultsViewModel {
vc.viewModel = vm
}
if let vc = segue.destination as? ReviewDetailsViewController, let vm = sender as? ReviewDetailsViewModel {
vc.viewModel = vm
}
if let vc = segue.destination as? ReviewComposerViewController, let vm = sender as? ReviewComposerViewModel {
vc.viewModel = vm
}
}
我尝试使用通用 UIViewController
s,但因为 而卡住了,因此无法在 Storyboard 中使用。
这里是将 associatedtype ViewModelType
更改为协议的想法。
protocol ViewModelProtocol {
}
protocol ViewModelContainer {
var viewModel: ViewModelProtocol? { get set }
}
class MediaPlaySelectionViewModel: ViewModelProtocol {
var title: String?
func play() {
print("playing")
}
}
class SearchResultsViewModel: ViewModelProtocol {
var results: [String]?
}
class MediaPlaySelectionViewController: UIViewController, ViewModelContainer {
var viewModel: ViewModelProtocol?
// So the view itself know which kind of vm it wants.
var myViewModel: MediaPlaySelectionViewModel? {
return viewModel as? MediaPlaySelectionViewModel
}
override func viewWillAppear(_ animated: Bool) {
print(myViewModel?.title ?? "Undefined")
}
}
class SearchResultsViewController: UIViewController, ViewModelContainer {
var viewModel: ViewModelProtocol?
// So the view itself know which kind of vm it wants.
var myViewModel: SearchResultsViewModel? {
return viewModel as? SearchResultsViewModel
}
override func viewWillAppear(_ animated: Bool) {
print(myViewModel?.results?.joined(separator: ", ") ?? "No Result")
}
}
class MenuViewController: UITableViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// NOTE: Swift doesn't allow me to use 'let' here
if var container = segue.destination as? ViewModelContainer, let cell = sender as? UITableViewCell, let vm = viewModel(for: cell) {
container.viewModel = vm
}
}
// NOTE: One difficulty here, how could you decide which ViewModel to prepare? I guess you need a Factory.
func viewModel(for cell: UITableViewCell) -> ViewModelProtocol! {
if let index = tableView.indexPath(for: cell) {
if index.item == 0 {
let vm = MediaPlaySelectionViewModel()
vm.title = "My Video"
return vm
}
else if index.item == 1 {
let vm = SearchResultsViewModel()
vm.results = ["Apple", "Banana"]
return vm
}
}
return nil
}
}
这比我预期的要棘手(所以我删除了之前的 post 以避免混淆)但我相信这对你有用:
protocol ViewModelContainerVC
{
mutating func setModel(_ :Any)
}
protocol ViewModelContainer:class,ViewModelContainerVC
{
associatedtype ViewModelType
var viewModel: ViewModelType! { get set }
}
extension ViewModelContainer
{
mutating func setModel(_ model:Any)
{ if model is ViewModelType { viewModel = model as! ViewModelType } }
}
然后您可以使用 ViewModelContainerVC 进行类型转换和赋值:
if let container = container as? ViewModelContainerVC
{
container.setModel(model)
}
[EDIT] 以供将来参考,这里与 Bool return 的类型兼容性相同:
protocol ViewModelContainerVC
{
@discardableResult mutating func setModel(_ :Any) -> Bool
}
extension ViewModelContainer
{
@discardableResult mutating func setModel(_ model:Any) -> Bool
{
if let validModel = model as? ViewModelType
{ viewModel = validModel; return true }
return false
}
}
这将允许组合条件:
if var container = container as? ViewModelContainerVC,
container.setModel(model)
{ ... }
我有一个 Swift 3 项目,我在其中声明了一个具有如下关联类型的协议:
protocol ViewModelContainer {
associatedtype ViewModelType
var viewModel: ViewModelType! { get set }
}
我想检查两个对象是否实现 ViewModelContainer
及其关联类型 ViewModelType
以 'generic' 方式进行分配。
理想情况下,我想做这样的事情:
if let container = container as? ViewModelContainer, let model = model as? container.ViewModelType {
container.viewModel = model
}
但我无法将 container
转换为 ViewModelContainer
:
Protocol 'ViewModelContainer' can only be used as a generic constraint because it has Self or associated type requirements
我目前的解决方法是直接回退到特定的 类 及其关联类型,但这使我的代码非常冗长且容易出错:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? MediaPlaySelectionViewController, let vm = sender as? MediaPlaySelectionViewModel {
vc.viewModel = vm
}
if let vc = segue.destination as? SearchResultsViewController, let vm = sender as? SearchResultsViewModel {
vc.viewModel = vm
}
if let vc = segue.destination as? ReviewDetailsViewController, let vm = sender as? ReviewDetailsViewModel {
vc.viewModel = vm
}
if let vc = segue.destination as? ReviewComposerViewController, let vm = sender as? ReviewComposerViewModel {
vc.viewModel = vm
}
}
我尝试使用通用 UIViewController
s,但因为
这里是将 associatedtype ViewModelType
更改为协议的想法。
protocol ViewModelProtocol {
}
protocol ViewModelContainer {
var viewModel: ViewModelProtocol? { get set }
}
class MediaPlaySelectionViewModel: ViewModelProtocol {
var title: String?
func play() {
print("playing")
}
}
class SearchResultsViewModel: ViewModelProtocol {
var results: [String]?
}
class MediaPlaySelectionViewController: UIViewController, ViewModelContainer {
var viewModel: ViewModelProtocol?
// So the view itself know which kind of vm it wants.
var myViewModel: MediaPlaySelectionViewModel? {
return viewModel as? MediaPlaySelectionViewModel
}
override func viewWillAppear(_ animated: Bool) {
print(myViewModel?.title ?? "Undefined")
}
}
class SearchResultsViewController: UIViewController, ViewModelContainer {
var viewModel: ViewModelProtocol?
// So the view itself know which kind of vm it wants.
var myViewModel: SearchResultsViewModel? {
return viewModel as? SearchResultsViewModel
}
override func viewWillAppear(_ animated: Bool) {
print(myViewModel?.results?.joined(separator: ", ") ?? "No Result")
}
}
class MenuViewController: UITableViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// NOTE: Swift doesn't allow me to use 'let' here
if var container = segue.destination as? ViewModelContainer, let cell = sender as? UITableViewCell, let vm = viewModel(for: cell) {
container.viewModel = vm
}
}
// NOTE: One difficulty here, how could you decide which ViewModel to prepare? I guess you need a Factory.
func viewModel(for cell: UITableViewCell) -> ViewModelProtocol! {
if let index = tableView.indexPath(for: cell) {
if index.item == 0 {
let vm = MediaPlaySelectionViewModel()
vm.title = "My Video"
return vm
}
else if index.item == 1 {
let vm = SearchResultsViewModel()
vm.results = ["Apple", "Banana"]
return vm
}
}
return nil
}
}
这比我预期的要棘手(所以我删除了之前的 post 以避免混淆)但我相信这对你有用:
protocol ViewModelContainerVC
{
mutating func setModel(_ :Any)
}
protocol ViewModelContainer:class,ViewModelContainerVC
{
associatedtype ViewModelType
var viewModel: ViewModelType! { get set }
}
extension ViewModelContainer
{
mutating func setModel(_ model:Any)
{ if model is ViewModelType { viewModel = model as! ViewModelType } }
}
然后您可以使用 ViewModelContainerVC 进行类型转换和赋值:
if let container = container as? ViewModelContainerVC
{
container.setModel(model)
}
[EDIT] 以供将来参考,这里与 Bool return 的类型兼容性相同:
protocol ViewModelContainerVC
{
@discardableResult mutating func setModel(_ :Any) -> Bool
}
extension ViewModelContainer
{
@discardableResult mutating func setModel(_ model:Any) -> Bool
{
if let validModel = model as? ViewModelType
{ viewModel = validModel; return true }
return false
}
}
这将允许组合条件:
if var container = container as? ViewModelContainerVC,
container.setModel(model)
{ ... }