使用NSOperation和NSOperationQueue(Swift)的相互依赖的NSURLSession任务

时间:2016-01-27 11:00:12

标签: ios sequence nsurlsession nsoperationqueue

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

  1. 使用用户帐户登录 - >异步响应返回 userId
  2. 获取 userId 的国家/地区 - >异步响应返回 countryId
  3. countryId 获取产品 - > ...等等...
  4. 基本上我想找到一种优雅的方法来实现这样的序列。

    方法: 首先,我首先在另一个的完成处理程序中调用新的(依赖的)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.

1 个答案:

答案 0 :(得分:1)

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

这里的问题是你试图推迟初始化直到第一次操作完成。那是死路一条。操作意味着可以提前创建,并且使用依赖关系保证订单。

让我们讨论一些解决这个非常频繁问题的方法。

(你可以注意到这些例子是用WWDC会话风格编写的。这实际上是因为我使用基于该演讲的Operations library。如果你对基于操作的兴趣,请看一下体系结构。)

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

这基本上意味着,对于您的示例,您有一些UserController,您将传递给LoginOperationCountryOperation的实例。 LoginOperationuserId写入该控制器,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以及任何其他共享实例一无所知。

我实际上在我的项目中使用了这种方法而且效果惊人。

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