【问题标题】:Interdependent NSURLSession tasks with NSOperation and NSOperationQueue (Swift)与 NSOperation 和 NSOperationQueue (Swift) 相互依赖的 NSURLSession 任务
【发布时间】:2016-01-27 11:00:11
【问题描述】:

问题: 我想在 Swift 中开发一个 iOS 应用程序,它在登录后立即执行初始加载。序列(通过 NSURLSession 基于 REST 的调用)如下所示:

  1. 使用用户帐户登录 -> 异步响应返回 userId
  2. 获取 userId 的国家 -> 异步响应返回 countryId 的
  3. 获取 countryId 的产品 -> ...等...

基本上我想找到一种优雅的方式来实现这样的序列。

方法: 首先,我只是在另一个的完成处理程序中调用新的(依赖的)REST 调用。但是如果需要执行很多调用并且依赖级别比上面描述的要多,那么代码看起来有点乱......

我遇到了关于 NSOperations 的 WWDC 2015 会议,并认为这可能是一个好主意,因为有些人可以很容易地定义依赖关系。 不幸的是,Apple 提供的示例代码没有给出上述问题的答案……是吗(我没有得到它?)? 在玩操作操作时,我无法考虑在创建不同操作时如何解决初始化问题(LoginOperation、CountryOperation(依赖于 LoginOperation)、ProductOperation(依赖于 CountryOperation)等。)

我发现这些帖子很有帮助,但我仍然不了解如何最好地解决我描述的问题: How To Download Multiple Files Sequentially using NSURLSession downloadTask in Swift Chaining NSOperation : Pass result from an operation to the next one NSURLSession with NSBlockOperation and queues

困难: 在另一个操作 A 成功完成并返回将由操作 B 使用的结果时初始化操作 B。

【问题讨论】:

  • 非常感谢任何帮助/提示!为每个基于 REST 的请求创建一个 NSOperation 子类的想法是一个好主意吗?这会导致代码重复,是吗?或者我应该为处理 GET/POST 请求的 NSURLSession 创建一个 NSoperation 基类。对于第二种方法,我不确定如何根据目标序列在完成处理程序中处理下一次调用的初始化...请帮助!

标签: ios sequence nsurlsession nsoperationqueue


【解决方案1】:

如果仍然相关,我可能会有答案。

这里的问题是你试图推迟初始化直到第一个操作完成。这是一个死胡同。操作意味着要尽早创建,并且使用依赖项来保证顺序。

所以让我们讨论一些解决这个极其常见的问题的方法。

(您可以注意到这些示例是用 WWDC-session 风格编写的。这实际上是因为我使用了基于该演讲的Operations library。如果您对基于操作的架构感兴趣,请查看它。 )

1。使用一些外部可变共享状态(Cocoa-way)

这基本上意味着,对于您的示例,您有一些 UserController,您将其实例传递给 LoginOperation 和 CountryOperation。 LoginOperation 将 userId 写入该控制器,CountryOperation 从中读取:

class UserController {
    var userID: String?
}

class LoginOperation: Operation {

    let userController: UserController

    init(userController: UserController) {
        self.userController = userController
    }

    override func execute() {
        // do your networking stuff
        userController.userID = receivedUserID
        finish()
    }

}

class CountryOperation: Operation {

    let userController: UserController

    init(userController: UserController) {
        self.userController = userController
    }

    override func execute() {
        let userID = userController.userID
        // continue
    }

}

然后:

let queue = OperationQueue()
let userController = UserController()
let login = LoginOperation(userController: userController)
let country = CountryOperation(userController: userController)
country.addDependency(login)
queue.addOperation(login)
queue.addOperation(country)

该解决方案的问题在于它非常复杂且难以测试。而且 Swift 也不太喜欢可变共享状态。

2。使用函数而不是值来初始化操作(Swifty-way)

嗯,这不是显而易见但非常优雅的解决方案。如您所见,值在初始化时被复制。我们真正需要的是检索所需的执行时间值(与前面的示例一样),但没有如此紧密的耦合。好吧,这很容易做到 - 只需将一个函数传递给初始化程序!

class LoginOperation: Operation {

    private(set) var userID: String?

    override func execute() {
        // do your networking stuff
        let receivedUserID = "1"
        userID = receivedUserID
        finish()
    }

}

class CountryOperation: Operation {

    private let getUserID: () -> String

    init(getUserID: () -> String) {
        self.getUserID = getUserID
    }

    override func execute() {
        /* Because this operation depends on `LoginOperation`,
        `getUserID()` is called after `LoginOperation` is finished. */
        let userID = self.getUserID()
        // continue
    }

}

然后:

let queue = OperationQueue()
let login = LoginOperation()
// Force-unwrap is no good, of course, but for this example it's ok
let country = CountryOperation(getUserID: { login.userID! })
country.addDependency(login)
queue.addOperation(login)
queue.addOperation(country)

如您所见,没有紧密耦合,没有共享可变状态(CountryOperation 只能读,不能写——这很好)。而且它非常容易测试:只需将你想要的任何东西传递给 CountryOperation 初始化器,你根本不需要 LoginOperation 来测试它!

实际上,通过这种方法,NSOperation 的主要目标得以实现:独特、抽象的作品。 CountryOperation 本身对 LoginOperation 以及任何其他共享实例一无所知。

我实际上在我的项目中经常使用这种方法,并且效果非常好。

如果您有任何其他问题,请回复:)

【讨论】:

    猜你喜欢
    • 2012-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-22
    • 1970-01-01
    • 2015-10-30
    • 1970-01-01
    相关资源
    最近更新 更多