Swift POST Request in same Thread - swift

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.

Related

I have been trying to write the Unit test cases in swift for making an API call but not able to figure out how to write

I have been trying to write Unit test cases in swift for making an API call but being new in this am not able to figure out how do I write the Unit test case for the same. here's my code for which I want to write the unit test case
class QuotesModel: ObservableObject {
#Published var quotes = [Quote]()
#MainActor
func fetchData() async {
guard let url = URL(string: "https://breakingbadapi.com/api/quotes") else {
print("Invalid URL")
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
quotes = try JSONDecoder().decode([Quote].self, from: data)
} catch {
print(error)
}
// print(quotes)
}
}
I have been trying to write the unit testcase for this but am not able to figure out how do I do it. Can someone help me with this?
URLSession.shared is a singleton. There's nothing wrong with using singletons. But if you use them directly, your dependencies become hard-wired and you give up control. And URLSession is an "awkward dependency" that makes testing harder. Let's replace it.
So change your code so that it uses URLSession.shared when in production, but something else during testing — something your tests can control. Let's introduce a protocol, as I describe in https://qualitycoding.org/swift-mocking/
protocol URLSessionProtocol {
// We will add more here
}
Use an extension to make URLSession conform to this new protocol:
extension URLSession: URLSessionProtocol {}
Change your production code to use an instance of this protocol instead of directly calling URLSession.shared. But provide URLSession.shared as the default value. For example, we can add an argument to your method:
func fetchData(urlSession: URLSessionProtocol = URLSession.shared) async { … }
To make it possible to use urlSession, the protocol needs the URLSession method you use:
protocol URLSessionProtocol {
func data(for request: URLRequest, delegate: URLSessionTaskDelegate?) async throws -> (Data, URLResponse)
}
Unfortunately, we can't directly add the = nil default value for the delegate argument directly in the protocol. The easiest way to resolve this is to pass it explicitly from your calling code, which will now look like
let (data, _) = try await urlSession.data(from: url, delegate: nil)
(We could also add an extension to the protocol.)
Now test code can provide a different implementation which records how it was called. (How many times was it called? Did you use the correct URL?) And it returns canned data controlled by the test.
…All this to say: Don't write code and expect to be able to write microtests for it, as-is. This is hard. Instead, shape your code to make it easy to write microtests.

Swift - Protocol can only be used as a generic constraint because it has Self or associated type requirements

I'm working on an app which needs to query multiple APIs. I've come up with classes for each API provider (and in more extreme cases, a class for each specific API Endpoint). This is because each API query is expected to return a very strict type of response, so if an API can, for instance, return both user profiles and profile pictures, I only want a response to be specific to either of those.
I've implemented it roughly in the following manner:
protocol MicroserviceProvider {
associatedtype Response
}
protocol ProfilePictureMicroserviceProvider: MicroserviceProvider {
func getPicture(by email: String, _ completion: (Response) -> Void)
}
class SomeProfilePictureAPI: ProfilePictureMicroserviceProvider {
struct Response {
let error: Error?
let picture: UIImage?
}
func getPicture(by email: String, _ completion: (Response) -> Void) {
// some HTTP magic
// will eventually call completion(_:) with a Response object
// which either holds an error or a UIImage.
}
}
Because I want to be able to Unit Test classes that will rely on this API, I need to be able to inject that profile picture dependency dynamically. By default it will use SomeProfilePictureAPI but when running tests I will be able to replace that with a MockProfilePictureAPI which will still adhere to ProfilePictureMicroserviceProvider.
And because I'm using associated types, I need to make classes that depend on ProfilePictureMicroserviceProvider generic.
At first, I naively did try to write my view controller like such
class SomeClass {
var profilePicProvider: ProfilePictureMicroserviceProvider
}
But that just led the frustratingly famous 'Protocol ProfilePictureMicroserviceProvider can only be used as a generic constraint because it has Self or associated type requirements' compile-time error.
Now I've been reading up on the issue over the last couple days, trying to wrap my head around Protocols with Associated Types (PATS), and figured I'd take the route of generic classes like such:
class SomeClass<T: ProfilePictureMicroserviceProvider> {
var profilePicProfider: T = SomeProfilePictureAPI()
}
But even then I get the following error:
Cannot convert value of type 'SomeProfilePictureAPI' to specified type 'T'
Even though having T being constrained to the ProfilePictureMicroserviceProvider protocol, and having SomeProfilePictureAPI adhere to it...
Basically the main idea was to reach 2 objectives: enforce Microservice structure with mandatory Response type, and make each Microservice mock-able for unit tests of dependent classes.
I'm now stuck with choosing either one of the two as I can't seem to make it work. Any help telling me what I'm doing wrong would be most welcome.
I've also had a look at type-erasure. But this to me seems very whacky and quite an effort for something that looks wrong on many aspects.
So basically my question is two-fold: how can I enforce my Microservices to define their own Response type ? And how can I easily replace them by mock microservices in classes that depend on them ?
You have to turn these requirements around;
Instead of injecting a MicroServiceProvider into each request, you should write a generic MicroService 'Connector' Protocol that should define what it expects from each request, and what each request expects it to return.
You can then write a TestConnector which conforms to this protocol, so that you have complete control over how your requests are handled. The best part is, your requests won't even need to be modified.
Consider the following example:
protocol Request {
// What type data you expect to decode and return
associatedtype Response
// Turn all the data defined by your concrete type
// into a URLRequest that we can natively send out.
func makeURLRequest() -> URLRequest
// Once the URLRequest returns, decode its content
// if it succeeds, you have your actual response object
func decode(incomingData: Data?) -> Response?
}
protocol Connector {
// Take in any type conforming to Request,
// do whatever is needed to get back some potential data,
// and eventually call the handler with the expected response
func perform<T: Request>(request: T, handler: #escaping (T.Response?) -> Void)
}
These are essentially the bare minimum requirements to setup such a framework. In real life, you'll want more requirements from your Request protocol (such as ways to define the URL, request headers, request body, etc).
The best part is, you can write default implementations for your protocols. That removes a lot of boilerplate code! So for an actual Connector, you could do this:
extension Connector {
func perform<T: Request>(request: T, handler: #escaping (T.Response?) -> Void) {
// Use a native URLSession
let session = URLSession()
// Get our URLRequest
let urlRequest = request.makeURLRequest()
// define how our URLRequest is handled
let task = session.dataTask(with: urlRequest) { data, response, error in
// Try to decode our expected response object from the request's data
let responseObject = request.decode(incomingData: data)
// send back our potential object to the caller's completion block
handler(responseObject)
}
task.resume()
}
}
Now, with that, all you need to do is implement your ProfilePictureRequest like this (with extra example class variables):
struct ProfilePictureRequest: Request {
private let userID: String
private let useAuthentication: Bool
/// MARK: Conform to Request
typealias Response = UIImage
func makeURLRequest() -> URLRequest {
// get the url from somewhere
let url = YourEndpointProvider.profilePictureURL(byUserID: userID)
// use that URL to instantiate a native URLRequest
var urlRequest = URLRequest(url: url)
// example use: Set the http method
urlRequest.httpMethod = "GET"
// example use: Modify headers
if useAuthentication {
urlRequest.setValue(someAuthenticationToken.rawValue, forHTTPHeaderField: "Authorization")
}
// Once the configuration is done, return the urlRequest
return urlRequest
}
func decode(incomingData: Data?) -> Response? {
// make sure we actually have some data
guard let data = incomingData else { return nil }
// use UIImage's native data initializer.
return UIImage(data: data)
}
}
If you then want to send a profile picture request out, all you then need to do is (you'll need a concrete type that conforms to Connector, but since the Connector protocol has default implementations, that concrete type is mostly empty in this example: struct GenericConnector: Connector {}):
// Create an instance of your request with the arguments you desire
let request = ProfilePictureRequest(userID: "JohnDoe", useAuthentication: false)
// perform your request with the desired Connector
GenericConnector().perform(request) { image in
guard let image = image else { return }
// You have your image, you can now use that instance whichever way you'd like
ProfilePictureViewController.current.update(with: image)
}
And finally, to set up your TestConnector, all you need to do is:
struct TestConnector: Connector {
// define a convenience action for your tests
enum Behavior {
// The network call always fails
case alwaysFail
// The network call always succeeds with the given response
case alwaysSucceed(Any)
}
// configure this before each request you want to test
static var behavior: Behavior
func perform<T: Request>(request: T, handler: #escaping (T.Response?) -> Void) {
// since this is a test, you don't need to actually perform any network calls.
// just check what should be done
switch Self.behavior {
case alwaysFail:
handler(nil)
case alwaysSucceed(let response):
handler(response as! T)
}
}
}
With this, you can easily define Requests, how they should configure their URL actions and how they decode their own Response type, and you can easily write mocks for you connectors.
Of course, keep in mind that the examples given in this answer are quite limited in how they can be used. I would highly suggest you to take a look at this library I wrote. It extends this example in a much more structured way.

What exactly does a closure do when retrieving data from a server?

I watched a video on closures and someone demonstrated the basics of closures in this way:
func outer(howMuch: Int) -> () -> Int {
var total = 0
inner() {
howMuch += total
return total
}
return inner
}
He then went on to say that when you do this:
let incrementBy10 = outer(10)
he said that incrementBy10 references the inner() function inside the closure.
Then he proceeds with a practical example with retrieving data:
let url = "*url here*"
let nsURL = NSURLSession.shareSession().dataTaskWithUrl(nsURL) {(data,response,error) in
print(NSString(data: data, encoding: NSUTF8StringEncoding)) }
How does the 'incrementby10' example relate to the practical example of fetching some data from a server. I did not understand what he meant by: "when you grab something from a url, you are not gonna have the content immediately. You can call the closure when the url has been downloaded."
This is an example of an asynchronous callback.
Asynchronous callbacks are used to execute a closure when a long-running operation (e.g. a network request) has finished. They allow us to fire the network request, passing in the callback, then continuing executing other code while the network operation is in progress. Only when the operation finishes, the closure is executed, with the data returned by the server passed in as an argument.
If we didn't use asynchronous closures, when we fetch something from the server, the app would freeze (execution would stop). This would be a synchronous network request, and it is not used as it would lead to a very laggy UI and a horrible user experience.
NSURLSession's dataTaskWithURL is by nature an asynchronous API, it accepts a closure as an argument and fires it when a response is received.
Asynchronous Callback
Example of an asynchronous callback network call (add it to a Swift Playground):
import UIKit
import XCPlayground // Only needed for Playground
// Only needed for Playground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
class HTTP {
class func GET(onSuccess: NSData -> Void ) {
NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://httpbin.org/get")!, completionHandler: { data, response, error in
onSuccess(data!)
}).resume()
}
}
print("About to fire request")
HTTP.GET({ payload in
let response = NSString(data: payload, encoding: NSUTF8StringEncoding)
print("Got network response: \(response)")
})
print("Just fired request")
The result that is printed is not what you might expect intuitively:
About to fire request
Just fired request
Got network response: ...
Just fired request is printed before Got network response: ... because the network request is performed asynchronously.
A synchronous version of the above code would produce the following output:
About to fire request
Got network response: ...
Just fired request

Pass scope to a named function, rather than a closure

I would like to separate the data processing of my NSURLSession into a separate method.
When making a URL request, I rely on the enclosing scope to provide me the user's callback.
This works:
static func makeRequest(url: String, callback: APICallback) {
let urlObject = NSURL(string: url)
var request = createRequest(urlObject!, method: "GET") // internal
var session = NSURLSession.sharedSession()
var task = session.dataTaskWithRequest(request){
(data, response, error) -> Void in
// do some basic parsing, error checking, then...
callback(data, nil)
}
task.resume()
}
There's rather a lot of basic parsing and error checking I'd like to do at the application level, however, so I want to define and pass a function instead of a closure to the dataTaskWithRequest method:
static func makeRequest(url: String, callback: APICallback) {
let urlObject = NSURL(string: url)
var request = createRequest(urlObject!, method: "GET") // internal
var session = NSURLSession.sharedSession()
var task = session.dataTaskWithRequest(request, completionHandler: gotResponse)
task.resume()
}
static private func gotResponse (nsdata: NSData!, response: NSURLResponse!, err: NSError!) -> Void {
// Do my parsing and handling here, instead.
// OOPS! Don't have access to the callback. :(
}
This all leads me to my question, which, despite the lengthy example, is about language features. Can I pass some captured scope to this method? In Javascript I could accomplish this using Function.prototype.bind, but I'm not sure how to do it in Swift.
This seems like a good example of when to use a curried method. Declare the function like this with a first parameter of an APICallback. Note the brackets.
static private func gotResponse(callback: APICallback)(nsdata: NSData!, response: NSURLResponse!, err: NSError!) -> Void {
// use callback: like normal
}
Then, use it like this:
var task = session.dataTaskWithRequest(request, completionHandler: gotResponse(callback))
(apologies, the syntax might not be 100% correct since your code isn’t stand alone so I can’t test it fully)
Curried functions are a little finicky and had some bugs in 1.1 (though they got fixed in 1.2), so instead of using the language support for them, you could try hand-rolling if the above doesn’t work, something like:
static private func gotResponse(callback: APICallback) -> (nsdata: NSData!, response: NSURLResponse!, err: NSError!) -> Void {
return { data, response, error in
// put the common code, capturing callback:, in here...
}
}

NSURLSession dataTaskWithRequest not being called

I have a second NSURLSession that is being called directly from the completionHandler of the previous one (it is dependent on the cookies generated from the first call). It worked for a while and sometimes still works, but most of the time does not. When I set through the debugger, it simply goes from the dataTaskWithRequest line to the line past the task.resume() call. Any thoughts?
func getDates () -> [NSDate] {
var urlDays = NSURL(string: "https://mycorrecturl.com")
var requestDays = NSMutableURLRequest(URL: urlDays!)
let sessionDays = NSURLSession.sharedSession()
// Create array of NSDate objects
var allDates = [NSDate]()
var task = sessionDays.dataTaskWithRequest(requestDays, completionHandler: {data, response, error -> Void in
// Convert into array of NSDate objects
})
task.resume()
return allDates
}
Why would this this dataTaskWithRequest function just not fire?
The problem that you are facing is that dataTaskWithRequest is an asynchronous call, that's the reason why you receive an empty array (that's only chance that finish and return a the same time and sometimes you receive data).
For that, you need to use a closure that get's call from the closure of dataTaskWithRequests.
Like this (here I only show you the declaration method with a closure):
func getDates (success:([NSDate])->Void){
And in the body of your network call:
var task = sessionDays.dataTaskWithRequest(requestDays, completionHandler: {data, response, error -> Void in
// Convert into array of NSDate objects
var yourArrayOfNSDateConverted:[NSDate] = [NSDate]()
success(yourArrayOfNSDateConverted)
})
Obviously the yourArrayOfNSDateConverted contains your process the data and also you need to manage the error (for that you can add another closure).
Looks like it is firing, I just wasn't waiting long enough. The function returned back to the calling function with no data, but thats because the NSURLSession wasn't finished yet. I guess I'm still getting the hang of the asynchronous nature of NSURLSession.