Swift closure async order of execution
A couple of observations:
It will always execute what's at 1 before 2. The only way you'd get the behavior you describe is if you're doing something else inside that for loop that is, itself, asynchronous. And if that were the case, you'd use a dispatch group to solve that (or refactor the code to handle the asynchronous pattern). But without seeing what's in that for loop, it's hard to comment further. The code in the question, alone, should not manifest the problem you describe. It's got to be something else.
Unrelated, you should note that it's a little dangerous to be updating model objects inside your asynchronously executing for loop (assuming it is running on a background thread). It's much safer to update a local variable, and then pass that back via the completion handler, and let the caller take care of dispatching both the model update and the UI updates to the main queue.
In comments, you mention that in the
for
loop you're doing something asynchronous, and something that must be completed before the completionHandler is called. So you'd use a dispatch group to do ensure this happens only after all the asynchronous tasks are done.Note, since you're doing something asynchronous inside the
for
loop, not only do you need to use a dispatch group to trigger the completion of these asynchronous tasks, but you probably also need to create your own synchronization queue (you shouldn't be mutating an array from multiple threads). So, you might create a queue for this.
Pulling this all together, you end up with something like:
func fetchMostRecent(completionHandler: ([TableItem]?) -> ()) {
addressBook.loadContacts { contacts, error in
var sections = [TableItem]()
let group = dispatch_group_create()
let syncQueue = dispatch_queue_create("com.domain.app.sections", nil)
if let unwrappedContacts = contacts {
for contact in unwrappedContacts {
dispatch_group_enter(group)
self.someAsynchronousMethod {
// handle contacts
dispatch_async(syncQueue) {
let something = ...
sections.append(something)
dispatch_group_leave(group)
}
}
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
self.mostRecent = sections
completionHandler(sections)
}
} else {
completionHandler(nil)
}
}
}
And
model.fetchMostRecent { sortedSections in
guard let sortedSections = sortedSections else {
// handle failure however appropriate for your app
return
}
// update some UI
self.state = State.Loaded(sortedSections)
self.tableView.reloadData()
}
Or, in Swift 3:
func fetchMostRecent(completionHandler: @escaping ([TableItem]?) -> ()) {
addressBook.loadContacts { contacts, error in
var sections = [TableItem]()
let group = DispatchGroup()
let syncQueue = DispatchQueue(label: "com.domain.app.sections")
if let unwrappedContacts = contacts {
for contact in unwrappedContacts {
group.enter()
self.someAsynchronousMethod {
// handle contacts
syncQueue.async {
let something = ...
sections.append(something)
group.leave()
}
}
}
group.notify(queue: .main) {
self.mostRecent = sections
completionHandler(sections)
}
} else {
completionHandler(nil)
}
}
}
Swift closures - order of code execution in regards to value assignment
Here is a summary of your function in question:
...
context.perform {
entities = try! fetch.execute()
completion(entities)
}
return entities
Swift sees it like this:
- Call this
context.perform
function - Call
return entities
- Process the output of
context.perform
inside the closure
Because of the order, it just returns the value and considers its work done. Your closure will likely complete, assuming it stays in scope, but otherwise nothing comes of it.
The easiest thing would be to change your function from:
@nonobjc public class func fetchEvents(completion: @escaping ([Event]) -> () = { _ in }) -> [Event]
to:
@nonobjc public class func fetchEvents(completion: @escaping ([Event]) -> () = { _ in })
The difference here is the removal of the [Event]
return.
Then, inside your function remove the return entities
line.
You now have an asynchronous function, so whatever you use to call it will have to use a closure for the [Event] value.
self.events = Event.getEventsFromAPI()
becomes something like:
Event.getEventsFromAPI() = { [weak self] events in
self?.events = events
}
Swift Closures and order of execution
Your implementation of load(onFinish:)
is very surprising and over-complicated. Luckily, though, that helps demonstrate the point you were asking about.
A closure is executed when something calls it. So in your case, onFinish
is called at the end of the method, which makes it synchronous. Nothing about being "a closure" makes anything asynchronous. It's just the same as calling a function. It is completely normal to call a closure multiple times (map
does this for instance). Or it might never be called. Or it might be called asynchronously. Fundamentally, it's just like passing a function.
When I say "it's slightly different than an anonymous function," I'm just referring to the "close" part of "closure." A closure "closes over" the current environment. That means it captures variables in the local scope that are referenced inside the closure. This is slightly different than a function (though it's more about syntax than anything really deep; functions can actually become closures in some cases).
The better implementation would just return the array in this case.
Swift: Order of Execution for Closures
If you have multiple async requests and you need to wait for all to complete, you could use a DispatchGroup
to notify you when all the tasks in the group completed.
In your case, you need to wait until all images were downloaded before calling the completion
function of getImages
func getImages(completion: @escaping([UIImage?]) -> Void) {
var downloadedImages: [UIImage?] = []
let downloadGroup = DispatchGroup()
for i in 0...8 {
downloadGroup.enter()
let imagePath = "/img" + String(i)
getImageFromStorage(imagePath: imagePath, completion: { myImage in
downloadedImages.append(myImage)
downloadGroup.leave()
})
}
downloadGroup.notify(queue: .main) {
// will be called when all requests are done
completion(downloadedImages)
}
}
Execution order of closures
You are going to background thread
mode when you call getDocuments
which have less priority than Main thread
.So replace below part of code into viewDidLoad
or a method which is called from viewDidLoad
db.collection("News").getDocuments(){(querySnapshot, err) in
if let err = err{
print("Errore: \(err)")
}else{
for document in (querySnapshot?.documents)!{
let cell = document.data()
self.news.append(cell)
print ("document number: \(self.news.count)")
}
DispatchQueue.main.async {
tableview.reloadData()
}
}
}
and when you get your data you go back to main thread and reload your tableview
using this code.
DispatchQueue.main.async {
tableView.reloadData()
}
You can learn more about threading from apple documentation and this tutorial also.
iOS Swift: order of code execution / asynchronous execution
Yes, you can be certain that your code will be run in order 1, 2, 3, 4.
Every step in the code you posted is performed synchronously. It completes the entire for loop, then the sort, then assigns to the filtered property, then tells the table view to reload.
Functions that do asynchronous tasks are documented as such. Usually such functions take a completion handler so that you can invoke code once they finish their task.
Related Topics
What Is the Cause of This Type Error
How to Display My App Documents in the Files App For Iphone
How to Reset the App Between Tests in Swift Xctest Ui
Changing Navigation Title Programmatically
How to Make an Enum Decodable in Swift
Calling Protocol Default Implementation from Regular Method
Iterate Through Files in a Folder and Its Subfolders Using Swift'S Filemanager
Swift - What's the Difference Between Metatype .Type and .Self
Saving Cgimageref to a Png File
Lidar and Realitykit - Capture a Real World Texture For a Scanned Model
Using Dateformatter on a Unix Timestamp
Does Swift Copy on Write For All Structs
Updating the Ui Using Dispatch_Async in Swift
Swift Beta 6 - Confusing Linker Error Message
Swift Generics Not Preserving Type
Number of Words in a Swift String For Word Count Calculation
Why Can't I Pass a Protocol.Type to a Generic T.Type Parameter