创建一个实现特定协议的对象数组。

4

TL;DR

我正在寻找一个类似于“所有子类为UIViewController且实现了协议MyProtocol的对象”的数组类型 (var array = [TheTypeImLookingFor]())。

Explanation

我正在构建一个向导视图,其中包含容器视图和嵌入的子视图(控制器)。只要我只有一个基础类型的子视图控制器,就没有问题。

由于屏幕内容的原因,我现在有一堆类型为MyTableViewController的视图控制器,它是UITableViewController的子类,以及其他具有常规UIViewControllers作为基础的视图控制器。

所有的视图控制器都有一个共同点。一个默认的数据属性myData: MyObject

我创建了一个包含这个属性的协议MyProtocol

现在,我必须将所有这些视图控制器合并成一个数组,以便将其用作向导步骤。只要我只访问视图控制器方法(数组项的类型为UIViewController),我就可以使用var viewControllers = [UIViewController](),或者如果我只想访问myData属性,我会将数组项类型更改为MyObject

但问题是,我必须从UIViewController和协议中访问方法。

这就是为什么我正在寻找类似于“所有子类为UIViewController且实现了协议MyProtocol的对象”的数组类型。

我尝试过:

  • var viewControllers = [UIViewController: MyProtocol]() // 是一个字典
  • `var viewControllers = UIViewController where MyProtocol
  • `var viewControllers = UIViewController.conforms(to: MyProtocol)
  • ...

但没有一个能像预期一样工作。


数组的元素必须同时被定义为 UIViewControllerMyProtocol 吗?否则,你可以将你需要的 UIViewController 方法放入 MyProtocol 中(或者创建一个单独的协议并使用协议组合)。 - Hamish
是的,它们必须同时存在。也许我的实现方式有误。 - Tobonaut
5个回答

2
据我所知,目前没有办法键入任何内容,以描述从给定类继承并符合给定协议的任何内容。
一种可能的hacky解决方法是创建一个包装类型,以便在需要将实例视为MyProtocol的情况下为您执行类型转换。
struct MyProtocolViewController {

    let base: UIViewController

    init<T : UIViewController>(_ base: T) where T : MyProtocol {
        self.base = base
    }

    func asMyProtocol() -> MyProtocol {
        return base as! MyProtocol
    }
}

现在您可以创建一个 [MyProtocolViewController],并且可以将元素视为 UIViewControllerMyProtocol
// given that ViewController and AnotherViewController conform to MyProtocol.
let viewControllers = [MyProtocolViewController(ViewController()),
                       MyProtocolViewController(AnotherViewController())]

for viewController in viewControllers {
    print(viewController.asMyProtocol().myData)
    print(viewController.base.prefersStatusBarHidden)
}

非常感谢大家!你们的想法帮了我很多。目前,我正在使用两个数组,一个用于未转换为 UIViewController 对象,另一个用于已转换(到协议)的对象。这很丑陋和愚蠢,但目前它能够工作。 - Tobonaut
@Tobonaut,我不建议使用并行数组来解决这个问题,尽管我承认似乎没有任何“完美”的解决方案。 - Hamish
嗯,好的。你认为我对此有错误的“想法”吗?我曾经是一名Java EE开发人员,也许我使用了错误的模式。 - Tobonaut
2
@Tobonaut 并行数组通常被认为是一种反模式,因为它使得很难保持它们同步,并且将本应该在一起的数据分离开来(例如,您的控制器类型为UIViewControllerMyProtocol)。通常最好将它们包含在适当的数据结构中,例如一个结构体。 - Hamish
听起来不错,我待会儿试一下。谢谢伙计。 - Tobonaut

1
你可以使用协议组合来创建一个占位协议,用于类的实现:
protocol UIViewControllerClass {}
extension UIViewController: UIViewControllerClass {}

protocol MyProtocol:class {}

class MySpecialVC:UIViewController,MyProtocol {}    

var viewControllers = [UIViewControllerClass & MyProtocol]()

viewControllers.append( MySpecialVC() )   

这涵盖了类型安全的部分,但是不允许您在没有进行类型转换的情况下访问UIViewController方法。您可以通过向协议添加一个带有类型的属性(适用于基类时)来减少类型转换的复杂性。
extension MyProtocol where Self: UIViewControllerClass
{
   var vc:UIViewController { return self as! UIViewController }
}

// accessing the view controller's methods would then only require insertion of a property name.
viewControllers.first!.vc.view

或者,您可以在占位符协议中定义需要调用的UIViewController方法,但如果您要使用许多这些方法,则可能很快变得繁琐和冗余。


0

为什么不简单地创建:

为什么不创建:

class ObservingViewController : UIViewController, MyProtocol {


}

var viewControllers : [ObservingViewController] = []

我认为 OP 希望这个数组的元素知道它们也是 UIViewController 的子类对象,如果仅使用有类型的协议对象,则不会这样。 - dfrib
是的,@dfri。那就是我的问题。 - Tobonaut
OP的一个类继承自UITableViewController,因此这不起作用。 - Hamish

0
你也可以创建一个定义所有需要的UIViewController函数的protocol。确保复制方法签名,否则您将不得不再次实现这些函数。
protocol UIViewControllerInteractions {
    //copy the signature from the methods you want to interact with here, e.g.
    var title: String? { get set }
}

然后,您可以扩展您现有的协议。

protocol MyProtocol: UIViewControllerInteractions { }

或者创建一个新的协议,扩展UIViewControllerInteractionsMyProtocol

protocol MyProtocolViewController: UIViewControllerInteractions, MyProtocol { }

现在,当您扩展您的SubclassUIViewController时,您仍然只需要添加您的myData,因为UIViewControllerInteractions中的方法已经由UIViewController实现(这就是为什么我们复制了方法签名)。
class SubclassUIViewController: MyProtocol {
    var myData ...
}

现在您可以拥有一个MyProtocolMyProtocolViewControllerarray,并调用在UIViewControllerInteractions中定义的方法,这将调用UIViewController方法。
var viewController: [MyProtocol] = [...]
viewController.forEach { (vc) in
    print(vc.myData)
    print(vc.title)
}

OP说他们需要能够将数组的元素用作UIViewController类型的实例 - 我也有同样的想法 :) - Hamish
我错过了那部分。谢谢 :) 也许这可以帮助其他遇到类似问题的人。 - Yannick

0

我曾经遇到类似的问题,并通过自定义基类解决了它。想象一下一个数组:

var viewControllers: [MapViewController]

所有的类都应该从UIViewController继承,并实现以下协议:

protocol MapViewControllerDelegate {
    func zoomToUser()
}

然后我声明了一个基类,如下:

class MapViewController: UIViewController {
    var delegate: MapViewControllerDelegate?
}

注意:这个类没有实现上述协议,但是它持有一个属性,提供所需的功能。下一步是定义其中一个将被添加到数组中的UIViewController

class GoogleMapsViewController: MapViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        delegate = self
    }
}

extension GoogleMapsViewController: MapViewControllerDelegate {
    func zoomToUser() {
        // Place custom google maps code here
    }
}

重要部分位于viewDidLoad方法中。视图控制器将自身分配为委托。

用法:

let googleMapsViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "GoogleMapsViewController") as! GoogleMapsViewController
let mapboxMapsViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MapboxMapsViewController") as! MapboxMapsViewController
let mapViewControllers: [MapViewController] = [googleMapsViewController, mapboxViewController]
for mapVC in mapViewControllers {
    mapVC.delegate?.zoomToUser()
}

好处:

  • MapViewController 类似于抽象类,如果我更改了 MapViewControllerDelegate,编译器会强制我在 GoogleMapsViewControllerMapboxMapsViewController 中实现更改。
  • 如果我需要第二个协议,我只需实现第二个委托属性。
  • 不像其他答案中那样需要类型转换。每个 UIViewController 仍然是 UIViewController,并提供其所有方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接