如何在Swift中完成外部asynchronous请求之前先完成内部asynchronous请求?
我一直在试图达到这一段时间,不能得到它的工作。
首先让我给出一个简单的示例代码:
override func viewDidLoad() { super.viewDidLoad() methodOne("some url bring") } func methodOne(urlString1: String) { let targetURL = NSURL(string: urlString1) let task = NSURLSession.sharedSession().dataTaskWithURL(targetURL!) {(data, response, error) in // DO STUFF j = some value print("Inside Async1") for k in j...someArray.count - 1 { print("k = \(k)") print("Calling Async2") self.methodTwo("some url string") } } task.resume() } func methodTwo(urlString2: String) { let targetURL = NSURL(string: urlString2) let task = NSURLSession.sharedSession().dataTaskWithURL(targetURL!) {(data, response, error) in // DO STUFF print("inside Async2") } task.resume() }
我基本上做的是我在我的methodTwo
中执行一个asynchronous请求,并在该函数内,我打电话给我的methodTwo
执行另一个asynchronous请求。
我遇到的问题是,当methodTwo
时,它永远不会进入asynchronous会话。 但是,它在methodTwo
中进入asynchronous会话,但只有一次k = someArray.count - 1
。 它基本上排队,直到最后,这不是我想要实现的。
这是一个示例输出:
Inside Async1 k = 0 Calling Async2 Inside Async1 k = 0 Calling Async2 k = 1 Calling Async2 Inside Async1 k = 0 Calling Async2 k = 1 Calling Async2 k = 2 Calling Async2 Inside Async1 ..... Inside Async1 k = 0 Calling Async2 k = 1 Calling Async2 k = 2 Calling Async2 k = 3 Calling Async2 k = 4 Inside Async2
换句话说,我希望在methodTwo
的asynchronous请求完成之前,在每次迭代中完成来自methodTwo
的asynchronous请求。
以下是我的目标是什么样的输出:
Inside Async1 k = 0 Calling Async2 Inside Async2 Inside Async1 k = 1 Calling Async2 Inside Async2 Inside Async1 ...
我在这里发现了类似的东西: 等到第一个asynchronous函数完成后再执行第二个asynchronous函数
但是,我无法得到这个build议和解决scheme。
有人能指出我正确的方向吗?
谢谢
一种方法是改变methodTwo()
接受一个callback作为参数,然后你可以使用一个信号量:
func methodOne(urlString1: String) { let targetURL = NSURL(string: urlString1) let task = NSURLSession.sharedSession().dataTaskWithURL(targetURL!) { data, response, error in let queue = dispatch_queue_create("org.myorg.myqueue", nil) dispatch_async(queue) { // DO STUFF j = some value print("Inside Async1") for k in j...someArray.count - 1 { print("k = \(k)") print("Calling Async2") dispatch_semaphore_t sem = dispatch_semaphore_create(0); self.methodTwo("some url string") { dispatch_semaphore_signal(sem); } dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); } } } task.resume() } func methodTwo(urlString2: String, callback: (() -> ())) { let targetURL = NSURL(string: urlString2) let task = NSURLSession.sharedSession().dataTaskWithURL(targetURL!) { data, response, error in // DO STUFF print("inside Async2") callback() } task.resume() }
请注意,为了不阻塞methodOne的任务callback的委托队列,示例将创build自己的队列,您可以随意阻止该队列。
你应该使用同步请求。 这个扩展很容易使用:
extension NSURLSession { public static func requestSynchronousData(request: NSURLRequest, completion: ((data: NSData?, error: NSError?) -> Void)?) { var data: NSData? = nil var error: NSError? = nil let semaphore: dispatch_semaphore_t = dispatch_semaphore_create(0) NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { taskData, _, taskError -> () in data = taskData error = taskError if data == nil, let error = error {print(error)} dispatch_semaphore_signal(semaphore); }).resume() dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) completion?(data: data, error: error) } }
并在方法中发送同步请求methodTwo
:
func methodOne(urlString1: String) { guard let targetURL = NSURL(string: urlString1) else { return } let request = NSURLRequest(URL: targetURL) NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) in // DO STUFF print("Inside Async1") for k in 0..<5 { print("k = \(k)") print("Calling Async2") self.methodTwo("http://www.google.com") } }.resume() } func methodTwo(urlString2: String) { guard let targetURL = NSURL(string: urlString2) else { return } let request = NSURLRequest(URL: targetURL) NSURLSession.requestSynchronousData(request) { (data, error) in // DO STUFF print("inside Async2") } }
你也可以使用Dispatch Queue进行pipe理。 了解更多关于GCD的信息
而不是其他人build议的信号量或组(它阻塞了一个线程,如果线程阻塞太多,这可能会产生问题),我会为networking请求使用一个自定义的asynchronousNSOperation
子类。 一旦你把请求包装在一个asynchronous的NSOperation
,你就可以将一堆操作添加到一个操作队列中,不会阻塞任何线程,而是享受这些asynchronous操作之间的依赖关系。
例如,networking操作可能如下所示:
class NetworkOperation: AsynchronousOperation { private let url: NSURL private var requestCompletionHandler: ((NSData?, NSURLResponse?, NSError?) -> ())? private var task: NSURLSessionTask? init(url: NSURL, requestCompletionHandler: (NSData?, NSURLResponse?, NSError?) -> ()) { self.url = url self.requestCompletionHandler = requestCompletionHandler super.init() } override func main() { task = NSURLSession.sharedSession().dataTaskWithURL(url) { data, response, error in self.requestCompletionHandler?(data, response, error) self.requestCompletionHandler = nil self.completeOperation() } task?.resume() } override func cancel() { requestCompletionHandler = nil super.cancel() task?.cancel() } } /// Asynchronous Operation base class /// /// This class performs all of the necessary KVN of `isFinished` and /// `isExecuting` for a concurrent `NSOperation` subclass. So, to developer /// a concurrent NSOperation subclass, you instead subclass this class which: /// /// - must override `main()` with the tasks that initiate the asynchronous task; /// /// - must call `completeOperation()` function when the asynchronous task is done; /// /// - optionally, periodically check `self.cancelled` status, performing any clean-up /// necessary and then ensuring that `completeOperation()` is called; or /// override `cancel` method, calling `super.cancel()` and then cleaning-up /// and ensuring `completeOperation()` is called. public class AsynchronousOperation : NSOperation { override public var asynchronous: Bool { return true } private let stateLock = NSLock() private var _executing: Bool = false override private(set) public var executing: Bool { get { return stateLock.withCriticalScope { _executing } } set { willChangeValueForKey("isExecuting") stateLock.withCriticalScope { _executing = newValue } didChangeValueForKey("isExecuting") } } private var _finished: Bool = false override private(set) public var finished: Bool { get { return stateLock.withCriticalScope { _finished } } set { willChangeValueForKey("isFinished") stateLock.withCriticalScope { _finished = newValue } didChangeValueForKey("isFinished") } } /// Complete the operation /// /// This will result in the appropriate KVN of isFinished and isExecuting public func completeOperation() { if executing { executing = false finished = true } } override public func start() { if cancelled { finished = true return } executing = true main() } } // this locking technique taken from "Advanced NSOperations", WWDC 2015 // https://developer.apple.com/videos/play/wwdc2015/226/ extension NSLock { func withCriticalScope<T>(@noescape block: Void -> T) -> T { lock() let value = block() unlock() return value } }
完成之后,您可以启动一系列可以按顺序执行的请求:
let queue = NSOperationQueue() queue.maxConcurrentOperationCount = 1 for urlString in urlStrings { let url = NSURL(string: urlString)! print("queuing \(url.lastPathComponent)") let operation = NetworkOperation(url: url) { data, response, error in // do something with the `data` } queue.addOperation(operation) }
或者,如果您不希望受到连续请求的显着性能损失,但仍想约束并发度(为了最小化系统资源,避免超时等),可以将maxConcurrentOperationCount
设置为3或4 。
或者,您可以使用依赖关系,例如在完成所有asynchronous下载时触发某个进程:
let queue = NSOperationQueue() queue.maxConcurrentOperationCount = 3 let completionOperation = NSBlockOperation() { self.tableView.reloadData() } for urlString in urlStrings { let url = NSURL(string: urlString)! print("queuing \(url.lastPathComponent)") let operation = NetworkOperation(url: url) { data, response, error in // do something with the `data` } queue.addOperation(operation) completionOperation.addDependency(operation) } // now that they're all queued, you can queue the completion operation on the main queue, which will only start once the requests are done NSOperationQueue.mainQueue().addOperation(completionOperation)
如果你想取消请求,你可以轻松取消它们:
queue.cancelAllOperations()
操作是控制一系列asynchronous任务的令人难以置信的丰富机制。 如果你参考了WWDC 2015的video高级NSOperations ,他们已经把这个模式带到了另一个层面,有条件和观察者(虽然他们的解决scheme可能对于简单的问题有点过分)。
这里是我已经在另一个西米拉问题的答案中build议的方法,专门为您的问题量身打造:
你的方法method1
和method2
都是asynchronous的。 asynchronous函数应该有一个方法来向调用者发送完成信号。 一种方法是使用完成处理程序:
func method1(url: NSURL, completion: (Result1?, ErrorType?) -> ()) func method2(url: NSURL), completion: (Result2?, ErrorType?) -> ())
这里, Result1
和Result2
是asynchronous函数的计算结果。 由于任务可能失败,所以完成处理程序的签名具有返回计算值或错误的方法。
假设你的第一个method1
方法评估一个项目列表,每个项目都包含另一个URL。 对于此列表中的每个URL,您要调用method2
。
把这些组合的任务包装成一个新的函数method
(它也是asynchronous的,因此它也有一个完成处理程序!):
func method(completion: (Result?, ErrorType?)-> ()) { let url = ... self.method1(url) { (result1, error) in if let result = result1 { // `result` is an array of items which have // a url as property: let urls = result.map { $0.imageUrl } // Now, for each url, call method2: // Use a dispatch group in order to signal // completion of a group of asynchronous tasks let group = dispatch_group_create() let finalResult: SomeResult? let finalError: ErrorType? urls.forEach { imageUrl in dispatch_group_enter(group) self.method2(imageUrl) { (result2, error) in if let result = result2 { } else { // handle error (maybe set finalError and break) } dispatch_group_leave(group) } } dispatch_group_notify(dispatch_get_global_queue(0,0)) { completion(finalResult, finalError) } } else { // Always ensure the completion handler will be // eventually called: completion(nil, error) } } }
上述方法使用一个调度组来分组一些任务。 当任务开始时,使用dispatch_enter
将增加任务组的数量。 当任务完成时,组中的任务数将随着dispatch_group_leave
减less而减less。
当该组为空(所有任务已完成)时,使用dispatch_group_notify
提交的块将在给定队列上执行。 我们使用这个块来调用外部函数method
的完成处理程序。
您可以创造性地处理错误。 例如,您可能只想忽略第二个方法method2
的失败并继续获取结果,或者您可能想要取消正在运行的每个任务并返回错误。 调用method2
时,也可以允许成功和失败,并将result作为finalResult
组成一个数组,并且返回finalResult
– 它将保存每个调用的详细结果。
您可能已经注意到,没有办法取消任务。 是的,没有。 这将需要取消任务。 这个问题也有优雅的解决scheme,但这超出了这个答案。
请参阅本文中的调度组: https : //www.mikeash.com/pyblog/friday-qa-2013-08-16-lets-build-dispatch-groups.html