2016-05-13 253 views
1

我一直試圖達到這一段時間,無法讓它工作。如何在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

但是,我不能讓這個與建議和解決方案的工作。

有人能指出我正確的方向嗎?

謝謝

+0

http://stackoverflow.com/a/28169498 – Shubhank

+0

如果您在MethodOne和MethodTwo之間有執行依賴關係的順序,爲什麼您使用兩個單獨的異步操作? – TJA

+0

看看這個非常相似的問題和我最近給出的答案:http://stackoverflow.com/a/37155037/465677 – CouchDeveloper

回答

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的任務回調的委託隊列,示例將創建自己的隊列,您可以隨意阻止該隊列。

+0

我不同意阻塞隊列將罰款。實際上,你的方法會導致嚴重的問題:每次執行循環中的代碼塊時,它都會調度到隊列中,並隨後阻塞底層線程。下一次迭代需要一個新的線程,並因此產生另一個線程。由於GCD限制了線程的最大數量(類似於64),所以調用線程(主線程)將會阻塞,並且可能還會死鎖。 – CouchDeveloper

+0

@CouchDeveloper是的,你是對的,顯然數據任務總是調用「委託隊列」。更新了示例以使用它自己的隊列。 – Pascal

+0

我將你的建議應用於我的實際代碼,但似乎由於某些代碼的執行時間早於其他代碼而出現一些錯誤...您是否可以從提供的鏈接查看示例代碼? – Pangu

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

而不是其他人建議的信號量或組(它阻塞一個線程,如果線程阻塞太多可能會產生問題),我會爲網絡請求使用自定義異步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,他們已經將這種模式帶入了另一個層面,包括條件和觀察者(雖然他們的解決方案對於簡單的問題可能有點過分)。

1

這裏是我已經在另一個答案的問題simalar,專門爲您量身定製的問題建議的方法:

你的方法method1method2都是異步的。異步函數應該有一種方法來向調用者發送完成信號。這樣做的一個方法是使用完成處理程序:

func method1(url: NSURL, completion: (Result1?, ErrorType?) ->()) 


    func method2(url: NSURL), completion: (Result2?, ErrorType?) ->()) 

這裏,Result1Result2是異步函數的計算結果。由於任務可能失敗,因此完成處理程序的簽名具有返回計算值或錯誤的方法。

假設您的第一個方法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 - 該維護關於每個調用的詳細結果。

您可能已經注意到,沒有辦法取消任務。是的,沒有。這將需要可取消任務。這個問題也有優雅的解決方案,但這超出了這個答案。