how to execute some code after execution of another class method code? - ios

I have a common web services class. in that class I have written method getcitydetail(). I want to populate that city detail after getting the result.
I have written following code in viewDidload:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let objWebService = NTMWebServices()
objWebService.getCityDetail()
}
After execution of getcitydetail, I want to do some operation here.
I think we can do it using closure in swift. but I didn't get an idea how to use it.

To use a closure in this situation try something like
func getCityDetail (completion:()->()){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
//background thread, do ws related stuff, the ws should block until finished here
dispatch_async(dispatch_get_main_queue()) {
completion() //main thread, handle completion
}
}
}
then you can use it like
objWebService.getCityDetail {
//do something when the ws is complete
}

You can try any of these:
If getCityDetail() is asynchronous request then, Use Delegate to
respond with the result to the registered class.
Ex:
NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{ (response: NSURLResponse!, data: NSData!, error:
NSError!) -> Void in
// Handle incoming data like you would in synchronous request
var reply = NSString(data: data, encoding: NSUTF8StringEncoding)
// Conform to the protocol with the results here
})
iOS Swift Pass Closure as Property? - As here u can use the closure as property and can communicate between the classes

Related

Wait for Firebase to load before returning from a function

I have a simple function loading data from Firebase.
func loadFromFireBase() -> Array<Song>? {
var songArray:Array<Song> = []
ref.observe(.value, with: { snapshot in
//Load songArray
})
if songArray.isEmpty {
return nil
}
return songArray
}
Currently this function returns nil always, even though there is data to load. It does this because it doesn't ever get to the perform the completion block where it loads the array before the function returns. I'm looking for a way to make the function only return once the completion block has been called but I can't put return in the completion block.
(Variations on this question come up constantly on SO. I can never find a good, comprehensive answer, so below is an attempt to provide such an answer)
You can't do that. Firebase is asynchronous. Its functions take a completion handler and return immediately. You need to rewrite your loadFromFirebase function to take a completion handler.
I have a sample project on Github called Async_demo (link) that is a working (Swift 3) app illustrating this technique.
The key part of that is the function downloadFileAtURL, which takes a completion handler and does an async download:
typealias DataClosure = (Data?, Error?) -> Void
/**
This class is a trivial example of a class that handles async processing. It offers a single function, `downloadFileAtURL()`
*/
class DownloadManager: NSObject {
static var downloadManager = DownloadManager()
private lazy var session: URLSession = {
return URLSession.shared
}()
/**
This function demonstrates handling an async task.
- Parameter url The url to download
- Parameter completion: A completion handler to execute once the download is finished
*/
func downloadFileAtURL(_ url: URL, completion: #escaping DataClosure) {
//We create a URLRequest that does not allow caching so you can see the download take place
let request = URLRequest(url: url,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 30.0)
let dataTask = URLSession.shared.dataTask(with: request) {
//------------------------------------------
//This is the completion handler, which runs LATER,
//after downloadFileAtURL has returned.
data, response, error in
//Perform the completion handler on the main thread
DispatchQueue.main.async() {
//Call the copmletion handler that was passed to us
completion(data, error)
}
//------------------------------------------
}
dataTask.resume()
//When we get here the data task will NOT have completed yet!
}
}
The code above uses Apple's URLSession class to download data from a remote server asynchronously. When you create a dataTask, you pass in a completion handler that gets invoked when the data task has completed (or failed.) Beware, though: Your completion handler gets invoked on a background thread.
That's good, because if you need to do time-consuming processing like parsing large JSON or XML structures, you can do it in the completion handler without causing your app's UI to freeze. However, as a result you can't do UI calls in the data task completion handler without sending those UI calls to the main thread. The code above invokes the entire completion handler on the main thread, using a call to DispatchQueue.main.async() {}.
Back to the OP's code:
I find that a function with a closure as a parameter is hard to read, so I usually define the closure as a typealias.
Reworking the code from #Raghav7890's answer to use a typealias:
typealias SongArrayClosure = (Array<Song>?) -> Void
func loadFromFireBase(completionHandler: #escaping SongArrayClosure) {
ref.observe(.value, with: { snapshot in
var songArray:Array<Song> = []
//Put code here to load songArray from the FireBase returned data
if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
})
}
I haven't used Firebase in a long time (and then only modified somebody else's Firebase project), so I don't remember if it invokes it's completion handlers on the main thread or on a background thread. If it invokes completion handlers on a background thread then you may want to wrap the call to your completion handler in a GCD call to the main thread.
Edit:
Based on the answers to this SO question, it sounds like Firebase does it's networking calls on a background thread but invokes it's listeners on the main thread.
In that case you can ignore the code below for Firebase, but for those reading this thread for help with other sorts of async code, here's how you would rewrite the code to invoke the completion handler on the main thread:
typealias SongArrayClosure = (Array<Song>?) -> Void
func loadFromFireBase(completionHandler:#escaping SongArrayClosure) {
ref.observe(.value, with: { snapshot in
var songArray:Array<Song> = []
//Put code here to load songArray from the FireBase returned data
//Pass songArray to the completion handler on the main thread.
DispatchQueue.main.async() {
if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
}
})
}
Making Duncan answer more precise. You can make the function like this
func loadFromFireBase(completionHandler:#escaping (_ songArray: [Song]?)->()) {
ref.observe(.value) { snapshot in
var songArray: [Song] = []
//Load songArray
if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
}
}
You can return the songArray in a completion handler block.

Waiting for Asynchronous function call to complete

I know this question has been asked before, but all solutions do not work for me.
I have a function with sends parameters to an API, and returns the data as a list, I have a UITableView set up to use that list, but it runs before the list is assigned to a variable.
code:
var functionResult = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//gradesscrollView.contentSize.height = 3000
fetchItems{ (str) in
var returnedItems = [String]()
let result = self.convertoArray(itemstoPass: str!)
for i in result{
functionResult.append(i)
}
}
self.tableofItems.delegate = self
self.tableofItems.dataSource = self //Data source is set up to use functionResult, however functionResult is empty before fetchItem runs.
}
I would appreciate it if it is not immediately voted as a duplicate, here is what I have tried.
Dispatch groups
Semaphore timing
running variables
including self.tableofItems.delegate = self & self.tableofItems.dataSource = self in the fetchItems{ (str) in part.
EDIT:
Fetch items has been requested,
func fetchItems(completionHandler: #escaping (String?) -> ()) -> () {
let headers = [
"Content-Type": "application/x-www-form-urlencoded"
]
//Switch to keychain
let username = UserDefaults.standard.object(forKey: "username") as! String?
let password = UserDefaults.standard.object(forKey: "password") as! String?
let usernametoSend = username!
let passwordtoSend = password!
print(usernametoSend)
print(passwordtoSend)
let parameters: Parameters = [
"username": usernametoSend,
"password": passwordtoSend
]
Alamofire.request("https://www.mywebsite.com/API/getItems", method: .post, parameters: parameters, headers: headers)
.responseString { response in
completionHandler(String(response.result.value!))
You can't - and shouldn't - wait until an async call to complete. You need to study async programming until you understand it.
An async function accepts a job to do, and returns immediately, before the job is done.
in Swift you usually write an async function to take a completion handler, which is a block of code that you want to be run one the async task is complete.
I have a project called Async_demo (link) on Github that illustrates this. It implements a DownloadManager class that handles async downloads.
The key part is the function downloadFileAtURL(), which should more properly be named downloadDataAtURL, since it returns in-memory data rather than a file.
I created that function to take a completion handler as a parameter:
/**
This function demonstrates handling an async task.
- Parameter url The url to download
- Parameter completion: A completion handler to execute once the download is finished
*/
func downloadFileAtURL(_ url: URL, completion: #escaping DataClosure) {
//We create a URLRequest that does not allow caching so you can see the download take place
let request = URLRequest(url: url,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 30.0)
let dataTask = URLSession.shared.dataTask(with: request) {
//------------------------------------------
//This is the completion handler, which runs LATER,
//after downloadFileAtURL has returned.
data, response, error in
//Perform the completion handler on the main thread
DispatchQueue.main.async() {
//Call the copmletion handler that was passed to us
completion(data, error)
}
//------------------------------------------
}
dataTask.resume()
//When we get here the data task will NOT have completed yet!
}
}
It uses an NSURLSession to download a block of data from the specified URL. The data request call I use takes a completion handler that gets executed on a background thread. In the completion handler that I pass to the data task, I invoke the completion handler that's passed in to the downloadFileAtURL() function, but on the main thread.
The code you posted is kind of confusing. It isn't clear which part is the async function, what the flow is, or what data is needed to display your table view.
If you rewrite your function that does async work to take a completion block then you could call tableView.reloadData() in your completion block. (Make sure that call is performed on the main thread.)
EDIT:
As others have said, you need to edit your question to show the code for your fetchItems() function.
I'm guessing that that function is the one that does the Async work, and that the block after it is a completion handler that gets performed asynchronously. If so, you should probably refactor your code like this:
var functionResult = [String]()
override func viewDidLoad() {
super.viewDidLoad()
//I moved these lines above the call to fetchItems to make it clear
//that they run before fetchItems' completion closure is executed
self.tableofItems.delegate = self
self.tableofItems.dataSource = self //Data source is set up to use functionResult, however functionResult is empty before fetchItem runs.
print("Step 1")
fetchItems{ (str) in
var returnedItems = [String]()
let result = self.convertoArray(itemstoPass: str!)
for i in result{
functionResult.append(i)
}
print("Step 3")
DispatchQueue.main.async() {
tableview.reloadData() //Do this from the main thread, inside the closure of `fetchItems()`
}
}
print("Step 2")
}
You should load the data of the list in the method of loadView, so that it will be loaded early before UITableView reads.
Sometimes, viewDidLoad performs a little bit slowly. Generally, people will initialize the data of list or sth in the method of loadView to make sure the data is completed before view is created.

Call the same code in every functions of a class

I have a Network Manager class (singleton).
For every API call that the application has to make, a different function from this Network Manager is called.
I would like that every functions of this NM starts by displaying an Activity Indicator until the completion handler of each function is reached: showing the user that a request is being performed.
What would be the best way to do that?
I am using Alamofire pod if that could help out.
Here is an example of one of my API call:
func loadSetting(for user: String, completionHandler: (UserSetting?) -> ()) {
myActivityIndicator.start //a line I'd like to perform in every function of this class
let parameters = ["UUID": user]
_ = Alamofire.request(.POST, "http://www.google.com/users", parameters: parameters)
.responseObject { (response: Response<UserSetting, NSError>) in
if let userSetting = response.result.value {
print("User Setting found remotely")
completionHandler(userSetting)
} else {
print("Couldn't fetch remote User Setting")
completionHandler(nil)
}
myActivityIndicator.stop //a line I'd like to perform in every function of this class
}
}
Thanks.
Why don't you try passing your activity indicator as an argument ?
So from the View controller where you're calling the loadSetting function, you pass in an activity indicator which you hold a reference to, and then inside your loadSetting function, you start/stop it there

Service API call blocks UI in UITableViewController

I have a UITableViewController and have to load a not so big amount of data on start. In my viewDidLoad, I use a different queue to send the request:
override func viewDidLoad() {
super.viewDidLoad()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
var data = self.getStoresData()
dispatch_async(dispatch_get_main_queue(), {
self.parseStoresData(data)
self.resultsController = PartnerStore.getAllStores()
});
});
}
And these are the methods:
func getStoresData() -> [NSDictionary]
{
var responseData = [NSDictionary]()
self.refreshControl.beginRefreshing()
AppDelegate.appDelegate().httpRequestOperationManager?.GET(
"partner_stores/",
parameters: nil,
success: { (operation: AFHTTPRequestOperation!, responseObject: AnyObject!) in
self.tableView.reloadData();
println("RESPONSE OBJECT IN GET PARTNER STORES: \(responseObject)") },
failure: { (operation: AFHTTPRequestOperation!, error: NSError!) in
println("FAIL IN GET PARTNER STORES: \(error)") })
self.refreshControl.endRefreshing()
return responseData
}
func parseStoresData(storesData: [NSDictionary])
{
for storeDict in storesData {
// just inserts a new object to CoreData
PartnerStore.addNewStore(storeDict)
}
}
The problem is (I think) that the API call is async, so the two functions in dispatch_async are executed before the data is downloaded from the server. But if I put everything in the success block of the GET call, it takes a lot of time and the whole UI is blocked. What is the best way to to the server call, without blocking the UI thread ?
You are right about the two calls in the dispatch_async in viewDidLoad being called before your GET request completes; that's a problem. You also had the right idea when you said you tried putting everything in the success block; that's where it should go flow wise. There are a few other things that need to be moved around as well though.
One good way to go about handling UI updates is to have separate functions for your UI updates and your data fetch. Doing it that way means we'll need to pass a callback to your getStoresData function and then call it appropriately in your GET function's success and error blocks. That will let us know when the data fetch is complete so that we can finish the UI updates. We'll also want to move the dispatch to the background queue out of viewDidLoad and in to getStoresData.
So, lets pull any UI changes out of getStoresData and move that dispatch:
func getStoresData(callback: (error: NSError?) -> Void) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
// ... do any setup ...
AppDelegate.appDelegate().httpRequestOperationManager?.GET(
// ... other GET parameters ...
success: { (operation: AFHTTPRequestOperation!, responseObject: AnyObject!) in
var responseData = [NSDictionary]()
// do what you need to convert responseObject to responseData
// then...
// NOTE: we'll dispatch the the main thread here because parseStoresData deals with CoreData.
// This dispatch could be done in parseStoresData itself but
// a callback function would need to be added to it as well
// in that case.
dispatch_async(dispatch_get_main_queue(), {
self.parseStoresData(responseData)
// The response has been dealt with, so call the callback
callback(error: nil)
});
},
failure: { (operation: AFHTTPRequestOperation!, error: NSError!) in
// There was an error, so call the callback with the error object
callback(error: error)
}
)
})
}
Now let's make that new function to handle updating the UI so that the data updates are decoupled from the UI updates. In this function we'll first start the refresh control and call getStoresData. Then, when getStoresData finishes, update the table view and stop the refresh control.
func reloadData() {
// start the refresh control on the main thread so the user knows we're updating
dispatch_async(dispatch_get_main_queue(), {
self.refreshControl.beginRefreshing()
})
// do the actual data fetch...
// (remember this will dispatch to a background thread on its own now)
getStoresData({
(error: NSError?) -> Void in
// since this callback could be called from any thread,
// make sure to dispatch back to the main UI thread to finish the UI updates
dispatch_async(dispatch_get_main_queue(), {
if let actualError = error {
// update the UI appropriately for the error
} else {
// update the data in the table view and reload it
self.resultsController = PartnerStore.getAllStores()
self.tableView.reloadData();
}
// we're done; stop the refresh control
self.refreshControl.endRefreshing()
})
})
}
That makes your viewDidLoad function very simple now:
override func viewDidLoad() {
super.viewDidLoad()
reloadData()
}
This also makes it easier to implement things like pull to refresh since you can simply call reloadData when the user triggers the refresh instead of duplicating the UI update code all over the place.

iOS takes long time to update view after HTTP request?

I am very new to learning iOS and Swift, so there may be something very basic here that I don't understand. I am using the agent library to send HTTP requests in Swift. Right now I'm just getting the hang of creating the request and parsing the JSON response.
I have a very simple UI with a button and a UILabel that I want to update with the results of some JSON.
Here is my code:
func updateWithJson() -> Void {
let req = Agent.get("http://xkcd.com/info.0.json")
req.end({ (response: NSHTTPURLResponse!, data: Agent.Data!, error: NSError!) -> Void in
let json = data! as Dictionary<NSString, NSString>
// this takes almost 30 seconds to propagate to the UI...
self.updateMe.text = json["safe_title"]!
})
}
The part I don't understand is the self.updateMe.text = json["safe_title"]! statement which is just updating some text on a UILabel called updateMe. It takes almost a full 30 seconds for this to reflect in the UI.
I know that the HTTP request itself is very fast - if I curl that URL it comes back instantaneously. It also seems like the Agent object is making the connection and returning the response pretty fast.
Am I missing something very basic? Should I be updating the UI in a different way? All pointers are appreciated!
UPDATE
Through another SO post it's my understanding that the block inside of req.end is a background thread and I need to do UI updates on the main thread. I updated my function to look like this and I get the desired result. However, now I'm wondering if there's something incorrect about doing it this way?
func updateWithJson() -> Void {
let req = Agent.get("http://xkcd.com/info.0.json")
req.end({ (response: NSHTTPURLResponse!, data: Agent.Data!, error: NSError!) -> Void in
let json = data! as Dictionary<NSString, NSString>
var text = json["safe_title"]!
dispatch_sync(dispatch_get_main_queue()) {
self.updateMe.text = text
}
})
}
What's happening is that your HTTP request is running on a background thread. When it calls the callback you provide, you're still on that background thread. In iOS all UI work must be done on the main thread. The easiest way to do this is by using GCD's dispatch_async like so:
dispatch_async(dispatch_get_main_queue()) {
[weak self] in
self?.updateMe.text = json["safe_title"]!
return
}
So your entire function would look like:
func updateWithJson() -> Void {
let req = Agent.get("http://xkcd.com/info.0.json")
req.end({ (response: NSHTTPURLResponse!, data: Agent.Data!, error: NSError!) -> Void in
let json = data! as Dictionary<NSString, NSString>
dispatch_async(dispatch_get_main_queue()) {
[weak self] in
self?.updateMe.text = json["safe_title"]!
return
}
})
}
The bit about [weak self] in is so you don't create a strong reference cycle. The return at the end is there because you have a closure with only one expression in it. In that case, Swift will try to implicitly return a value based on that statement and in this case that's not what you want.

Resources