}
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.
Related
I am new to iOS objective C and currently am enhancing some features to an existing project. I would like to implement a callback method to return the results from an API call in the viewDidLoad method as I need the results to determine the number of icons to display on the screen.
I have looked around for various answers, however, I am not sure what has went wrong and I am unable to return the responses from the APIs to the NSMutableArray in the ViewDidLoad method. I would greatly appreciate any help on how to go about get the response into the customerArray in the viewDidLoad method
This is the Service Delegate header file that defines the method:
I made use of the first method requestCompleted in my ViewController file to retrieve the response returned from the API. Within this method, customerArray which is a globally declared array contains the items that was returned from the API, however when this array was called in the viewDidLoad method, it appears to be null.
I tried the following way to retrieve using the dispatch async method, but the customer Array is still empty
You are printing customerArray, before dispatch_async executed. Try putting breakpoint in requestCompleted method or put print statement in requestCompleted method. and check the value.
You can't execute the callback method in viewDidLoad function. And then this attempt is not good pattern in iOS.
I understand what you want. You want to display various icons when view controller is appeared.
For this, you may need to use UITableView or CollectionView.
In this case, i have been doing following.
First, Run API call by using Alamofire
Second. Show loading icon (by using HUD)
Third, In API callback, fetch the list information and reload tableview.
*Following is swift code but it is very similar with Objective C.
override func viewDidLoad() {
super.viewDidLoad()
HUD.show(.rotatingImage(UIImage(named: "rotateLogo")), onView: nil)
APIManager.request(Constant.sdkCredential,
parameters: [:],
fullResponse:true)
{ (response, success, error) in
if success == true {
guard (response as? Dictionary<String,Any>) != nil else {
self.showAlert(withTitle: "Error", message: "Invalid Credential")
return
}
if let dict = response as? Dictionary<String,Any> {
print(dict)
if let dictData = dict["data"] as? Dictionary<String,Any>{
self.tableView.reloadData();
}
}
} else {
HUD.hide()
self.showAlertWithError(error)
}
}
}
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 have the following async recursive code:
func syncData() {
dal.getList(...) { [unowned self] list, error in
if let objects = list {
if oneTime {
oneTime = false
syncOtherStuffNow()
}
syncData() // recurse until all data synced
} else if let error = error {... }
func syncOtherStuffNow() { } // with its own recursion
My understanding is that the recursion will build the call stack until all the function calls complete, at which point they will all unwind and free up the heap.
I also want to trigger another function (syncOtherStuffNow) from within the closure. But don't want to bind it to the closure with a strong reference waiting for it's return (even though it's async too).
How can I essentially trigger the syncOtherStuffNow() selector to run, and not affect the current closure with hanging on to its return call?
I thought of using Notifications, but that seems overkill given the two functions are in the same class.
Since dal.getList() takes a callback I guess it is asynchronous and so the the first syncData starts the async call and then returns immediately which lets syncData() return.
If syncOtherStuffNow() is async it will return immediately and so dataSync() will not wait on it finishing its job and so continue with its execution to the end.
You can test whether sth builds a callstack by putting a breakpoint on every recursion and look on the callstack how many calls of the same function are ontop.
What I do is recurse with asyncAfter, which unwinds the call stack.
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.")
}
}
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.