Eşzamansız ağ istekleriyle döngü için hızlı bir şekilde yürütmenin bitmesini bekleyin


159

Ben bir for in loop ateş temeline ağ istekleri bir demet göndermek, sonra yöntem yürütme bittikten sonra yeni bir görünüm denetleyicisine veri geçirmek istiyorum. İşte benim kod:

var datesArray = [String: AnyObject]()

for key in locationsArray {       
    let ref = Firebase(url: "http://myfirebase.com/" + "\(key.0)")
    ref.observeSingleEventOfType(.Value, withBlock: { snapshot in

        datesArray["\(key.0)"] = snapshot.value
    })
}
// Segue to new view controller here and pass datesArray once it is complete 

Birkaç endişem var. İlk olarak, for döngüsü bitip tüm ağ istekleri tamamlanana kadar nasıl beklerim? ObserveSingleEventOfType işlevini değiştiremiyorum, firebase SDK'sının bir parçası. Ayrıca, for döngüsü farklı yinelemelerinden datesArray'a erişmeye çalışarak bir çeşit yarış koşulu oluşturacak mıyım? GCD ve NSOperation hakkında okuyordum ama bu benim yaptığım ilk uygulama olduğu için biraz kayboldum.

Not: Locations dizisi, firebase'de erişmem gereken anahtarları içeren bir dizidir. Ayrıca, ağ isteklerinin eşzamansız olarak başlatılması da önemlidir. Sadece dateArray'ı bir sonraki görünüm denetleyicisine iletmeden önce TÜM eşzamansız isteklerin tamamlanmasını beklemek istiyorum.

Yanıtlar:


338

Tüm istekleriniz bittiğinde gönderme gruplarını eşzamansız bir geri arama başlatmak için kullanabilirsiniz .

Aşağıda, birden çok ağ isteği tamamlandığında bir geri çağrıyı eşzamansız olarak yürütmek için gönderme gruplarını kullanan bir örnek verilmiştir.

override func viewDidLoad() {
    super.viewDidLoad()

    let myGroup = DispatchGroup()

    for i in 0 ..< 5 {
        myGroup.enter()

        Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
            print("Finished request \(i)")
            myGroup.leave()
        }
    }

    myGroup.notify(queue: .main) {
        print("Finished all requests.")
    }
}

Çıktı

Finished request 1
Finished request 0
Finished request 2
Finished request 3
Finished request 4
Finished all requests.

Bu harika çalıştı! Teşekkürler! DatesArray'ı güncellemeye çalışırken herhangi bir yarış koşulu ile karşılaşıp karşılaşmayacağım hakkında bir fikrin var mı?
Josh

Burada bir yarış koşulu olduğunu düşünmüyorum çünkü tüm istekler datesArrayfarklı bir anahtar kullanarak değer katıyor.
paulvs

1
İlişkin yarış durumunu @Josh: aynı bellek konumu en az bir erişim yazma, farklı ipliklerin, erişilebilir eğer bir yarış durumu ortaya çıkar - olmadan senkronizasyon kullanılmıştır. Yine de aynı seri gönderme kuyruğundaki tüm erişimler senkronize edilir. Senkronizasyon ayrıca, başka bir dağıtım kuyruğuna B gönderilen gönderim kuyruğu A'da meydana gelen bellek işlemleri ile de gerçekleşir. A kuyruğundaki tüm işlemler daha sonra kuyruk B'de senkronize edilir. ;)
CouchDeveloper

@josh, "yarış pisti programlamasının" tek kelimeyle muazzam derecede zor olduğunu unutmayın. Anında "orada bir probleminiz var / probleminiz yok" demek asla mümkün değildir. Hobi programcıları için: "sadece" her zaman yarış pisti problemlerinin imkansız olduğu anlamına gelir. (Örneğin, "bir seferde sadece bir şey yapın" vb.) Bunu yapmak bile büyük bir programlama zorluğudur.
Fattie

Süper havalı. Ama bir sorum var. İstek 3 ve istek 4'ün başarısız olduğunu varsayalım (örneğin sunucu hatası, yetkilendirme hatası, herhangi bir şey), daha sonra yalnızca kalan istekler (döngü 3 ve istek 4) için tekrar döngü nasıl çağrılır?
JD.

43

Xcode 8.3.1 - Hızlı 3

Swift 3'e dönüştürülen paulvs'un kabul edilen cevabı şu:

let myGroup = DispatchGroup()

override func viewDidLoad() {
    super.viewDidLoad()

    for i in 0 ..< 5 {
        myGroup.enter()
        Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
            print("Finished request \(i)")
            myGroup.leave()
        }
    }

    myGroup.notify(queue: DispatchQueue.main, execute: {
        print("Finished all requests.")
    })
}

1
Merhaba, bu 100 istek için işe yarıyor mu? veya 1000? Çünkü bunu yaklaşık 100 istek ile yapmaya çalışıyorum ve isteğin tamamlanması üzerine çöküyor.
lopes710

I @ @ lopes710-- Bu, tüm isteklerin paralel çalışmasına izin veriyor gibi görünüyor, değil mi?
Chris Prince

i bir döngü içinde diğeri ile iç içe 2 ağ istekleri varsa, sonra döngü her yineleme için, her iki istek tamamlandığından emin olmak için nasıl. ?
Awais Fayyaz

@ Kanal, lütfen bunu sipariş etmenin bir yolu var mı?
İsrail Meshileya

41

Swift 3 veya 4

Eğer varsa yok umurumda siparişler , kullanım @ paulvs en cevabı , mükemmel çalışıyor.

başkası aynı anda ateş etmek yerine sonucu sırayla almak isterse, işte kod.

let dispatchGroup = DispatchGroup()
let dispatchQueue = DispatchQueue(label: "any-label-name")
let dispatchSemaphore = DispatchSemaphore(value: 0)

dispatchQueue.async {

    // use array categories as an example.
    for c in self.categories {

        if let id = c.categoryId {

            dispatchGroup.enter()

            self.downloadProductsByCategory(categoryId: id) { success, data in

                if success, let products = data {

                    self.products.append(products)
                }

                dispatchSemaphore.signal()
                dispatchGroup.leave()
            }

            dispatchSemaphore.wait()
        }
    }
}

dispatchGroup.notify(queue: dispatchQueue) {

    DispatchQueue.main.async {

        self.refreshOrderTable { _ in

            self.productCollectionView.reloadData()
        }
    }
}

Uygulamamın bir FTP sunucusuna birden fazla dosya göndermesi gerekiyor. Bu yaklaşım, uygulamanın temelde aynı anda ("sıralanmamış" yaklaşımda olduğu gibi) birden çok kez yapmaya çalışmak yerine, yalnızca bir kez (ilk dosyayı yüklemeden önce) oturum açmasını garanti eder. Teşekkürler!
Neph

Yine de bir sorum var: dispatchSemaphore.signal()Ayrılmadan önce veya sonra yaparsanız sorun olur dispatchGroupmu? Semaforun engelini olabildiğince geç açmanın en iyisi olduğunu düşünürdünüz, ancak gruptan ayrılmanın buna müdahale edip etmediğini ve nasıl etkileyeceğini bilmiyorum. Her iki siparişi de test ettim ve bir fark yaratmadı.
Neph

16

ayrıntılar

  • Xcode 10.2.1 (10E1001), Swift 5

Çözüm

import Foundation

class SimultaneousOperationsQueue {
    typealias CompleteClosure = ()->()

    private let dispatchQueue: DispatchQueue
    private lazy var tasksCompletionQueue = DispatchQueue.main
    private let semaphore: DispatchSemaphore
    var whenCompleteAll: (()->())?
    private lazy var numberOfPendingActionsSemaphore = DispatchSemaphore(value: 1)
    private lazy var _numberOfPendingActions = 0

    var numberOfPendingTasks: Int {
        get {
            numberOfPendingActionsSemaphore.wait()
            defer { numberOfPendingActionsSemaphore.signal() }
            return _numberOfPendingActions
        }
        set(value) {
            numberOfPendingActionsSemaphore.wait()
            defer { numberOfPendingActionsSemaphore.signal() }
            _numberOfPendingActions = value
        }
    }

    init(numberOfSimultaneousActions: Int, dispatchQueueLabel: String) {
        dispatchQueue = DispatchQueue(label: dispatchQueueLabel)
        semaphore = DispatchSemaphore(value: numberOfSimultaneousActions)
    }

    func run(closure: ((@escaping CompleteClosure) -> Void)?) {
        numberOfPendingTasks += 1
        dispatchQueue.async { [weak self] in
            guard   let self = self,
                    let closure = closure else { return }
            self.semaphore.wait()
            closure {
                defer { self.semaphore.signal() }
                self.numberOfPendingTasks -= 1
                if self.numberOfPendingTasks == 0, let closure = self.whenCompleteAll {
                    self.tasksCompletionQueue.async { closure() }
                }
            }
        }
    }

    func run(closure: (() -> Void)?) {
        numberOfPendingTasks += 1
        dispatchQueue.async { [weak self] in
            guard   let self = self,
                    let closure = closure else { return }
            self.semaphore.wait(); defer { self.semaphore.signal() }
            closure()
            self.numberOfPendingTasks -= 1
            if self.numberOfPendingTasks == 0, let closure = self.whenCompleteAll {
                self.tasksCompletionQueue.async { closure() }
            }
        }
    }
}

kullanım

let queue = SimultaneousOperationsQueue(numberOfSimultaneousActions: 1, dispatchQueueLabel: "AnyString")
queue.whenCompleteAll = { print("All Done") }

 // add task with sync/async code
queue.run { completeClosure in
    // your code here...

    // Make signal that this closure finished
    completeClosure()
}

 // add task only with sync code
queue.run {
    // your code here...
}

Tam örnek

import UIKit

class ViewController: UIViewController {

    private lazy var queue = { SimultaneousOperationsQueue(numberOfSimultaneousActions: 1,
                                                           dispatchQueueLabel: "AnyString") }()
    private weak var button: UIButton!
    private weak var label: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        let button = UIButton(frame: CGRect(x: 50, y: 80, width: 100, height: 100))
        button.setTitleColor(.blue, for: .normal)
        button.titleLabel?.numberOfLines = 0
        view.addSubview(button)
        self.button = button

        let label = UILabel(frame: CGRect(x: 180, y: 50, width: 100, height: 100))
        label.text = ""
        label.numberOfLines = 0
        label.textAlignment = .natural
        view.addSubview(label)
        self.label = label

        queue.whenCompleteAll = { [weak self] in self?.label.text = "All tasks completed" }

        //sample1()
        sample2()
    }

    func sample1() {
        button.setTitle("Run 2 task", for: .normal)
        button.addTarget(self, action: #selector(sample1Action), for: .touchUpInside)
    }

    func sample2() {
        button.setTitle("Run 10 tasks", for: .normal)
        button.addTarget(self, action: #selector(sample2Action), for: .touchUpInside)
    }

    private func add2Tasks() {
        queue.run { completeTask in
            DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + .seconds(1)) {
                DispatchQueue.main.async { [weak self] in
                    guard let self = self else { return }
                    self.label.text = "pending tasks \(self.queue.numberOfPendingTasks)"
                }
                completeTask()
            }
        }
        queue.run {
            sleep(1)
            DispatchQueue.main.async { [weak self] in
                guard let self = self else { return }
                self.label.text = "pending tasks \(self.queue.numberOfPendingTasks)"
            }
        }
    }

    @objc func sample1Action() {
        label.text = "pending tasks \(queue.numberOfPendingTasks)"
        add2Tasks()
    }

    @objc func sample2Action() {
        label.text = "pending tasks \(queue.numberOfPendingTasks)"
        for _ in 0..<5 { add2Tasks() }
    }
}

5

Bu amaçla semafor kullanmanız gerekecektir.

 //Create the semaphore with count equal to the number of requests that will be made.
let semaphore = dispatch_semaphore_create(locationsArray.count)

        for key in locationsArray {       
            let ref = Firebase(url: "http://myfirebase.com/" + "\(key.0)")
            ref.observeSingleEventOfType(.Value, withBlock: { snapshot in

                datesArray["\(key.0)"] = snapshot.value

               //For each request completed, signal the semaphore
               dispatch_semaphore_signal(semaphore)


            })
        }

       //Wait on the semaphore until all requests are completed
      let timeoutLengthInNanoSeconds: Int64 = 10000000000  //Adjust the timeout to suit your case
      let timeout = dispatch_time(DISPATCH_TIME_NOW, timeoutLengthInNanoSeconds)

      dispatch_semaphore_wait(semaphore, timeout)

     //When you reach here all request would have been completed or timeout would have occurred.

3

Swift 3: Semaforları da bu şekilde kullanabilirsiniz. Çok yararlı sonuçlar verir, ayrıca ne zaman ve hangi işlemlerin tamamlandığını tam olarak takip edebilirsiniz. Bu benim koddan alınmıştır:

    //You have to create your own queue or if you need the Default queue
    let persons = persistentContainer.viewContext.persons
    print("How many persons on database: \(persons.count())")
    let numberOfPersons = persons.count()

    for eachPerson in persons{
        queuePersonDetail.async {
            self.getPersonDetailAndSave(personId: eachPerson.personId){person2, error in
                print("Person detail: \(person2?.fullName)")
                //When we get the completionHandler we send the signal
                semaphorePersonDetailAndSave.signal()
            }
        }
    }

    //Here we will wait
    for i in 0..<numberOfPersons{
        semaphorePersonDetailAndSave.wait()
        NSLog("\(i + 1)/\(persons.count()) completed")
    }
    //And here the flow continues...

1

Bunu özyineleme ile yapabiliriz. Aşağıdaki koddan fikir alın:

var count = 0

func uploadImages(){

    if count < viewModel.uploadImageModelArray.count {
        let item = viewModel.uploadImageModelArray[count]
        self.viewModel.uploadImageExpense(filePath: item.imagePath, docType: "image/png", fileName: item.fileName ?? "", title: item.imageName ?? "", notes: item.notes ?? "", location: item.location ?? "") { (status) in

            if status ?? false {
                // successfully uploaded
            }else{
                // failed
            }
            self.count += 1
            self.uploadImages()
        }
    }
}

-1

Dağıtım grubu iyi ancak gönderilen isteklerin sırası rastgele.

Finished request 1
Finished request 0
Finished request 2

Proje durumumda, başlatılması gereken her istek doğru sıradadır. Bu birine yardımcı olabilirse:

public class RequestItem: NSObject {
    public var urlToCall: String = ""
    public var method: HTTPMethod = .get
    public var params: [String: String] = [:]
    public var headers: [String: String] = [:]
}


public func trySendRequestsNotSent (trySendRequestsNotSentCompletionHandler: @escaping ([Error]) -> () = { _ in }) {

    // If there is requests
    if !requestItemsToSend.isEmpty {
        let requestItemsToSendCopy = requestItemsToSend

        NSLog("Send list started")
        launchRequestsInOrder(requestItemsToSendCopy, 0, [], launchRequestsInOrderCompletionBlock: { index, errors in
            trySendRequestsNotSentCompletionHandler(errors)
        })
    }
    else {
        trySendRequestsNotSentCompletionHandler([])
    }
}

private func launchRequestsInOrder (_ requestItemsToSend: [RequestItem], _ index: Int, _ errors: [Error], launchRequestsInOrderCompletionBlock: @escaping (_ index: Int, _ errors: [Error] ) -> Void) {

    executeRequest(requestItemsToSend, index, errors, executeRequestCompletionBlock: { currentIndex, errors in
        if currentIndex < requestItemsToSend.count {
            // We didn't reach last request, launch next request
            self.launchRequestsInOrder(requestItemsToSend, currentIndex, errors, launchRequestsInOrderCompletionBlock: { index, errors in

                launchRequestsInOrderCompletionBlock(currentIndex, errors)
            })
        }
        else {
            // We parse and send all requests
            NSLog("Send list finished")
            launchRequestsInOrderCompletionBlock(currentIndex, errors)
        }
    })
}

private func executeRequest (_ requestItemsToSend: [RequestItem], _ index: Int, _ errors: [Error], executeRequestCompletionBlock: @escaping (_ index: Int, _ errors: [Error]) -> Void) {
    NSLog("Send request %d", index)
    Alamofire.request(requestItemsToSend[index].urlToCall, method: requestItemsToSend[index].method, parameters: requestItemsToSend[index].params, headers: requestItemsToSend[index].headers).responseJSON { response in

        var errors: [Error] = errors
        switch response.result {
        case .success:
            // Request sended successfully, we can remove it from not sended request array
            self.requestItemsToSend.remove(at: index)
            break
        case .failure:
            // Still not send we append arror
            errors.append(response.result.error!)
            break
        }
        NSLog("Receive request %d", index)
        executeRequestCompletionBlock(index+1, errors)
    }
}

Aramak :

trySendRequestsNotSent()

Sonuç:

Send list started
Send request 0
Receive request 0
Send request 1
Receive request 1
Send request 2
Receive request 2
...
Send list finished

Daha fazla bilgi için bakın: Gist

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.