How to check if function finished? - ios

I have a function that requests data from an API and puts it in a global struct, so I can read that data from anywhere in the application, all other functions and classes are mainly working in this data. I want to check if that function has finished loading the data before running the other class functions.
The other functions are in different classes.
I cannot use a completion handler (completion: #escaping(_ success: Bool) -> Void) . as this will trigger that function every time it's called, thus it will lead to the data being downloaded over and over again.
Instead, I'm looking for a way to check if that function has completed it's download tasks, then continue with the other functions.
This has to be dynamic, since if let's say, the internet on the device was not working, and then the user connects to the internet, that function will need to run again to try to get the data (could use a refresh button) and notify other classes and function that it finished again so the other functions can start loading the data into the variables
import Foundation
import Alamofire
import SwiftyJSON
struct GlobalUser {
static var json : JSON!
}
func getData() {
// set api url
let LOCATIONAPI_URL = "https://api.darksky.net/forecast/0123456789abcdef9876543210fedcba/42.3601,-71.0589"
// request JSON from url, using Alamofire Library
Alamofire.request(LOCATIONAPI_URL).responseJSON { (response) in
print("data requested")
let result = response.result
// check if respose is valid
if result.isSuccess{
GlobalUser.json = JSON(result.value!)
}
}
}

Sometime ago I came across this.
The idea is to have a completionHandler, that return
func yourFonction(_ parameters: [String: String], completionHandler: (_ result: [String: Any], _ error: Error) -> Void){
//Your Code
completionHandler(result, error)
}
look here (Tutorial), here et even there (Swift Closures Doc) for more information

Related

Dispatch Group not allowing Alamofire request to execute

I'm using DispatchGroup to wait until a callback for one of my functions executes before continuing. Within that function, I'm calling Alamo fire get request. My issue occurs when I introduce the DispatchGroup, the AlamoFire closure never gets executed.
Sample
let group = DispatchGroup()
group.enter()
Networking.getInfo(userID: userID) { info in
group.leave()
}
group.wait()
Networking class:
static func getInfo(userID: Int, completion: #escaping(_ info: String) -> Void) {
// Program reaches here
Alamofire.request("https://someurl.com").responseJSON { response in
// Program does NOT get here
if let json = response.result.value {
completion("Successful request")
} else {
completion("Some Error")
}
}
}
When I don't use the DispatchGroup, it works fine. When I DO use the DispatchGroup, The getInfo function starts, but the closure of the Alamofire request never gets executed.
Not sure I'm right, but I suspect that the Alamofire response is being queued on the same queue (main) that the group has suspended (wait()). Because the queue is suspended, the completion closure never executes.
Manually writing asynchronous code like this can be quite tricky. My suggestion would be to use any one of the asynchronous libraries out there which can help with this. My personal favourite being PromiseKit which also has specific extensions to support Alamofire. Projects like this can take a lot of the headache out of asynchronous code. They may take some time to get your head around their paradigms, but it's worth doing.
I faced the same problem. In that case I used URLSession request to get ride of it. This API enables your app to perform background downloads when your app isn’t running or, in iOS, while your app is suspended.
https://developer.apple.com/documentation/foundation/urlsession
let request = try URLRequest(url: url, method: .get, headers: headers)
Alamofire.request(request) { response in
...
...
}
I changed it like this :-
let request = try URLRequest(url: url, method: .get, headers: headers)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
...
...
}
task.resume()
And then it worked fine.

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

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