How can I trigger async requests out of view controller - swift

I am building an iOS app and I just finished my login/register part ( requesting a sails.js rest Api)
At the moment I have 2 view controllers with duplicate code because i issue the rest calls on register/login button event listener of each class and there is a lot of similar code I can refactor.
What I want to do is to create a singleton called ApiManager that will contain all the calls that I need. (And the futur ones )
The problem is that with async calls I can't create a function func login(username,password) that will return data so I can store them and prepareforsegue.
What is the simple/proper way to achieve that correctly? Which means call ApiManager.myFunction and using the result wherever it's needed ( filling a tableview for data, initiating a segue for login or register with succes ) and to make this function reusable in another view controller even if it is for another usage. I am using swift.
EDIT : Here is how i did it so i hope it will help you
The function executing the rest call :
func login(#username: String, password: String, resultCallback: (finalresult: UserModel!,finalerror:String!) -> Void) {
Alamofire.request(.POST, AppConfiguration.ApiConfiguration.apiDomain+"/login", parameters: ["username": username,"password": password], encoding: .JSON)
.responseJSON { request, response, data, error in
if let anError = error
{
resultCallback(finalresult: nil,finalerror:anError.localizedDescription)
}else if(response!.statusCode == 200){
var user:UserModel = self.unserializeAuth(data!)//just processing the json using SwiftyJSON to get a easy to use object.
resultCallback(finalresult: user,finalerror:nil)
}else{
resultCallback(finalresult: nil,finalerror:"Username/Password incorrect!")
}
}.responseString{ (request, response, stringResponse, error) in
// print response as string for debugging, testing, etc.
println(stringResponse)
}
}
And this is how i call this function from my ViewController :
#IBAction func onLoginTapped(sender: AnyObject) {//When my user tap the login button
let username = loginInput.text;//taking the content of inputs
let password = passwordInput.text;
ApiManager.sharedInstance.login(username:username,password:password){
[unowned self] finalresult,finalerror in
if(finalresult !== nil){//if result is not null login is successful and we can now store the user in the singleton
ApiManager.sharedInstance.current_user=finalresult
self.performSegueWithIdentifier("showAfterLogin", sender: nil)//enter the actual app and leave the login process
}else{
self.displayAlert("Error!", message: finalerror)//it is basically launching a popup to the user telling him why it didnt work
}
}
}

Almost all of my apps end up with a Server class which is the only one that knows how to communicate with the server. It makes the call, parses the result into a Swift struct and returns it. Most of my servers return json so I use SwiftyJSON, but you can do whatever you want.
The point is, that since this is the only class that knows about server communication, if I need to change the library being used to do the communication (AFNetworking 1 vs 2 vs Parse, vs whatever) this is the only class I need to touch.
class Server {
static let instance = Server()
func loginWithUsername(username: String, password: String, resultCallback: (result: Either<User, NSError>) -> Void) {
// if login is successful call
resultCallback(result: .Left(self.user!))
// otherwise call
resultCallback(result: .Right(error))
}
}
An example of use:
let server = Server.instance
SVProgressHUD.showWithStatus("Loggin In...")
server.loginWithUsername(username, password: password) { [unowned self] result in
SVProgressHUD.dismiss()
switch result {
case .Left(let user):
self.presentUserType(user.userType)
case .Right(let error):
self.warnUserWithMessage("An error occured. \(error.localizedDescription)")
}
}
If the username/password are needed for all subsequent calls, then the server object will maintain a copy of them. If the login returns a token, then the server keeps a copy of that.
QED.

I usually have utility functions in a base class shared by my view controllers and use NSNotificationCenter for reacting to the results of the requests. It can also easily be achieved through delegation (protocol & delegate.
It is mostly about perception but I find it is easier to visualize that you can, for example, start an action on one controller and react on another because the call took this long and you were not blocking navigation in your app.

Related

How do I wait for a download to complete before continuing?

I have this block of code. It fetches data from the API and adds it to a locationDetails array, which is part of a singleton.
private func DownloadLocationDetails(placeID: String) {
let request = AF.request(GoogleAPI.shared.getLocationDetailsLink(placeID: placeID))
request.responseJSON { (data) in
guard let detail = try? JSONDecoder().decode(LocationDetailsBase.self, from: data.data!),
let result = detail.result else {
print("Something went wrong fetching nearby locations.")
return
}
DownloadManager.shared.locationDetails.append(result)
}
}
This block of code is the block in question. I'm creating a caching system of sorts that only downloads new information and retains any old information. This is being done to save calls to the API and for performance gains. The line DownloadLocationDetails(placeID: placeID) is a problem for me because if I execute this line of code it will continue to loop over and over again using unnecessary API calls while waiting for the download to complete. How do I effectively manage this?
func GetLocationDetail(placeID: String) -> LocationDetail {
for location in locationDetails {
if location.place_id == placeID { return location }
}
DownloadLocationDetails(placeID: placeID)
return GetLocationDetail(placeID: placeID)
}
I expect this GetLocationDetail(....) to be called whenever a user interacts with an interface object, so how do I also ensure that the view that calls this is properly notified that the download is complete?
I attempted using a closure but I can't get it to return the way I'm wanting it to. I have a property on the singleton that I want to set this value so that it can be called globally. I am also considering using GCD but I'm not sure of the structure for that.
Generally the pattern for something like this is to store the request object you created in DownloadLocationDetails so you can check to see if one is active before making another call. If you only want to support one at a time, then it's as simple as keeping the bare reference to the request object, but you could make a dictionary of request objects keyed off the placeID (and you probably want to think about maximum request count, and queue up additional requests).
Then the trick is to get notified when the given request object completes. There are a couple ways you could do this, such as keeping a list of callbacks to invoke when it completes, but the easiest would probably be just to refactor the code a bit so that you always update your UI when the request completes, so something like:
private func DownloadLocationDetails(placeID: String) {
let request = AF.request(GoogleAPI.shared.getLocationDetailsLink(placeID: placeID))
request.responseJSON { (data) in
guard let detail = try? JSONDecoder().decode(LocationDetailsBase.self, from: data.data!),
let result = detail.result else {
print("Something went wrong fetching nearby locations.")
return
}
DownloadManager.shared.locationDetails.append(result)
// Notify the UI to refresh for placeID
}
}

how to change a variable after data changes in swift

I am using an API to learn how API's work and I wrote a function that takes data from the web and parses it to my model but when I want to print anything from that model it does not work except it is in my function.
import UIKit
class MovieDetailViewController: UIViewController {
var selectedMovie : Int?
var movieDetail : MovieDetailModel? = nil
override func viewDidLoad() {
super.viewDidLoad()
getMovioesDetail(url: "\(URLs().baseURL)/movie/\(selectedMovie!)\(URLs().API_key)")
print(movieDetail?.backdrop_path)
// It does not print the path
}
//MARK: - Functions
func getMovioesDetail (url: String) {
WebServices().get(url) { (type, model : MovieDetailModel?) in
switch type {
case .Succeed :
self.movieDetail = model
print(self.movieDetail?.backdrop_path)
// It prints the path
case .Failed : print("Failed")
case .FailedDecode : print("FailedDecode")
default : break
}
}
}
}
as #Aaron Cyrman stated, your function includes a web service call and the web service runs asynchronously, meaning that it is not known when you get a response.
That's why if you want to print out something or reload your tableview/collectionview, do that in case .Succeed; not right after calling your function.
Also you better do nil check of your model class after filling it.
Just to show an example:
case .Succeed:
self.movieDetail = model
if self.movieDetail != nil{
print(movieDetail!.backdrop_path)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
break
Without knowing more about WebServices is difficult to know.
I'm guessing the WebServices().get(url) runs asynchronous in a different thread, so the closure is being call when the API request returns data and that's why the print works.
When you are executing code in viewDidLoad is running on the main thread. Immediately after the getMovioesDetail call, print(movieDetail?.backdrop_path) is executed and most likely movieDetail is still nil since the WebService hasn't finish.
To be certain change:
print(movieDetail?.backdrop_path)
with this:
print(movieDetail?.backdrop_path ?? "Movie detail not available yet")
If the web service runs very fast you will see the path printed two times, if not you will first the Movie detail not available yet and then the path.

How to test asynchronous method results?

When we get table view datasource, we will ask a network request. It is asynchronous. I have no idea to test the result operation. There is a method to get the points.
func loadPoints() {
API().retrievePoints{ [weak self](pointsSet, error) in
DispatchQueue.main.async(execute: {
// Make sure the call succeeded; return an error if it didn't
guard error == nil else {
self?.showErrorMessage()
Device.debugLog(item:"Error loading Points: \(String(describing: error))")
return
}
self?.pointsSet = pointsSet
self?.tableView.reloadData()
})
}
}
I have known that if we want to test the retrievePoints method, we can test like bellow
//points
func testRetrievePoints() {
let expectation = self.expectation(description: "RetrievePoints")
API().retrievePoints{ (pointsSet, error) -> Void in
XCTAssertNil(pointsSet)
XCTAssertNotNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 15.0, handler: nil)
}
Now I want to test the codes
self?.pointsSet = pointsSet
self?.tableView.reloadData()
self?.showErrorMessage()
For now I just use sleep(15) to wait the method. But it is inaccurate.
Could you please help me? Thanks in advance.
Just as what you have said, it's asynchronous. So it will take time before finish. That is to say that you need to wait before it can success.
Also note that it's just timeout value. All your task must finish within this value. Or it will be treated as failure.
You want to test your data source — not your web service.
there for you should mock the api call.
To achieve this you could use a mocking framework. But I'd rather go another route:
create a protocol that declares the public interface of API, let API conform to that protocol
pass the API as a dependency into the data source. either as an init parameter or via a property. Passing objects is easier that classes, I'd change retrievePoints to be an instance method.
For your test write an APIMock that implements the protocol. Let retrievePoints' callback return prepared points.
Now the points will be returned immediately, no need for timeouts. If you want to defer that your mock can use a DispatchQueue.main.asyncAfter call.

Swift POST Request in same Thread

Hope you can help me. I want a swift function that make a post request and return the json data
so here is my class
import Foundation
class APICall {
//The main Url for the api
var mainApiUrl = "http://url.de/api/"
func login(username: String, password: String) -> String {
let post = "user=\(username)&password=\(password)";
let action = "login.php";
let ret = getJSONForPOSTRequest(action: action, post: post)
return ret;
}
//Function to call a api and return the json output
func getJSONForPOSTRequest(action: String, post: String) -> String {
var ret: String?
let apiUrl = mainApiUrl + action;
let myUrl = URL(string: apiUrl);
var request = URLRequest(url:myUrl!);
request.httpMethod = "POST";
let postString = post;
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil
{
print("error=\(error)")
return
}
print("response=\(response)")
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let login = parseJSON["Login"] as? String
print("login: \(login)")
ret = login
}
} catch {
print(error)
}
}
task.resume()
return ret!;
}
}
But ret is nil. In the debugger is see the inner of the task is called later by another thread?
How can if fix that?
Thank you guys
The data task completion closure is called on another thread and after the execution of the method is completed so you need to re-jig your code a bit. Instead of having a String return value for your getJSONForPOSTRequest, don't return anything and instead have an additional argument that is a closure and call that from within your dataTask closure instead.
func getJSONForPOSTRequest(action: String, post: String, completion: (string: String) -> Void) {
// ...
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
// ... (Convert data to string etc.)
completion(string: myString)
}
task.resume()
}
Remember, doing this means that the completion handler will be called once the network request completes and not right away.
EDIT:
Lets take this from the beginning. When you download something from the network in iOS you typically use NSURLSession. NSURLSession has a number of methods available to it for different means of interacting with the network, but all of these methods use a different thread, typically a background thread, which will do work independently of the rest of your code.
With this in mind, when you call the dataTask method you will notice that you have to add a completion closure as one of the parameters (notice in your example you are using something called a 'trailing closure' which is a closure that is the last argument in the method call that doesn't fall within the parenthesis of the method with the rest of the arguments). Think of a closure as a piece of code that is executed at a different time, it's not executed in line with the rest of the code around it (See the Swift documentation on closures here). In this case the closure will be called once the network request has been completed. Network requests aren't instant so we typically use a background thread to execute them while the user is shown an activity indicator etc and can still use the app. If we waited until the network request completed on the same thread as the rest of our code then it results in the app appearing laggy and even frozen which is terrible for users.
So going back to your example at hand; when you call your getJSONForPOSTRequest method the code within that method will complete and return before the network request has completed which is why we don't need to use a return value. Once the network request has completed your closure code will get called. Because the closure is called later it's also being called from an entirely different place within the code, in this case it's called from within iOS's network code. Because if this if you return a value from within the closure you will be trying to return the value to the network code which isn't what you want, you want to return the value to your own code.
To return the value of the network response to your code you need to define a closure (or a delegate, but I'm not going to go into that here) yourself. If you look at the example code above I've removed the return value from your getJSONForPOSTRequest method and added a new argument called 'completion', and if you look at the type of that argument you can see it's (string: String) -> Void, this defines a closure that passes in a string (the string that you will have downloaded from the network). Now that we have a closure thats within your method we can use this to call back to the caller of the getJSONForPOSTRequest with the data we have downloaded form the network.
Lets take your login method and see how we use getJSONForPOSTRequest within it:
func login(username: String, password: String, completion: (success: Bool) -> Void) {
let post = "user=\(username)&password=\(password)";
let action = "login.php";
let ret = getJSONForPOSTRequest(action: action, post: post) { string in
// This will be called once the network has responded and 'getJSONForPOSTRequest' has processed the data
print(string)
completion(success: true)
}
}
See that again we aren't returning anything directly from the login method as it has to rely on the a-synchronousness of calling off to the network.
It might feel by now that you are starting to get into something called 'callback hell', but this is the standard way to deal with networking. In your UI code you will call login and that will be the end of the chain. For example here is some hypothetical UI code:
func performLogin() {
self.activityIndicator.startAnimating()
self.apiCaller.login(username: "Joe", password: "abc123") { [weak self] success in
print(success)
// This will get called once the login request has completed. The login might have succeeded of failed, but here you can make the decision to show the user some indication of that
self?.activityIndicator.stopAnimating()
self?.loginCompleted()
}
}
Hopefully that clarifies a few things, if you have any other questions just ask.

Returning data after async task

I am uploading an image with using a library. This library is working async.
My function:
func upload() -> String {
let imageData:NSData = UIImageJPEGRepresentation(pureImage!, 100)!
var picture=""
SRWebClient.POST("http://domain.com/upload.php")
.data(imageData, fieldName:"image_field", data: ["username":"test","key":"test"])
.send({(response:AnyObject!, status:Int) -> Void in
if status == 200 {
let responseJSON = response! as! Dictionary<String, AnyObject>
let s_status=responseJSON["status"] as! Int
if s_status == 1 {
picture=responseJSON["picture"] as! String
print(picture)
}
}
},failure:{(error:NSError!) -> Void in
picture=""
})
return picture
}
As you can see, I have to return picture name. But now it is always returning empty string because upload process is async. How can I return the picture name after upload process?
Obviously you cannot return the picture name as function result, not unless you want to wait till the async task is done and waiting would make it a synchronous task again.
There are three very common ways to make async tasks deliver results:
Pass the task a callback (either a callback function or a completion block if you need to capture state or references). Once the task is done, it calls the callback. In your case, the callback could get the image name as argument and the callback code then needs to decide what to do with it.
If the task is encapsulated in an object, allow the object to have a delegate. Once the task is done, a delegate method is called. Either the method gets the image name as argument or can query the image name from the object it is delegate of (usually you'd pass the object itself as an argument to the delegate, that is common practice and good coding style according to Apple).
Send a notification that an image was uploaded. The image name can be the object of the notification; or some object that encapsulates the image name and possibly other properties. Whoever is interested to know when an upload task completed can register for that notification.
Some notes regarding the options above:
I'd use notifications with care. While they are easy to use and very useful if a lot of components spread across a huge project need to be informed about events, they are hard to debug (you cannot follow the code flow easily in a debugger) and they create a very lose coupling (which may or may not be desirable), yet a strong coupling to the notification itself. Also notifications cannot return a value in case that is every required.
A delegate is always a great option, but it forces users to create a class that implements the delegate protocol. This usually only pays off if you need more than just a single callback method or when you plan to call the delegate methods very frequently. Delegates are great for unit testing.
A callback is like a tiny delegate with just a single callback method. If you commonly make "fire and forget" tasks on the go and there is only a single callback required that will be called in case of success and in case of failure; and it will only be called once and there is no need to ever recycle it, then a callback is often preferable to a delegate. It has all the advantages of a delegate but it is more lightweight.
This is the sort of problem that Promises were designed for. You could implement callbacks but it quickly becomes unmanageable if you have more than a few of them to deal with.
Do yourself a big favor and import PromiseKit into you code. Take the half-hour to learn how to use it.
You will end up with something like
func upload() -> Promise<String>
you can use blocks to get a call back
func upload(completionHandler : (pictureName : NSString?)-> Void){
let imageData:NSData = UIImageJPEGRepresentation(pureImage!, 100)!
var picture=""
SRWebClient.POST("http://domain.com/upload.php")
.data(imageData, fieldName:"image_field", data: ["username":"test","key":"test"])
.send({(response:AnyObject!, status:Int) -> Void in
if status == 200 {
let responseJSON = response! as! Dictionary<String, AnyObject>
let s_status=responseJSON["status"] as! Int
if s_status == 1 {
picture=responseJSON["picture"] as! String
print(picture)
completionHandler(pictureName: picture)
}
}
},failure:{(error:NSError!) -> Void in
picture=""
completionHandler(pictureName: nil)
})
}