如何在Swift中完成外部异步请求之前完成内部异步请求?
我一直试图达到这一段时间,无法让它工作。如何在Swift中完成外部异步请求之前完成内部异步请求?
首先让我展示一个简单的示例代码:
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()
}
什么我基本上做的是我我methodOne
内执行异步请求,并在该函数内,我打电话给我的methodTwo
执行另一异步请求。
我遇到的问题是当调用methodTwo
时,它永远不会进入异步会话。但是,它在methodTwo
内输入异步会话,但仅输入一次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
异步请求从methodOne
完成异步请求之前每次迭代完成。
这里是我的目标是什么样的输出:
Inside Async1
k = 0
Calling Async2
Inside Async2
Inside Async1
k = 1
Calling Async2
Inside Async2
Inside Async1
...
我发现这里类似的东西:Wait until first async function is completed then execute the second async function
但是,我不能让这个与建议和解决方案的工作。
有人能指出我正确的方向吗?
谢谢
一种方式做,这是改变methodTwo()
接受回调作为参数,那么你可以使用一个信号:
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的任务回调的委托队列,示例将创建自己的队列,您可以随意阻止该队列。
我不同意阻塞队列将罚款。实际上,你的方法会导致严重的问题:每次执行循环中的代码块时,它都会调度到队列中,并随后阻塞底层线程。下一次迭代需要一个新的线程,并因此产生另一个线程。由于GCD限制了线程的最大数量(类似于64),所以调用线程(主线程)将会阻塞,并且可能还会死锁。 – CouchDeveloper
@CouchDeveloper是的,你是对的,显然数据任务总是调用“委托队列”。更新了示例以使用它自己的队列。 – Pascal
我将你的建议应用于我的实际代码,但似乎由于某些代码的执行时间早于其他代码而出现一些错误...您是否可以从提供的链接查看示例代码? – Pangu
您应该使用同步请求。 很容易与此扩展使用:
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")
}
}
您也可以使用调度队列管理。 Learn more about GCD
而不是其他人建议的信号量或组(它阻塞一个线程,如果线程阻塞太多可能会产生问题),我会为网络请求使用自定义异步NSOperation
子类。一旦将请求包装在异步NSOperation
中,然后可以将一堆操作添加到操作队列中,而不是阻塞任何线程,但享受这些异步操作之间的依赖关系。
例如,网络操作可能是这样的:如果你
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)
}
或者:
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
}
}
已经这样做了,你可以启动一个全系列的请求,可以依次进行不希望受到连续请求的显着性能损失,但仍然希望限制并发程度(以最小化系统资源,避免超时等),可以将maxConcurrentOperationCount
设置为3或4的值。
或者,你可以使用相关性,例如引发一些过程,当所有异步下载完成:
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()
操作是控制一系列异步任务的令人难以置信的丰富机制。如果你参考了WWDC 2015的视频Advanced NSOperations,他们已经将这种模式带入了另一个层面,包括条件和观察者(虽然他们的解决方案对于简单的问题可能有点过分)。
这里是我已经在另一个答案的问题simalar,专门为您量身定制的问题建议的方法:
你的方法method1
和method2
都是异步的。异步函数应该有一种方法来向调用者发送完成信号。这样做的一个方法是使用完成处理程序:
func method1(url: NSURL, completion: (Result1?, ErrorType?) ->())
func method2(url: NSURL), completion: (Result2?, ErrorType?) ->())
这里,Result1
和Result2
是异步函数的计算结果。由于任务可能失败,因此完成处理程序的签名具有返回计算值或错误的方法。
假设您的第一个方法method1
评估项目列表,每个项目都包含另一个URL。对于此列表中的每个网址,您想致电method2
。
总结这些组成的任务到一个新的功能method
(这也是异步的,因此,它也有一个完成处理程序,以及!):
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
。
当该组为空(所有任务已完成)时,将使用dispatch_group_notify
提交的块在给定队列上执行。我们使用这个块来调用外部函数method
的完成处理程序。
您在处理错误方面可能很有创意。例如,您可能只想忽略第二个方法method2
的失败并继续获得结果,或者您可能想要取消仍在运行并返回错误的每个任务。调用method2
时,您也可以允许成功和失败,并将“结果”数组组合为finalResult
,让该组成功并返回finalResult
- 该维护关于每个调用的详细结果。
您可能已经注意到,没有办法取消任务。是的,没有。这将需要可取消任务。这个问题也有优雅的解决方案,但这超出了这个答案。
http://stackoverflow.com/a/28169498 – Shubhank
如果您在MethodOne和MethodTwo之间有执行依赖关系的顺序,为什么您使用两个单独的异步操作? – TJA
看看这个非常相似的问题和我最近给出的答案:http://stackoverflow.com/a/37155037/465677 – CouchDeveloper