Kevv Keka
Kevv Keka

Reputation: 314

Swift 5 : Wait on Global Queue to finish execution on on main queue

We got a project from another team as the original dev left the org. I need to fix a defect. I have some code like below where UIGraphicsBeginPDFContextToData happens on Global queue. But this gives memory warnings as below UIView extension's renderToImage does not happens on main thread.

I wanted to wrap the renderToImage in DispatchQueue.main.async but it would break the for loop in someFunction. I can't use .sync on main thread. Can someone tell me how can I finish renderToImage waiting on Global queue?

ORIGINAL CODE:

         DispatchQueue.global(qos: .userInitiated).async {
             let pdfData = NSMutableData()
             UIGraphicsBeginPDFContextToData(pdfData, CGRect.zero, nil)

             zip(self.pdfPages, self.pageViews).forEach { (page, view) in

                 let image = view.renderToImage()
                 guard let imageData = image.jpegData(compressionQuality: 0.5),
                     let compressedImage = UIImage(data: imageData) else { return }

                 UIGraphicsBeginPDFPageWithInfo(CGRect(origin: CGPoint.zero, size: page.originSize), nil)
                 compressedImage.draw(in: CGRect(origin: CGPoint.zero, size: page.originSize))

                 DispatchQueue.main.async {
                     self.progressLabel.text = "Processing PDF... \(page.pageNumber) / \(pageNumbers)"
                 }
             }
             UIGraphicsEndPDFContext()

             DispatchQueue.main.async {
                 self.progressIndicator.stopAnimating()
                 self.progressView.isHidden = true
                 completionBlock?(pdfData as Data)
             }
         }


extension UIView {
    func renderToImage() -> UIImage {
        UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0.0)
        guard let context = UIGraphicsGetCurrentContext() else { return UIImage() }
        layer.render(in: context)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return image!
    }
}

After applying @paulw11 answer:


    fileprivate func generatePDF(_ completionBlock: ((_ pdfData: Data) -> Void)?) {

        let pageNumbers = pageViews.count

        progressView.isHidden = false
        progressIndicator.startAnimating()
        progressLabel.text = "Processing PDF... \(0) / \(pageNumbers)"

        let semaphore = DispatchSemaphore(value: 0)
        let pdfContextQueue = DispatchQueue(label: kMessagesFaxPDFConversionQueue, target: DispatchQueue.global(qos: .userInitiated))

        let pdfData = NSMutableData()
        UIGraphicsBeginPDFContextToData(pdfData, CGRect.zero, nil)

        pdfContextQueue.async {
            zip(self.pdfPages, self.pageViews).forEach { (page, view) in
                view.renderToImage { image in
                    guard let imageData = image.jpegData(compressionQuality: 0.5),
                    let compressedImage = UIImage(data: imageData) else { return }

                    UIGraphicsBeginPDFPageWithInfo(CGRect(origin: CGPoint.zero, size: page.originSize), nil)
                    compressedImage.draw(in: CGRect(origin: CGPoint.zero, size: page.originSize))

                    DispatchQueue.main.async {
                        self.progressLabel.text = "Processing PDF... \(page.pageNumber) / \(pageNumbers)"
                    }
                    semaphore.signal()
                }
                semaphore.wait()
            }
            UIGraphicsEndPDFContext()

            DispatchQueue.main.async {
                self.progressIndicator.stopAnimating()
                self.progressView.isHidden = true
                completionBlock?(pdfData as Data)
            }
        }
}


extension UIView {
    func renderToImage(completion: @escaping ((UIImage) -> Void)) {
         DispatchQueue.main.async {
             UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0.0)

             guard let context = UIGraphicsGetCurrentContext() else {
                 completion(UIImage()); return
             }

             self.layer.render(in: context)
             let image = UIGraphicsGetImageFromCurrentImageContext()
             UIGraphicsEndImageContext()

             completion(image!)
         }
     }
}

Upvotes: 0

Views: 815

Answers (1)

Paulw11
Paulw11

Reputation: 114836

The first thing I would check is if you can't simply perform the work on the main queue - Does it really take that long that there is a noticeable impact on the UI? What else would the user be doing while the PDF was being generated anyway? If they are waiting for the PDF to be generated so that it can be shared or printed, then simply showing a spinner may be enough.

If you do want to keep the work on another queue then I would convert renderToImage to an asynchronous function and use a DispatchSemaphore to block your work queue until the loop iteration is complete.

func someFunction() {
    let semaphore = DispatchSemaphore(value:0)
    let queue = DispatchQueue(label:"PDFQueue", target: DispatchQueue.global(qos: .userInitiated))
    queue.async {
        for view in PDFViews {
            UIGraphicsBeginPDFContextToData(pdfData, CGRect.zero, nil)
            zip(self.pdfPages, self.pageViews).forEach { (page, view) in
                view.renderToImage { image in 
                   if let image = image {
                      ...
                      ...
                   }
                   semaphore.signal()
                }
                semaphore.wait()
            }
        }
    }   
}

extension UIView {
    func renderToImage(completion: ((UIImage?)->Void)) {
        DispatchQueue.main.async {
            UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0.0)
            guard let context = UIGraphicsGetCurrentContext() else {
                completion(nil)
            }

            layer.render(in: context)
            let image = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()

            completion(image)
        }
    }   
}


Upvotes: 1

Related Questions