Sequential upload / serial queue with alamofire - swift

I'm using Alamofire and a serial DispatchQueue to try and upload one image at a time from an array of images. I would like to upload one at a time so I can update a single progress HUD as each upload goes through. My code looks something like this:
let serialQueue = DispatchQueue(label: "project.serialQueue")
for image in images {
serialQueue.async {
uploadImage(image: image, progress: { progress in
//update progress HUD
}, completion: { json, error in
//dismiss HUD
})
}
}
The problem is that my uploadImage() calls are all executing at once. I think this is because Alamofire requests are run asynchronously. Any ideas on how to best solve this?

Alamofire runs in his own background queue , so You can try
var counter = 0
func uploadImg(_ img:UIImage) {
uploadImage(image: img, progress: { progress in
//update progress HUD
}, completion: { json, error in
//dismiss HUD
self.counter += 1
if self.counter < images.count {
self.uploadImg(images[counter])
}
})
}
Call
uploadimg(images.first!)

Related

Swift : telling callback to run on global queue

I'm running a background upload task but I found it's blocking the main thread. After looking I suspect this happens because 3rd party library (Firebase in this case) must be scheduling its async callback on the main thread.
Is there a way to explicitly make the callback run on the global thread?
Here's how I start the task from the main thread:
DispatchQueue.global(qos: .background).async {
PhotoUploadOperation().start()
}
Here's an oversimplified version of the upload task:
class PhotoUploadOperation {
func uploadCameraRoll() {
for element in photos {
self.uploadPhoto(element.image, uid) { url in
// Some work
if let url = url {
let photo = Photo(uid: uid, url: url, creationDate: element.date)
self.sendPhoto(photo: photo) { success in
// Some work
}
}
}
}
}
}
Could you try this solution?
Firebase or other 3party framework use "method swizling" to accomplish some network log or other things. But your scenario is a little bit different.
extension DispatchQueue {
static func background(delay: Double = 0.0, background: (()->Void)? = nil, completion: (() -> Void)? = nil) {
DispatchQueue.global(qos: .background).async {
background?()
if let completion = completion {
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: {
completion()
})
}
}
}
}
Usage:
DispatchQueue.background(delay: 3.0, background: {
// do something in background
}, completion: {
// when background job finishes, wait 3 seconds and do something in main thread
})
DispatchQueue.background(background: {
// do something in background
}, completion:{
// when background job finished, do something in main thread
})
DispatchQueue.background(delay: 3.0, completion:{
// do something in main thread after 3 seconds
})
And you dont forget allow background processing from Signing and Capabilities

SWIFT 5: Increment Value Async of Determinate Circular Progress Indicator

I searched a lot but I can't find a good explanation of using a determinate circular progress indicator.
I have a Helper-Class that converts a PDF to an image. I have more than 400 pages so I decided to make these things asynchron with the following code:
DispatchQueue.concurrentPerform(iterations: pdfDocument.numberOfPages) { i in
… do the work …
view.incrementProgressIndicator()
}
Insight the view, where the ProgressIndicator lives, the called function looks the following:
func incrementProgressIndicator() {
self.progressIndicator.increment(by: 1)
}
For sure, Swift says: "UI API called on a background thread".
I tried to surround the code of incrementing with:
DispatchQueue.main.async {
self.progressIndicator.increment(by: 1)
}
But now, all the work of indicating is done after the conversion of PDF is completed.
Can someone tell my how this can be done? I can't find a working tutorial for my issue.
Thank you for your help!
I found the solution for my issue. I replace the
DispatchQueue.concurrentPerform(iterations: pdfDocument.numberOfPages) { i in
… do the work …
view.incrementProgressIndicator()
}
with the following code:
for i in 0..<pdfDocument.numberOfPages {
DispatchQueue.global(qos: .background).async {
let pdfPage = pdfDocument.page(at: i + 1)!
if let result = self.convertPageToImage(withPdfPage: pdfPage, sourceURL: sourceURL, destUrl: destinationURL, index: i) {
urls[i] = result
}
DispatchQueue.main.async {
if let delegate = self.delegate {
print("Ask for Increment")
delegate.incrementProgressIndicator()
}
}
}
}

Convert images to videos in iOS without locking UI Thread (Swift)

I am trying to convert image snapshots into a video but I am facing UI Thread problems: my view controller is locked. I would like to know how to handle this because I did a lot of research and tried to detach the process into different DispatchQueues but none of them worked. So, it explains why I am not using any Queue on the code below:
class ScreenRecorder {
func renderPhotosAsVideo(callback: #escaping(_ success: Bool, _ url: URL)->()) {
var frames = [UIImage]()
for _ in 0..<100 {
let image = self.containerView.takeScreenshot()
if let imageData = UIImageJPEGRepresentation(image, 0.7), let compressedImage = UIImage(data: imageData) {
frames.append(compressedImage)
}
}
self.generateVideoUrl(frames: frames, complete: { (fileURL: URL) in
self.saveVideo(url: fileURL, complete: { saved in
print("animation video save complete")
callback(saved, fileURL)
})
})
}
}
extension UIView {
func takeScreenshot() -> UIImage {
let renderer = UIGraphicsImageRenderer(size: self.bounds.size)
let image = renderer.image { _ in
self.drawHierarchy(in: self.bounds, afterScreenUpdates: true)
}
return image
}
}
class ViewController {
let recorder = ScreenRecorder()
recorder.renderPhotoAsVideo { success, url in
if (success) {
print("ok")
} else {
self.alert(title: "Erro", message: "Nao foi possivel salvar o video", block: nil)
}
}
}
PS: I used this tutorial as reference -> http://www.mikitamanko.com/blog/2017/05/21/swift-how-to-record-a-screen-video-or-convert-images-to-videos/
It really looks like this is not possible, at least not the way you are trying to do it.
There are quite a few ways to render a UIViews content into an image, but all of them must be used from the main thread only. This applies also to the drawInHierarchy method you are using.
As you have to call it on the main thread and the method is just getting called so many times, I think this will never work in a performant way.
See profiling in Instruments:
Sources:
How to render view into image faster?
Safe way to render UIVIew to an image on background thread?

Playing audio freezes the main thread

For some reason the following code freezes the main thread when I try playing audio. I've tried everything from dispatching to another thread but that doesn't do much. I even tried running the queue in async, but then I run into problems when the user clicks another audio file before the first one finishes loading. Any tips on which thread to use?
let serialQueue = DispatchQueue(label: "queuename")
serialQueue.sync {
if (jukeboxPlayer.jukebox != nil) {
if let playingURL = jukeboxPlayer.jukebox?.currentItem?.URL {
jukeboxPlayer.jukebox?.removeItems(withURL: playingURL)
}
jukeboxPlayer.jukebox?.append(item: JukeboxItem(URL: url), loadingAssets: true)
} else {
jukeboxPlayer.jukebox = Jukebox(delegate: self, items: [
JukeboxItem(URL: url),
])
}
print("3.0")
jukeboxPlayer.jukebox?.play()
print("==========Started playing===========")
self.showPlayer()
}
Also tried:
serialQueue.sync {
and
DispatchQueue.global(qos: .background).async {
And here's the framework I use:
https://github.com/teodorpatras/Jukebox

Setting GIF image blockes UI when updating performed in background or main thread

I'm setting a GIF image within a UIImageView using an objective-c library FLAnimatedImage. I'm currently facing an issue where the UI is being blocked while the GIF image is being loaded (touches not registered on overlay UIView which is presented in front of the image, overlay view is hidden/shown on tap). I've tried updating the image on a background thread and on a main thread, results remain the same. Is there something I'm doing wrong here? Or any alternatives? (PS. I also have a UITextView with detects links, doubt that's important)
UITableViewCell
//Main Thread blocks UI
dispatch_async(dispatch_get_main_queue(), { () -> Void in
cell.shotImg.setShotImage(self.shots[self.currentIndex])
})
//Background thread also blocks ui. See code below for background thread function
backgroundThread(0.1, completion: {
cell.shotImg.setShotImage(self.shots[self.currentIndex])
})
FLAnimatedImage Extension
extension FLAnimatedImageView{
func setRegularImage(url: String){
self.af_setImageWithURL(NSURL(string: url)!)
}
func setGIFImage(url: String){
let animatedImage = FLAnimatedImage(animatedGIFData: NSData(contentsOfURL: NSURL(string: url)!)!)
self.animatedImage = animatedImage
}
func setShotImage(shot: Shots){
var url: String!
if(shot.images.hidpi != nil){
url = shot.images.hidpi
}else{
url = shot.images.normal
}
if(shot.animated!){
self.setGIFImage(url)
}else{
self.setRegularImage(shot.images.normal)
}
}
}
Background thread
func backgroundThread(delay: Double = 0.0, background: (() -> Void)? = nil, completion: (() -> Void)? = nil) {
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.rawValue), 0)) {
if(background != nil){ background!(); }
let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC)))
dispatch_after(popTime, dispatch_get_main_queue()) {
if(completion != nil){ completion!(); }
}
}
}
My guess is that the problem is this:
NSData(contentsOfURL: NSURL(string: url))
That's not how you download something (if that's what you're doing). Use NSURLSession.
Unless you have static cells, you cannot update the cell directly in a background thread as you are doing. While you scroll, the table view will reuse cells off screen for those newly appearing. By the time the background task finishes, the cell is likely to belong to another row.
Instead you should store the image in an array when the task finishes, and then call reloadData (on the main thread). Then cellForRowAtIndexPath should display the image from the array, or fetch it in the background if it hasn't been fetched yet (which will trigger the reloadData to display it when finished).