Swinject 将 self 的属性注入新的 UIViewController

Posted

技术标签:

【中文标题】Swinject 将 self 的属性注入新的 UIViewController【英文标题】:Swinject inject self's property into new UIViewController 【发布时间】:2017-06-04 17:16:05 【问题描述】:

假设我们有一个UITableViewController,它在didSelectRowAtSection 上加载了一个名为ClassToInject 的类的实例,并且它想通过属性注入来注入它,因为我们的ViewControllerToBePushed 具有ClassToInject 的属性,随后(因为它是一个UITabBarViewController)在didSet 回调中它搜索其所有符合ClassToInjectPresentableviewControllers 属性,简单如下:

protocol ClassToInjectPresentable  
  var property: ClassToInject  get set  

到目前为止,我只会做这样的事情:

func didSelectRowAtIndexPath 
     let classToInject = self.loadClassToInjectFor(indexPath)
     let tabBarViewController = SomeTabBarViewController()
     tabBarViewController.property = classToInject
     self.navigationController.push(tabBarViewController, animated: true)

SomeTabBarViewController ...

class SomeTabBarViewController: ClassToInjectPresentable 
  var property: ClassToInject? 
  didSet(newValue) 
      self.viewControllers.filter $0 is ClassToInjectPresentable .map $0 as! ClassToInjectPresentable .forEach $0.property = newValue 
  
 

并且所有内容都应该可以轻松轻松地加载(但事实并非如此)。我读过Swinject,这可能会解决。我已经看到很多注册的例子,比如:

container.register(Animal.self)  _ in Cat(name: "Mimi") 

但是不知道能不能注册一些在self中加载的属性:

container.register(ClassToInjectInjector.self)  _ in 
self.loadClassToInjectFor(indexPath) 
// And then
container.register(ClassToInjectPresentable.self)  _ in 
SomeTabBarViewController() 
    .initCompleted  r, p in
        let tabBar = p as! SomeTabBarViewController
        tabBar.property = r.resolve(ClassToInjectInjector.self)
        // And lastly?
        self.navigationController.pushViewController(tabBar, animated: true)
    

【问题讨论】:

【参考方案1】:

在不了解应用程序细节的情况下很难推荐合适的解决方案,但这里有一些建议:

container.register(ClassToInjectInjector.self)  _ in 
    self.loadClassToInjectFor(indexPath) 

一般来说,所有register-ations 都应该在您的对象之外完成。常见设置是拥有一个全局 Container,其中包含所有注册 - 您应该将它们视为构建应用程序对象的指令,而无需任何隐式上下文。如果您的依赖项需要在UITableViewController 中创建,您可以将其作为参数传递给resolve 方法:

container.register(ClassToInjectPresentable.self)  resolver, property in
    let tabBar = SomeTabBarViewController()
    tabBar.property = property
    return tabBar


// in UItableVIewController
container.resolve(ClassToInjectPresentable.self, 
                  argument: self.loadClassToInjectFor(indexPath))

这通常也是个坏主意:

.initCompleted  r, p in
    ...
    self.navigationController.pushViewController(tabBar, animated: true)
 

您不应该将应用程序逻辑与 DI 混合使用 - 纯粹使用 Swinject 来构建您的依赖项。

所以你的UITableViewController 可能看起来像这样:

func didSelectRowAtIndexPath 
    let classToInject = self.loadClassToInjectFor(indexPath)
    let tabBar = container.resolve(
        SomeTabBarViewController.self, argument: loadClassToInjectFor(indexPath)
    )
    navigationController.push(tabBar, animated: true)

至于您的TabBar 及其视图控制器:UIViewControllers 如何进入TabBar?有可能做这样的事情吗?

class SomeTabBarViewController 
    init(viewControllers: [UIViewController]) 
        ...
       


container.register(SomeTabBarViewController.self)  r, property
    SomeTabBarViewController(viewControllers:[
        r.resolve(MyViewController.self, argument: property),
        r.resolve(MyViewController2.self, argument: property)
    ])

【讨论】:

tabBar.property = injector 来自哪里? 抱歉,本来是tabBar.property = property。我已经更新了答案【参考方案2】:

最后我按照提出的建议得到了最终的答案。

public class Containers 
    fileprivate init()  


extension Containers 
    static let activityPresentableContainer: Container = 
        let container = Container()
        container.register(ActivityTabBarController.self)  (r: Resolver, arg1: Activity) in
            return ActivityTabBarController(activity: arg1)
        
        container.register(ActivityPresentable.self) 
           (r: Resolver, arg1: ActivityPresentableTabs, arg2: Activity) in
           switch arg1 
           case .summary:
               return ActivitySummaryViewController(activity: arg2)
           case .detail:
               return ActivityDetailPageViewController(activity: arg2)
           case .map:
               return ActivityMapViewController(activity: arg2)
           case .charts:
               return ActivityChartsViewController(activity: arg2)
           case .strava:
              return ActivityStravaViewController(activity: arg2)
           
        .inObjectScope(.transient)
       return container
    ()

使用这种方法,ActivityTabBarController 总是由 activityPresentableContainer 使用以下语句实例化:

let controller = Containers.activityPresentableContainer.resolve(
    ActivityTabBarController.self, argument: activity
)!

然后,TabBarController 中的每个选项卡都使用所需的参数Activity 和选项卡本身的类型使用.transient 上下文进行实例化。它是这样解决的:

let activitySummary = Containers.activityPresentableContainer.resolve(
   ActivityPresentable.self, arguments: ActivityPresentableTabs.summary, activity!
) as! UIViewController

通过这种方式,我可以根据标签栏所使用的信息来概括标签栏的标签。如果其中一个选项卡随时发生变化,我可以按照ActivityPresentable 协议更改注册。

【讨论】:

以上是关于Swinject 将 self 的属性注入新的 UIViewController的主要内容,如果未能解决你的问题,请参考以下文章

Swinject:如何将委托模式与接口隔离(类与接口)一起使用?

如何使用 swinject 正确注入依赖项

我在 Swinject 中做错了啥?

Swinject 容器注册失败

Swinject:使用元类型列表解析

属性包装器到属性包装器