Асинхронные функции занимают много времени при использовании Dispatch Group - PullRequest
0 голосов
/ 10 октября 2019

Я использую Firebase Storage для хранения фотографий профиля пользователей моего приложения, из которых они могут иметь до шести. Поэтому, когда пользователь сохраняет свои изображения, я создаю DispatchGroup и одновременно удаляю все ранее связанные photoUrls в Firebase Storage и загружаю новые изображения в Firebase Storage. Пользовательский объект в базе данных Firebase также обновляется. Я полагаю, что это проблема способа реализации DispatchGroup, поэтому я предоставлю псевдокод того, как я его использую:

//PSEUDOCODE
func buttonPressed() {
    let group = DispatchGroup()
    for url in oldUrl {
        group.enter()
        asyncFuncThatDeletesImage(at: url) {
            group.leave()
        }
    }

    for image in newImages {
        group.enter()
        asyncFuncThatUploadsNewImage(image) {
            group.leave()
        }
    }

    group.notify(queue: .main) {
        //continue
    }
}

Мои асинхронные функции (asyncFuncThatDeletesImage и asyncFuncThatUploadsNewImage) в отдельности занимает максимум пару секунд. Однако при использовании моего DispatchGroup .notify не вызывается, пока не пройдет около 2 минут .

Вот полный код для всех, кто интересуется:

@IBAction func saveButtonPressed(_ sender: Any) {
    let oldUrls = parentVC.photoUrls
    activityIndicator.isHidden = false
    activityIndicator.startAnimating()
    let photoUploader = PhotoUploader()
    var data = [Data]()
    var urls: [Int:String] = [:]

    let group = DispatchGroup()
    for oldUrl in oldUrls {
        group.enter()
        let storageRef = Storage.storage().reference(forURL: oldUrl)

        //Removes image from storage
        storageRef.delete { error in
            if let error = error {
                print(error)
                group.leave()
            } else {
                // File deleted successfully
                print("File deleted, took \(Date().timeIntervalSince1970 - startTime) seconds")
                group.leave()
            }
        }
    }

    for (index,image) in imagesArray.enumerated() {
        group.enter()
        let imageData = image.jpegData(compressionQuality: 1)!
        data.append(imageData)
        photoUploader.upload(image: image, to: "Users/\(Auth.auth().currentUser!.uid)/photoUrls/\(index)", completion: {(url, error) in
            if error == nil {
                urls[index] = url
                group.leave()

                print("Image uploaded, took \(Date().timeIntervalSince1970 - startTime) seconds")
            } else {
                print(error?.localizedDescription)
                group.leave()
            }
        })
    }

    for i in imagesArray.count...6 {
        Database.database().reference().child("Users/\(Auth.auth().currentUser!.uid)/photoUrls/\(i)").removeValue()
    }

    group.notify(queue: .main) {
        //Do whatever I need to do
    }

Вот функция в моем PhotoUploader классе:

func upload(image: UIImage, to firebasePath: String, completion: @escaping (String?, Error?) -> Void) {
    //... creates data from the image...
    var data = NSData()
    // data = UIImageJPEGRepresentation(image, 0.8)! as NSData
    data = image.jpegData(compressionQuality: 0.8)! as! NSData
    //...sets the upload path
    let filePath = "\(Auth.auth().currentUser!.uid)\(String.randomStringWithLength(len: 20))" // path where you wanted to store img in storage
    let metaData = StorageMetadata()
    metaData.contentType = "image/jpg"

    let storageRef = Storage.storage().reference().child(filePath)
    storageRef.putData(data as Data, metadata: metaData){(metaData,error) in
        if let error = error {
            print(error.localizedDescription)
            completion(nil, error)
            return
        } else {
            storageRef.downloadURL(completion: { (url, error) in
                //Returns the url string to the newly uploaded image so that we can set the user's photoURL database property to this string
                Database.database().reference().child(firebasePath).setValue(url!.absoluteString)
                completion(url!.absoluteString, nil)
            })
        }
    }
}

1 Ответ

1 голос
/ 11 октября 2019

Возможно, вы ставите notify в очередь .main. Или что ваши async функции не должны находиться в очереди .main. В любом случае, я бы сделал независимую очередь .concurrent и поставил бы ваши async вызовы и notify на это. т.е.

let customQueue = DispatchQueue(label: "customQueue", attributes: .concurrent)

...

func asyncFuncThatUploadsNewImage(image){
    customQueue.async{ ... }
}
func asyncFuncThatDeletesImage(image){
    customQueue.async{ ... }
}

...

group.notify(queue: customQueue) {
    //continue
}

Мне кажется, что вы правильно используете DispatchGroup.

Помещение вещей в основную очередь отличается от настраиваемой очереди по нескольким причинам, одна из которых состоит в том, что это последовательная, а не параллельная очередь, а также (я думаю), что другие вещи могут выполняться в главной очередичто вы не знаете, что усложняет обоснование, которое вы должны сделать.

Правка из OP Предлагаемое правка сработало, хотя я не совсем уверен, почему. Кроме того, в методе group.notify(queue:), несмотря на то, что я прошел пользовательскую очередь, мне пришлось обернуть вложенный код в основной поток. Это интересно, но вот код, который работает:

@IBAction func saveButtonPressed(_ sender: Any) {
    let oldUrls = parentVC.photoUrls
    activityIndicator.isHidden = false
    activityIndicator.startAnimating()
    let photoUploader = PhotoUploader()
    var data = [Data]()
    var urls: [Int:String] = [:]

    //Custom queue
    let customQueue = DispatchQueue(label: "customQueue", attributes: .concurrent)
    let group = DispatchGroup()
    for oldUrl in oldUrls {
        group.enter()
        customQueue.async {
            let storageRef = Storage.storage().reference(forURL: oldUrl)

            //Removes image from storage
            storageRef.delete { error in
                if let error = error {
                    print(error)
                    group.leave()
                } else {
                    // File deleted successfully
                    print("File deleted, took \(Date().timeIntervalSince1970 - startTime) seconds")
                    group.leave()
                }
            }
        }
    }

    for (index,image) in imagesArray.enumerated() {
        group.enter()
        customQueue.async {
            let imageData = image.jpegData(compressionQuality: 1)!
            data.append(imageData)
            photoUploader.upload(image: image, to: "Users/\(Auth.auth().currentUser!.uid)/photoUrls/\(index)", completion: {(url, error) in
                if error == nil {
                    urls[index] = url
                    group.leave()

                    print("Image uploaded, took \(Date().timeIntervalSince1970 - startTime) seconds")
                } else {
                    print(error?.localizedDescription)
                    group.leave()
                }
            })
        }
    }

    for i in imagesArray.count...6 {
        Database.database().reference().child("Users/\(Auth.auth().currentUser!.uid)/photoUrls/\(i)").removeValue()
    }

    group.notify(queue: customQueue) {
        DispatchQueue.main.async {
            //Do whatever I need to do
        }
    }
...