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.
Related
I'm trying to fiddle with concurrent/serial queues and sync/async operations and came across a scenario which I'm not able to solve. Would be glad if someone can assist.
So it goes like this -
I have a queue and I'm trying to simulate an async image download operation by using asynAfter, and I'm able to get below result by this code.
var downloadQueue = DispatchQueue(label: "com.image.download", attributes: .concurrent)
var operation1 = {
downloadQueue.asyncAfter(deadline: DispatchTime.now() + 6.0) {
print("Image Download 1 Done")
}
}
var operation2 = {
downloadQueue.asyncAfter(deadline: DispatchTime.now() + 4.0) {
print("Image Download 2 Done")
}
}
var operation3 = {
downloadQueue.asyncAfter(deadline: DispatchTime.now() + 2.0) {
print("Image Download 3 Done")
}
}
operation1()
operation2()
operation3()
OUTPUT:
Image Download 3 Done //Prints after 2 seconds
Image Download 2 Done //Prints after 4 seconds
Image Download 1 Done //Prints after 6 seconds
Now the question arises if I want to get below 2 scenarios -
I want operation2 to start after my operation1 finishes, and operation3 to start after operation2 finishes. So that all operations are completed in combined (6.0+4.0+2.0) 12.0 seconds.
I want all operations to start simultaneously, but completions to trigger in order they were entered in queue. So that all operations are completed in combined 6.0 seconds.
I tried serial queue and concurrent queue with sync/async blocks, but everytime answer is same. Please guide.
Suppose you have an array with imageURLs that contains all image URL that you want to download.
let imageUrls = ["imageUrl1","imageUrl2","imageUrl3"]
Here is the block method that will process operation ex: image download in this case.
func getResult(url:String, completion: #escaping (Any?) -> Void) {
.... // THIS IS YOUR PROCESS THAT YOU WANT TO EXECUTE
// After completing process you got the image
completion(image)
}
In the completion block, you just pass the value that you have got (image in this case)
Now, this is the main process to use the getResult block method. Suppose you have a method like downloadAllImages that need imageUrls.
func downloadAllImages(imageUrls: [String]) {
var imageUrls = imageUrls
if imageUrls.count > 0 {
getResult(url: imageUrls[0]) { (data) in
// Now you are in the Main thread
// here data is the output got the output
imageUrls.remove(at: 0)// remove the down element
downloadAllImages(imageUrls: imageUrls) // Again call the next one
}
}
}
Hope you understand.
The point of asyncAfter(deadline:) is to submit a block to the queue at some point in the future. But what you're saying is that you want to submit a block now and have it block the queue until it has completed. First, if you want things to occur in order, then you want a serial queue (and in fact you almost always want a serial queue).
let downloadQueue = DispatchQueue(label: "com.image.download")
Then you're saying you have a task that you want to take 6 seconds, and block the queue until it's done. That's generally not something you should do, but for testing it's of course fine, and you'd use sleep to achieve it. (You should almost never use sleep, but again, for testing it's fine.)
let operation1 = {
Thread.sleep(forTimeInterval: 6)
print("Image Download 1 Done")
}
let operation2 = {
Thread.sleep(forTimeInterval: 4)
print("Image Download 2 Done")
}
let operation3 = {
Thread.sleep(forTimeInterval: 2)
print("Image Download 3 Done")
}
And submit them:
downloadQueue.async(execute: operation1)
downloadQueue.async(execute: operation2)
downloadQueue.async(execute: operation3)
If, alternately, you want these to run in parallel, you can use a concurrent queue instead.
For non-testing situations, there are other techniques you generally should use (most commonly DispatchGroup), but for something where you're just simulating something taking time, this is fine.
You said:
Now the question arises if I want to get below 2 scenarios -
I want operation2 to start after my operation1 finishes, and operation3 to start after operation2 finishes. So that all operations are completed in combined (6.0+4.0+2.0) 12.0 seconds.
First, this is a pattern generally to be avoided with network requests because you’re going to magnify network latency effects. You would only use this pattern where absolutely needed, e.g. if request 1 was a “sign in” operation; or, you needed something returned in the first request in order to prepare the subsequent request).
Often we’d do something simple, such as initiating the subsequent request in the completion handler of the first. Or, if you wanted a more flexible set of dependencies from a series of requests, you adopt a pattern that doesn’t use dispatch queues, e.g. you might create a custom, asynchronous Operation subclass that only completes when the network request is done (see point 3 in https://stackoverflow.com/a/57247869/1271826). Or if targeting recent OS versions, you might use Combine. There are a whole bunch of alternatives here. But you can’t just start a bunch of asynchronous tasks and have them run sequentially without one of these sorts of patterns.
I want all operations to start simultaneously, but completions to trigger in order they were entered in queue. So that all operations are completed in combined 6.0 seconds.
The whole idea of concurrent patterns is that you shouldn’t care about the order that they finish. So, use a structure that is not order-dependent, and then you can store the results as they come in. And use dispatch group to know when they’re all done.
But one thing at a time. First, how do you know when a bunch of concurrent requests are done? Dispatch groups. For example:
let group = DispatchGroup()
group.enter()
queue.asyncAfter(deadline: .now() + 6) {
defer { group.leave() }
print("Image Download 1 Done")
}
group.enter()
queue.asyncAfter(deadline: .now() + 4) {
defer { group.leave() }
print("Image Download 2 Done")
}
group.enter()
queue.asyncAfter(deadline: .now() + 2) {
defer { group.leave() }
print("Image Download 3 Done")
}
group.notify(queue: .main) {
// all three are done
}
Now, how do you take these requests, store the results, and retrieve them in the original order when you’re all done? First, create some structure that is independent of the order of the tasks. For example, let’s say you were downloading a bunch of images from URLs, then create a dictionary.
var images: [URL: UIImage] = [:]
Now fire off the requests concurrently:
for url in urls {
group.enter()
downloadImage(url) { result in
defer { group.leave() }
// do something with the result, e.g. store it in our `images` dictionary
switch result {
case .failure(let error): print(error)
case .success(let image): images[url] = image
}
}
}
// this will be called on main queue when they’re all done
group.notify(queue: .main) {
// if you want to pull them in the original order, just iterate through your array
for url in urls {
if let image = images[url] {
print("image \(url) has \(image.size)")
}
}
}
By the way, the above is using the following method to retrieve the images:
enum DownloadError: Error {
case unknown(Data?, URLResponse?)
}
#discardableResult
func downloadImage(_ url: URL, completion: #escaping (Result<UIImage, Error>) -> Void) -> URLSessionTask {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard
let responseData = data,
let image = UIImage(data: responseData),
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode,
error == nil
else {
DispatchQueue.main.async {
completion(.failure(error ?? DownloadError.unknown(data, response)))
}
return
}
DispatchQueue.main.async {
completion(.success(image))
}
}
task.resume()
return task
}
The details here are not relevant. The key observation is that you should embrace concurrent patterns for determining when tasks are done (using dispatch groups, for example) and retrieving the results (store results in an unordered structure and access them in a manner that honors the order you intended).
These are the methods and processes which need to happen:
Download images from Parse
Download associated data (which matches images)
Plot this data on a map
Here is my code from view did load:
override func viewDidLoad() {
imageDownload { () -> () in
print("5-----inside closure")
self.queryParseandSave(callback: self.plotImages)
}
}
Image download function:
func imageDownload(completed: #escaping FinishedDownload){
print("1-----Started Image download")
// Query for places
let query = PFQuery(className:"ViewFinderObjects")
query.whereKey("ImageVerified", equalTo: true)
query.whereKey("coordinates", nearGeoPoint:myGeoPoint)
query.limit = 10
query.findObjectsInBackground { (objects, error) -> Void in
if (error == nil) {
for object in objects! {
print("2-----inside object for block")
let imageFromParse = object["image"] as! PFFile
imageFromParse.getDataInBackground(block: {(imageData, error) -> Void in
print("Searching for Image")
if error == nil {
let obsImage:UIImage = UIImage(data: imageData!)!
self.imageToShow = obsImage
self.closestImage.append(self.imageToShow!)
print("There are \(self.closestImage.count) images in the image array")
}
})
print("3-----Completed object loop")
}
}
print("4-----Calling completed statement")
completed()
}
}
Which then calls another function queryParseandSave(callback: self.plotImages)
with the self.plotImages plotting the images on a map.
I have 1 huge issue:
self.plotImahes is always called before the images have finished downloading
I have researched async_dispatch but have no idea if this is the right thing to do.
I'm not familiar with the implementations of the query.findObjectsInBackground and imageFromParse.getDataInBackground methods, but their naming implies that they both happen asynchronously. Also judging from what you're provided above, the former retrieves the object data, while the latter does the actual image data download. If that is indeed the case, then it looks like you're calling your completion handler inside the body of the first asynchronous method instead of waiting for the second method (what appears to be the actual image download).
A couple of ideas for how to resolve this:
You could move your completion handler into the imageFromParse.getDataInBackground block, but this would only make sense if you're comfortable calling the completion block multiple times, after each image finishes downloading.
You could create your own dispatch or operation queue and wait until all tasks complete, then call the completion handler.
You could set up an observer or notification pattern that will call your completion handler at the appropriate time.
There are many different ways to address the issue, but the key thing to remember is that it sounds like you want to call your completion handler after all of the asynchronous operations have completed. Right now you're calling it after you've retrieved the objects, but all of your images are still downloading in the background when your completion handler is called.
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
}
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).
I would like to update some data on my device in the background. For example:
Ill got a webservice, which returns me some changes that i need to store into my core data. So my goal is to load the JSON data with Alamofire, loop through all my json objects, and store them into my database. When everything is finished, i would like to update my UI.
The changes could be some hundred rows, so i would like to perform this on the background thread. When ill use:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
var context = appDel.managedObjectContext
do {
tasks = try context.executeFetchRequest(fetchRequest) as! [Tasks]
}
catch {
fatalError("Fetching from the store failed")
}
for task in tasks {
// check something here, or set some new values
}
// save them in background? But how do i know if save is completed?
dispatch_async(dispatch_get_main_queue()) {
// update my UI
}
});
Will this all run in background, or do i need multiple contexts here? Ill read somewhere else, that when ill load my managedObjectContext inside a Thread, the whole Fetch runs in background. And how do i know when my context has finished saving, so i can refresh my UITableView?
Thanks in advance!
You should perform the background code within context.performBlock {} instead of dispatch_async
Call completionHandler(.NewData) when you're done