我一直在努力实现这一目标,并且无法让它发挥作用。
首先让我展示一个简单的示例代码:
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
但是,我无法使用这些建议和解决方案。
有人能指出我正确的方向吗?
由于
答案 0 :(得分:1)
您应该使用同步请求。 这个扩展程序很容易使用:
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
答案 1 :(得分:1)
执行此操作的一种方法是更改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的任务回调的委托队列,该示例创建了自己的队列,您可以随意阻止它。
答案 2 :(得分:1)
而不是其他人已经建议的信号量或组(阻塞线程,如果你有太多的线程被阻塞可能会有问题),我会使用自定义的异步NSOperation
子类来处理网络请求。一旦您将请求包装在异步NSOperation
中,您就可以将一堆操作添加到操作队列中,而不是阻塞任何线程,而是享受这些异步操作之间的依赖关系。
例如,网络操作可能如下所示:
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)
}
或者,如果您不希望遭受顺序请求的显着性能损失,但仍希望限制并发度(最小化系统资源,避免超时等),则可以设置{{1等于3或4的值。
或者,您可以使用依赖项,例如在完成所有异步下载时触发某个进程:
maxConcurrentOperationCount
如果您想取消请求,可以轻松取消它们:
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)
操作是用于控制一系列异步任务的非常丰富的机制。如果您参考WWDC 2015视频Advanced NSOperations,他们已经将这种模式与条件和观察者结合在一起(尽管他们的解决方案对于简单的问题可能有点过度设计。恕我直言。)
答案 3 :(得分:1)
以下是我在另一个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
并编写一个&#34;结果&#34;的数组时,您也可以允许成功和失败。如finalResult
,让小组成功并返回finalResult
- 这会保留每次通话的详细结果。
您可能已经注意到,没有办法取消任务。是的,没有。这将需要可取消的任务。对于这个问题也有优雅的解决方案,但这超出了这个答案。
答案 4 :(得分:-2)