2016-05-13 4 views
1

Я пытался достичь этого в течение некоторого времени и не могу заставить его работать.Как сделать внутренний запрос асинхронного запроса завершен первым до завершения внешнего запроса async в 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

Однако, я не мог получить эту работу с предложениями и решениями.

Может кто-нибудь указать мне в правильном направлении?

Thanks

+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() 
} 

Обратите внимание, что для того, чтобы не блокировать очередь делегатов обратного вызова задачи методаOne, пример создает свою собственную очередь, которую вы можете заблокировать по желанию.

+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, вы можете добавить кучу операций в очередь операций, не блокируя нити, но наслаждаясь зависимостями между этими асинхронными операциями.

Например, работа сети может выглядеть следующим образом:

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.

Или, вы можете использовать зависимости, например, чтобы вызвать некоторый процесс, когда все асинхронной загрузки сделаны:

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, они взяли этот шаблон на совершенно другой уровень с условиями и наблюдателями (хотя их решение может быть немного перенастроено для простых задач. IMHO).

1

Вот подход, который я уже предлагал в других ответы для simalar вопроса, специально предназначенных для вашей проблемы:

Ваших методов method1 и method2 являются асинхронной. Асинхронные функции должны иметь средство для завершения сигнала для вызывающего. Один из подходов для этого используют обработчик завершения:

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


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

Здесь Result1 и Result2 это вычисленный результат асинхронных функций. Поскольку задача может завершиться неудачей, подпись обработчика завершения имеет средство для возврата вычисленного значения или ошибки.

Предположим, ваш первый метод method1 оценивает список элементов, каждый из которых содержит другой URL. Для каждого URL в этом списке вы хотите позвонить method2.

Wrap эти составленные задачи в новую функцию 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 - которая содержит подробный результат о каждом вызове.

Возможно, вы заметили, что нет возможности отменить задачу. Да, нет. Для этого потребуется отменяемых задач. Есть и элегантные решения для этой проблемы, но это выходит за рамки этого ответа.

Смежные вопросы