Does iOS Firebase framework downloads the file on different queue? - ios

I have an app where I send a request to Firebase to download file from it's database.
Here is the code snippet!
// Create a reference to the file you want to download
let islandRef = storageRef.child("images/island.jpg")
// Download in memory with a maximum allowed size of 1MB (1 * 1024 * 1024 bytes)
islandRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if (error != nil) {
// Uh-oh, an error occurred!
} else {
// Data for "images/island.jpg" is returned
// ... imageView.image = UIImage(data: data!)
}
}
So does Firebase sends the downloading operation in other queue other than MAIN queue? The reason why I have question is because I want to update my imageView.image so it would appear on the screen. I do know iOS framework requires MAIN queue to update the UI, so I want to make sure if I need to update it on main queue!

Firebase Storage does the download on a separate background queue so as to not block UI, but surfaces the callback on the main queue, which allows you to perform UI work immediately.
If you want to change which queue the callback is surfaced on, you can use the FIRStorage.setCallbackQueue() method (docs).

Related

AWS S3 iOS - Know when listing objects is finished

I'm listing all the objects in an S3 bucket using the following function
let s3 = AWSS3.default()
let listObjectsRequest = AWSS3ListObjectsV2Request()
s3.listObjectsV2(listObjectsRequest!) { (result, error) in
...
}
It's an asynchronous function. How can I know when the listing is done using Swift 3? I have an activity indicator that is running and stops when the listing is done.
Turns out it had a completion handler.
I just added the code to stop the animation at the bottom of the listObjectsV2.
Just remember to update the UI inside a main queue
DispatchQueue.global(qos: .userInitiated).async {
// Bounce back to the main thread to update the UI
DispatchQueue.main.async {
// Stop animation
}
}

Download Firebase Storage Synchronously

I am working on downloading an image from Firebase Storage and displaying it on a table view. I have been using this:
referenceOfImage.data(withMaxSize: 100 * 1024 * 1024) { data, error in
if let error = error {
print(error)
} else {
guard let data = data else {
print("no data")
return
}
guard let image = UIImage(data: data) else {
print("no image")
return
}
//use image
}
}
However, according to documentation, the task
Asynchronously downloads the object at the FIRStorageReference to an NSData object in memory.
I am currently using a loop to download multiple images and it would work better if I could synchronously download the images (otherwise loop would continue and the task would be incomplete). How can I download the image synchronously? Thanks!
You can not. These methods are asynchronous because they require server calls, and making them synchronous would block the main thread and cause very poor UX and performance. You could set up your completion call to do a bit of recursion, perhaps?
Stick the image load into a function that takes an array of things to fetch, the current index, and a selector to call when finished. Have a terminating condition (index == array.count), which calls the selector you want to happen when all images are loaded, otherwise fetch the image at the index, and in the completion handler, increment the index and fetch the next image by calling the same method.

Firebase Storage download task is not completing after the app has spent some time in the background

I am downloading an image from Firebase storage as follows:
let storage = FIRStorage.storage()
// Create a storage reference from our storage service
let storageRef = storage.reference(forURL: "MY_STORAGE_URL")
let imageRef = storageRef.child("Path_to_image")
// Download image in memory
let downloadTask = imageRef.data(withMaxSize: 1 * 1024 * 1024) {
(data, error) -> Void in
if (error != nil) {
//Handle the error
} else {
guard let imageData = data else {
print("Unable to unwrap image data.")
return
}
let downloadedImage = UIImage(data: imageData)
//Do some stuff with the image
}
}
I am also monitoring what happens with the download using the following observers:
// Observe changes in status
downloadTask.observe(.resume) { (snapshot) -> Void in
// Download resumed, also fires when the download starts
}
downloadTask.observe(.pause) { (snapshot) -> Void in
// Download paused
}
downloadTask.observe(.progress) { (snapshot) -> Void in
// Download reported progress
}
downloadTask.observe(.success) { (snapshot) -> Void in
// Download completed successfully
}
downloadTask.observe(.failure) { (snapshot) -> Void in
//Download failed
}
This all works just fine when the app is first started. However, I am getting problems if the app enters the background and I play around with some other applications (Facebook, Twitter, etc.), then bring the app back to the foreground. I also have problems if I leave the app open and running in the foreground for greater than or equal to 1 hour.
The problem is that the completion handler in let downloadTask = imageRef.data(withMaxSize: blah blah blah (in the first block of code above) is never called. If the completion handler is never called, I can never unwrap the data and attempt to use the image in my application.
Also, in the downloadTask observers, the only completion handlers that get fired are .resume and .progress. The .success or .failure events are never triggered. This seems to be a Firebase Storage bug to me, but I am not sure. Has anyone else encountered a similar issue? I don't understand why the code would work just fine from a fresh launch, but then after some time in the foreground or after some time in the background the image download stops working. Thanks in advance for any input you may have.
This is currently the expected behavior, unfortunately. Firebase Storage (at present) is foreground only: if the app is backgrounded, we haven't persisted the upload URL, and can't upload in the background nor restart it after it gets out of the background, so it probably is killed by the OS and the item isn't uploaded.
It's The Next Big Thing™ we'd like to tackle (our Android SDK makes it possible, though not easy), but unfortunately for now we haven't made more progress on this.
As a bit of a side note, your observers won't exist after the activity change--downloadTask is gone once the app is backgrounded, so when it comes back into the foreground, we basically need a method that retrieves all tasks that are currently backgrounded, and allows you to hook observers back up. Something like:
FIRStorage.storage().backgroundedTasks { (tasks) -> Void in
// tasks is an array of upload and download tasks
// not sure if it needs to be async
}

iOS best way to download bulk images

In my app, I have to download nearly 500 images as I open a ViewController. Downloading all 500 images at a time is not a right idea. I'd like to keep 5 active asynchronous downloads at a time. When any one in five is completed, it should start the next.
I also have a refresh control which will restart downloading all the images from first.
Which technique I could go for to implement this modal?
Here is what I tried so far,
Semaphore is created in property declaration
private var semaphore = dispatch_semaphore_create(5)
After getting web service response,
private func startDownloadingImages() {
for place in places {
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER)
self.downloadImageForPlace(place)
}
}
private func downloadImageForPlace(place: Place) {
ApplicationControls.getImageForPlace(place, withCompletion: { (image, error) -> () in
// error checks
dispatch_async(dispatch_get_main_queue(), {
// UI update
dispatch_semaphore_signal(self.semaphore)
})
})
}
But when I tap refresh control, app locks at dispatch_semaphore_wait and I could able to find a way to reset semaphore.
I would use an OperationQueue like this
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 5;
for url in imageUrls {
queue.addOperationWithBlock { () -> Void in
let img1 = Downloader.downloadImageWithURL(url)
NSOperationQueue.mainQueue().addOperationWithBlock({
//display the image or whatever
})
}
}
you can stop your operations with this
queue.cancelAllOperations();
and then just restart the whole thing.
The only thing that you have to change is that your requests have to be synchronous then. Because this approach wont work with callbacks.

Swift changing text of a label after downloading content of a web page

I have this code and I want to change content of a label with a text from web
var url = NSURL(string: "SOME_URL");
var task = NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: {
(data, response, error) -> Void in
if error == nil {
self.label.text = "SOME_TEXT" // not working
dispatch_async(dispatch_get_main_queue()){
self.label.text = "SOME_TEXT" // working
}
}
else{
println("Error")
}
})
task.resume()
Why do I have to use dispatch_async(dispatch_get_main_queue()){ ... } to change the content of the label ?
The principe of iOS: you have only 1 thread that can modify your UI. It's called UI Thread. Whenever you want to change the UI content, all functions that change your UI content must be called in the UI Thread. In your case, the handler is executed in a background thread, hence you have to put the self.label.text = "SOME_TEXT" in the UI Thread.
It's a very common pattern in GUI programming.
Basically the main message loop runs single threaded without the cost of synchronizing everything. That means that if you want to interact with your window you need to do it on the main GUI thread -- that's what dispatch does, it posts a message on the message queue and the loop interprets it, bringing your code on its thread.
It's both for simplicity and for performance.

Resources