1

我计划在一个新创建的项目中引入一个依赖注入框架,并且发现Typhoon看起来非常不错(除了在 Swift 中引用方法和属性时必须使用字符串)。它通常似乎很适合我的应用程序,但是我为处理网络请求所做的构造存在问题。

我所有的视图控制器(需要网络访问)都继承自一个ServiceViewController用于延迟初始化服务的视图控制器(此上下文中的服务是一个用于处理网络通信的类):

class ServiceViewController {    
  var services = [BDService]()//a list of (lazily initialized) services used by the the view controller

  //create a service of the specified type (or return from the existing list if it has already been created)
  func getService<T : BDService>() -> T {
    for service in services {
        if service is T {
            return service as T
        }
    }
    let klass : T.Type = T.self
    let newService : T = klass()//don't ask - due to Swift bug: http://stackoverflow.com/questions/27336607
    services.append(newService)
    return newService
  }

  //tell all initialized services to cancel pending requests
  func cancelAllPendingTasks() {
    services.each { $0.cancelPendingTasks() }
  }

  //inform whether a request has been sent and we are still waiting for a response
  func hasPendingTasks() -> Bool {
    return services.any { $0.pendingTasks.count > 0 }
  }
}

....

//usage from MyViewController:
let accountService : BDAccountService = super.getService()
/* do something on accountService */
...
let postingService : BDPostingService = super.getService()
/* do something on postingService */
...
super.cancelAllPendingTasks() //e.g. if user clicks back button

最后一个方法cancelPendingTaskshasPendingTasks是我想要保留这个架构的原因。通过将 ViewController 的所有服务放在一个列表中,我们可以确保我们不会忘记取消其中一项服务上的待处理任务。

现在,当我想引入 Typhoon 时,我可以在初始化程序中注入我所有的服务:

public dynamic func myViewController() -> AnyObject {
    return TyphoonDefinition.withClass(MyViewController.self) {
        (definition) in
        definition.useInitializer("initWithAccountService:postingService:") {//(forgive me if I wrote this wrong)
            (initializer) in
            initializer.injectParameterWith(self.accountService())
            initializer.injectParameterWith(self.postingService())
        }
    }
}

这种方法有两个缺点:

  1. 取消所有任务(或类似任务)需要调用所有服务
  2. 服务不再在需要时延迟初始化

广告 1) 处理 now 的一种方法是将这两个服务从初始化程序cancelAllPendingTasks()添加到列表中。ServiceViewController.services然后cancelAllPendingTasks()将能够访问所有服务。但我正在寻找一种避免这样做的方法,这样以后添加服务就不需要“记住做这个和那个”

广告 2)我没有看到通过依赖注入来做到这一点的方法。但我不认为这是一个大问题,因为服务的内存占用可能不那么值得注意:-)

所以我的问题是:我可以以某种方式将服务同时注入构造函数服务列表吗?

编辑: 当我从@JasperBlues 的回答中发现很容易将同一个对象两次注入属性和初始化程序时,我的第二个问题引起了我的更多关注:如果我们将一堆服务注入到初始化程序中,但忘记注入完全相同的服务放入数组中(例如我们忘记了其中一个),我们会遇到非常难以发现的错误,因为在hasPendingTasks某天方法返回错误结果之前,没有人会发现它,并且因此而挂起。这在我的原始设计中是不可能的,但如果我们应该注入两次服务,我们会突然得到一个容易出错的重复。

4

2 回答 2

1

Typhoon 允许您选择使用初始化程序注入(或不使用)以及以下任何一项:

  • 属性注入
  • 方法注入(带有一个或多个参数)。
  • 其他配置,例如指定在注入发生后调用的回调方法或设置范围。

如果该属性已通过初始化程序设置,则使用属性注入没有问题。

可以为同一个方法多次调用方法注入。(例如收集日志目录名称的方法)。

Typhoon 示例应用程序中的cityListController显示了将初始化程序注入与属性注入混合的示例。用户指南展示了如何进行方法注入、设置范围或在注入发生后调用回调方法。

于 2015-02-25T10:40:34.283 回答
0

在解决了我的问题并实施了 Typhoon 之后,我有一些想法要分享:

  1. 两次注入一个对象非常容易:

    public dynamic func myViewController() -> AnyObject {
        return TyphoonDefinition.withClass(MyViewController.self) {
            $0.useInitializer("initWithAccountService:postingService:") {
                $0.injectParameterWith(self.accountService())
                $0.injectParameterWith(self.postingService())
            }
            $0.injectProperty("services", with:[self.accountService(), self.postingService()])
        }
    }
    

    这会两次注入相同的对象,并且不会创建 accountService 和 postingService 的两个实例。在阅读@JasperBlues 的答案之前,我并不清楚这一点。

  2. 我对accountService和postingService重复注入的担忧其实很容易克服。我刚刚再次删除了初始化程序并返回使用该getService<T>()方法。因此,我的程序集现在看起来像这样:

    public dynamic func myViewController() -> AnyObject {
         return TyphoonDefinition.withClass(MyViewController.self) {
             $0.injectProperty("services", with:[self.accountService(), self.postingService()])
         }
    }
    

    以上填充了services数组,因此该getService<T>()方法将从该数组返回正确的注入对象。

  3. 关于延迟初始化的第三个问题实际上仍然可以通过简单地不注入服务来克服,而是像以前一样直接在代码中创建它们。毕竟,既然可以注入这些服务,那么测试它们还是很容易的。尽管如此,由于其他问题,例如在一个地方指定依赖项,即程序集,注入它们可能会更好:-)

但毕竟这是一次很好的学习经历,Typhoon 现在正在进入我的项目 :-)

于 2015-02-26T12:38:41.600 回答