【问题标题】:Create an array of objects that implements a specific protocol创建一个实现特定协议的对象数组
【发布时间】:2017-06-24 16:59:11
【问题描述】:

TL;DR

我正在寻找一个数组类型 (var array = [TheTypeImLookingFor]()),例如“所有子类化 UIViewController 的对象并且实现了协议 MyProtocol

说明

我正在构建一种带有容器视图和嵌入式子视图(控制器)的向导视图。没问题,只要我只有一个base 类型的子视图控制器,这将起作用。

由于屏幕的内容,我现在有一堆 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 中(或为它们创建单独的协议并使用协议组合)。
  • 是的,他们必须是两者。也许我对实现的想法是错误的。

标签: ios swift uiviewcontroller


【解决方案1】:

我遇到了类似的问题,并使用自定义基类解决了它。想象一个像这样的数组:

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 并提供其所有方法。

【讨论】:

    【解决方案2】:

    您可以将协议组合与类的占位符协议一起使用:

    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 方法,但如果您要使用其中的许多方法,这很快就会变得乏味和多余。

    【讨论】:

      【解决方案3】:

      您还可以创建一个protocol 来定义您需要的所有UIViewController 函数。确保复制方法签名,否则您将不得不再次实现这些功能。

      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 ...
      }
      

      您现在可以拥有MyProtocolMyProtocolViewController 中的array,还可以调用UIViewControllerInteractions 中定义的方法,这些方法将调用UIViewController 方法。

      var viewController: [MyProtocol] = [...]
      viewController.forEach { (vc) in
          print(vc.myData)
          print(vc.title)
      }
      

      【讨论】:

      • OP says 他们需要能够将数组的元素用作UIViewController 类型的实例——我也有同样的想法:)
      • 我错过了那部分。谢谢 :) 也许这可以帮助其他有类似问题的人
      【解决方案4】:

      据我所知,目前没有办法键入一些内容,以便它描述从给定类继承的任何内容并且符合给定协议。

      一种可能的解决方法是创建一个包装器类型,以便在您需要将实例视为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)
      }
      

      【讨论】:

      • 非常感谢大家!你对我的想法帮助很大。目前,我使用 2 个数组,一个用于未转换的 UIViewController 对象,一个用于转换(到协议)对象。这既丑陋又愚蠢,但目前有效。
      • @Tobonaut 我不建议为此使用并行数组,尽管我承认似乎没有任何“完美”的解决方案。
      • 嗯,好的。你认为我对此有错误的“想法”吗?我是一名前 Java EE 开发人员,也许我使用了错误的模式。
      • @Tobonaut 并行数组通常被认为是一种反模式,因为它使它们难以保持同步,并且它将应该在一起的数据分开(例如,您的控制器键入为UIViewControllerMyProtocol)。几乎总是最好将它们包含在合适的数据结构中,例如结构。
      • 这听起来很实用。我稍后会尝试。谢谢朋友。
      【解决方案5】:

      为什么不简单地创建:

      为什么不创建:

      class ObservingViewController : UIViewController, MyProtocol {
      
      
      }
      
      var viewControllers : [ObservingViewController] = []
      

      【讨论】:

      • 我相信 OP 希望这个数组的元素知道它们也是 UIViewController 的子类对象,如果只使用类型化的协议对象,情况就不会如此。
      • 是的,@dfri。那是我的问题。
      • OP 的一个类继承自UITableViewController,所以这行不通。
      猜你喜欢
      • 1970-01-01
      • 2020-06-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-19
      • 1970-01-01
      相关资源
      最近更新 更多