I read some questions about that. But I still have issues with asynchronous functions.
For example: I have a viewController1 where a button perform a segue to a viewController2. In the viewController2 class, I initialize some values in another class file named exampleClass. These values are retrieved from Firebase database or location values. These values need a little moment to be retrieved. I return thes values from the exampleClass into my viewController2. I print these values in the viewController2 viewDidLoad().
My issue: The device doesn't wait that the values are retrieved and execute following functions. Result: When I touch the button, printed values are nil values. It can also make the app crash if I don't secure the code.
What I've found so far: I learned that I only have to call a func at the end of a Firebase snapshot (for example) like this:
userRef.observeSingleEvent(of: .value, with: { (snapshot) -> Void in
self.name = snapshot.value as! String!
print(self.name)
self.forceEnd()
}) { (error) in
print(error.localizedDescription)
}
I named this function forceEnd to be clear. This is not working for me. I also tried to create handlers but no positive results.
My question: How can I force the device to wait for the values to be retrieved before performing the following question?
How can I force the device to wait for the values to be retrieved before performing the following question?
You don't want to force the device to wait, only need to perform some operations once these values are retrieved from Firebase database.
Performing an operation asynchronously can be done in multiple ways like blocks, protocols, notifications, etc.
Generally, blocks are the more elegant approach.
Some sample code can be like:
func myFirebaseNetworkDataRequest(finished: () -> Void) { // the function thats going to take a little moment
...
print("Doing something!") // firebase network request
finished()
}
// usage of above function can be as-
override func viewDidLoad() {
myFirebaseNetworkDataRequest {
// perform further operations here after data is fetched
print("Finally! It took a lot of moments to end but now I can do something else.")
}
}
Related
When I try to run the following code on a Simulator (also on a Device, the result is the same) I have a problem: the function executes first the part 2 of the following code (the comments are there just to have a reference, the code itself is entirely in the viewDidLoad. Just to be clear) and then it executes the part 1 (the firebase part). I need the ref.child.... function to be performed before the other function because the "createAlbums" func needs the albums taken from my database (So a completion block would be perfect, but that function doesn't allow me to put any).
I thought about a pair of solutions:
A way to add a completion block (remember that the ref.child... func is like a cycle so it will print the entire "albums" array for every single value that it finds on the database.
Create a Int var and make the 2 part of the code run only if the value of the var is the same as the number of values of "Albums" in the database, so it will fill up the local "Albums" array.
Use two different functions: the the viewDidLoad and the viewDidAppear. As I know, the second one will be performed after the first one.
Which one is the best solution? Is there any better solution?
I also ask you to make suggests on how I can create my own solutions, because I can logically arrive to the point, but I'm not sure to be able to develop them in Xcode.
Just to summarize the entire thread: how can I make the 2 part of the following code run after the 1 part? And why does it run before, even if the order of writing is correct and the compiler runs from the line 1 to the last line?
Thank you for your help and sorry for my bad English, I hope that you can understand everything, unfortunately trying to explain a "complex" problem in English is not always easy.
But I think that the moderators will be great as always to make some corrections!
// 1
let ref = Database.database().reference()
var albums: [String] = [String]()
override func viewDidLoad() {
super.viewDidLoad()
ref.child("Albums").observe(.childAdded) { (snapshot) in
albums.append(snapshot.key)
print("\(albums)")
print("Test print")
}
// 2
createAlbums()
setupConstraints()
}
You'll need to move createAlbums() and setupConstraints() into where you are doing Test Print.
Although it looks like your code is all happening in one place, it's really not.
These lines, between the curly braces...
albums.append(snapshot.key)
print("\(albums)")
print("Test print")
... are happening asynchronously, when firebase calls you back because it has data.
So you need something more like this:
// 1
let ref = Database.database().reference()
var albums: [String] = [String]()
override func viewDidLoad() {
super.viewDidLoad()
var createdAlbums = false
ref.child("Albums").observe(.childAdded) { (snapshot) in
albums.append(snapshot.key)
print("\(albums)")
print("Test print")
if !createdAlbums {
// Only run this part once, when we
// get our first callback from firebase
self.createAlbums()
self.setupConstraints()
createdAlbums = true
}
}
// 2
}
OTHER WORKING SOLUTION
In that case, observe the .value event on the same reference/query. In your closure, loop over the children as shown here stackoverflow.com/a/27342233, and at the end of the loop call createAlbums().
From a Frank van Puffelen comment
}
Hey guys, I have a problem setting a value for the label. The label should display the number of elements in the array inside my JSON (link - followers_url variable). I call alamo and make a request with that url. When I print the value inside parseData() method I get the right result. When I print it inside configureView() and viewDidLoad() I always get 0.
Setting the label text also works only inside parseData() method. Any ideas how I can get it to work?
Alamofire.request(url).validate().responseJSON { response in
self.parseData(data: response.data!)
}
This above request runs on another background thread.
So when you call the function callAlamo the response is received in the completion block ( { response in ). So when you call print() after callAlamo. the response has not yet been received and print is called so value is not updated. So please perform the operation on the response only through completion block.
If you want to set a label write you set label code after self.parseData in completion block ({response in). Make sure you set it in main queue as the UI operation needs to be performed on main queue only
Following question will help to set label on main thread.
In Swift how to call method with parameters on GCD main thread?
You need to understand multithreading concept to get a better understanding of this. Follow this https://medium.com/#gabriel_lewis/threading-in-swift-simply-explained-5c8dd680b9b2
You should learn something about iOS Parsing techniques. Then learn how to create Model using class or struct. Then you will get Idea.
You should look into Object Mapper as well.
You're dealing with an asynchronous operation. Asynchronous operations are "actions" that are dispatched and require you to wait before they complete. Think about loading a website in Safari. Once you type, let's say, stackoverflow.com in your browser, a loading spinner will notify that something is loading. While the page is loading, you obviously cannot see what's on the webpage. There's only an empty, white page.
The same is happening with your request. When you call the callAlamo function you're telling the app to start loading something. This is requiring you to wait until the task is done. If you count the elements in the followersAndFollowingArray right after the server call, then you'll get it empty, because the request is still waiting to be completed. It's like pretending to view the stackoverflow.com website immediately after having typed the URL. You can't.
That's where closures come in handy. You can use closures to execute something when another action has been completed. In this case, I would fire the web request, display a loading spinner to notify the user that something is loading, and finally populate the followersLabel along with stopping the animation. You can do something like that
func callAlamo(url: String, completion: #escaping ([User]) -> Void) {
if Connectivity.isConnectedToInternet {
Alamofire.request(url).validate().responseJSON { response in
let userData = self.parseData(data: response.data!)
completion(userData)
}
}
}
Additionally you need to let the parseData method to return the parsed array of Users, so the callAlamo function could use it.
func parseData(data : Data) -> [User] {
do {
return try JSONDecoder().decode([User].self, from: data)
} catch let jsonErr {
print("Error serializing", jsonErr)
return [User]()
}
}
Finally, you can execute the callAlamo function on inside the configureView method, performing an action when the server request has been completed. In our case, we want to populate the label.
private func configureView(){
followersLabel.text = String(followers)
// Starting the loading animation
startAnimation()
callAlamo(url: "Hello") { userData in
// Assigning the callAlamo result to your followers array
// once the server request has been completed
self.followersAndFollowingArray = userData
// This will return the number you'd expect
print(self.followersAndFollowingArray.count)
// Stopping the loading animation
stopAnimation()
}
}
Right now you probably won't have the startAnimation and stopAnimation methods, but you can feel free to implement them, I just wanted to give you an idea of a classic implementation.
I'm struggling a little bit trying to create an application for my own education purposes using Swift.
Right now I have the following (desired) order of execution:
TabView
FirstViewController - TableView
Check into CoreData
If data exists update an array using a closure
If data doesn't exists then download it using Alamofire from API and store it into Core Data
SecondViewController - CollectionView
Checks if data of images exists in Core Data, if it does, loads it from there, otherwise download it.
The problem that I'm struggling the most is to know if the code after a closure is executed after (synchronously) the closure ends or it might be executed before or while the closure is executed.
For example:
FirstViewController
var response: [DDGCharacter]
//coreData is an instance of such class
coreData.load(onFinish: { response in //Custom method in another class
print("Finished loading")
self.response = response
})
print("Executed after loading data from Core Data")
//If no data is saved, download from API
if response.count == 0 {
//Download from API
}
I have done the above test with the same result in 10 runs getting:
Finished loading
Executed after loading data from Core Data
In all 10, but it might be because of load is not taking too much time to complete and thus, appear to be synchronous while it's not.
So my question is, is it going to be executed in that order always independent of amount of data? Or it might change? I've done some debugging as well and both of them are executed on the main thread as well. I just want to be sure that my suppositions are correct.
As requested in the comments, here's the implementation done in the load() method:
func load(onFinish: ([DDGCharacter]) -> ()) {
var characters: [DDGCharacter] = []
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject> (entityName: "DDGCharacter")
do {
characters = try managedContext.fetch(fetchRequest) as! [DDGCharacter]
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
onFinish(characters)
}
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.
I am using Firebase realtime database. Data structure is an array of posts, which user can also comment and like.
I retrieve data like this and put them into a local posts array:
ref.observe(.childAdded, with: { (snapshot) -> Void in
self.posts.append(snapshot)
self.tableView.reloadData()
})
They are displayed correctly and no problems so far. Now let's say user likes a post. I add his id to likers array of post in local posts array. However firebase database don't know this yet.
My question is what is the correct way to keep local data and firebase data synchronized?
The trick with Firebase is usually to only update the database when the user performs an action, such as liking a post. From that database update you then get a new event, for example a .childChanged for updating the likes. You then update your UI based on the event from the database.
This is sometimes known as a reactive model, or more formally as Command Query Responsibility Segregation: you separate the flow of the commands (from user to database) from the flow of the queries (from database to views).
You should use DatabaseHandler. You should listen your database and then remove handler when you leave your viewcontroller.
fileprivate lazy var ref = Database.database().reference().child("...")
private var yourHandler: DatabaseHandle?
override func viewDidLoad() {
super.viewDidLoad()
yourHandler = ref.observe(.childAdded, with: { (snapshot) -> in
self.posts.append(snapshot)
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
}
deinit {
if let handler = yourHandler {
ref.removeObserver(withHandle: handler)
}
}
Now, when you add new item to database, your handler get this item and display it in your viewcontroller.
Note: Always call the reloadData () method on the main thread
My question is very similar to several others here but I just can't get it to work. I'm making an API call via a helper class that I wrote.
First I tried a standard function with a return value and the result was as expected. The background task completed after I tired to assign the result.
Now I'm using a closure and I can get the value back into my view controller but its still stuck in the closure, I have the same problem. I know I need to use GCD to get the assignment to happen in the main queue.
this is what I have in my view controller
var artists = [String]()
let api = APIController()
api.getArtistList("foo fighters") { (thelist) -> Void in
if let names = thelist {
dispatch_async(dispatch_get_main_queue()) {
artists = names
print("in the closure: \(artists)")
}
}
}
print ("method 1 results: \(artists)")
as the results are:
method 1 results: []
in the closure: [Foo Fighters & Brian May, UK Foo Fighters, John Fogerty with Foo Fighters, Foo Fighters, Foo Fighters feat. Norah Jones, Foo Fighters feat. Brian May, Foo Fighters vs. Beastie Boys]
I know why this is happening, I just don't know how to fix it :( The API calls need to be async, so what is the best practice for capturing these results? Based on what the user selects in the table view I'll be making subsequent api calls so its not like I can handle everything inside the closure
I completely agree with the #Craig proposal of the use of the GCD, but as your question involves the request of the API call every time you select a row, you can do the following:
Let's suppose you use the tableView:didSelectRowAtIndexPath: method to handle the selection, then you can do the following inside it:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// it is just a form to get the item
let selectedItem = items.objectAtIndex(indexPath.row) as String
api.getArtistList(selectedItem) { (thelist) -> Void in
if let names = thelist {
dispatch_async(dispatch_get_main_queue()) {
artists = names
}
}
}
}
And then you can observe the property and handle do you want inside it :
var artists: [String] = [] {
didSet {
self.tableView.reloadData() // or anything you need to handle.
}
}
It just another way to see it. I hope this help you.
The easy solution is to do whatever you're doing at your print(), inside the closure.
Since you're already dispatch_asyncing to the main queue (the main/GUI thread), you can complete any processing there. Push a new view controller, present some modal data, update your current view controller, etc.
Just make sure that you don't have multiple threads modifying/accessing your local/cached data that is being displayed. Especially if it's being used by UITableViewDelegate / UITableViewDataSource implementations, which will throw fits if you start getting wishy-washy or inconsistent with your return values.
As long as you can retrieve the data in the background, and the only processing that needs to occur on the main thread is an instance variable reassignment, or some kind of array appending, just do that on the main thread, using the data you retrieved on the back end. It's not heavy. If it is heavy, then you're going to need more sophisticated synchronization methods to protect your data.
Normally the pattern looks like:
dispatch_async(getBackgroundQueue(), {
var theData = getTheDataFromNetwork();
dispatch_async(dispatch_get_main_queue() {
self.data = theData // Update the instance variable of your ViewController
self.tableView.reloadData() // Or some other 'reload' method
});
})
So where you'd normally refresh a table view or notify your ViewController that the operation has completed (or that local data has been updated), you should continue your main-thread processing.