Sequence of actions with RxSwift - swift

I'm using RxSwift to simply my code. For my current project I'd like to apply RxSwift's principles to a mess of completion blocks from the LayerKit:
layerClient.connectWithCompletion { (success, error) -> () in
if (!success) {
// Error
} else {
layerClient.requestAuthenticationNonceWithCompletion { (nonce, error) -> () in
// Even more blocks
}
}
}
I'm thinking about something like this:
// In extension
public func rx_connect() -> Observable<Bool> {
return create { observer in
self.connectWithCompletion { (success, error) -> ()in
if (success) {
observer.on(.Next(success))
observer.on(.Completed)
} else {
observer.on(.Error(error))
}
}
return NopDisposable.instance
}
}
public func rx_requestAuthenticationNonce() -> Observable<String> {
// Same for annother method
}
// In AppDelegate
self.layerClient.rx_connect()
.then() // requestAuthenticationNonceWithCompletion and use the nonce for next action
.then()
.subscribeNext(…
.onError(… // To catch all errors
RxSwift does not have a then() method. Is there another way to do this chaining stuff or am I thinking wrong on how to use ReactiveX in general?

Right, RxSwift doesn't have the then operator because the name is very risky.
then can be a lot of things in the Reactive Extensions world a map a flatMap or even a switchLatest, depending on the context.
In this case I would suggest to use a flatMap because that is going to be returned using a map is an Observable of Observables, that in most cases are hard to combine and manage. So I would do:
self.layerClient.rx_connect()
.flatMap(){ _ in
return rx_requestAuthenticationNonce()
.catchError(displayError)
}
.subscribeNext(…
.onError(… // To catch all errors
I would use flatMap instead of map in this case because I am then able to catch errors without terminating the sequence in case rx_requestAuthenticationNonce() fails.
Be careful in using the catchError operator, this operator will terminate the sequence after recovering and any extra event will be ignored after that.

This is authorization part in my project. Just an example for chaining requests. It turns the result of chained requests into a bool answer. It use 'flatMap' because result of old request is used in building new request. Otherwise, you can use 'concat' instead of 'flatMap'.
let client = RxSimpleLazyHttpClient.sharedInstance
let uuid = UIDevice().identifierForVendor!.UUIDString
let helloheaders = ["X-User-Identifier": "id:" + uuid]
let helloRequest: Observable<StringDictionary> = client.makeRequest(verb: .GET, url: NSURL(string: self.API_HOST + self.API_HELLO)!, parameters: [:], headers: helloheaders)
.map { result in
if (self.enableDebugOutput) {
self.debug(result)
}
let json = JSON(data: result.2!)
let userKey = json["user_key"].string
let userSecretHash = json["user_secret_hash"].string
return ["user_key": userKey ?? "", "user_secret_hash": userSecretHash ?? ""]
}
let jointAuthRequest: (StringDictionary -> Observable<StringDictionary>) = { [unowned self] stringDictionary in
Logger.D(stringDictionary.debugDescription)
let userKey = stringDictionary["user_key"]
let headers = ["X-User": userKey ?? ""]
return client.makeRequest(verb: .GET, url: NSURL(string: self.API_HOST + self.API_AUTH)!, parameters: [:], headers: headers)
.map { result in
if (self.enableDebugOutput) {
self.debug(result)
}
let json = JSON(data: result.2!)
let nonce = json["nonce"].string
var result = [String: String]()
result["nonce"] = nonce
return result.merge(stringDictionary)
}
}
let jointAuthNonceRequest: (StringDictionary -> Observable<StringDictionary>) = { [unowned self] stringDictionary in
Logger.D(stringDictionary.debugDescription)
let nonce = stringDictionary["nonce"] ?? ""
let userSecretHash = stringDictionary["user_secret_hash"] ?? ""
let headers = ["X-Pass": (userSecretHash + nonce).sha1().webSafeBase64()]
return client.makeRequest(verb: .GET, url: NSURL(string: self.API_HOST + self.API_AUTH + "/" + nonce)!, parameters: [:], headers: headers)
.map { result in
if (self.enableDebugOutput) {
self.debug(result)
}
let json = JSON(data: result.2!)
let sessionKey = json["session_key"].string
let sessionSecret = json["session_secret"].string
var result = [String: String]()
result["session_key"] = sessionKey
result["session_secret"] = sessionSecret
return result.merge(stringDictionary)
}
}
let jointResult: (StringDictionary -> Observable<Bool>) = { result in
let userKey = result["user_key"]
let userSecretHash = result["user_secret_hash"]
let sessionKey = result["session_key"]
let sessionSecret = result["session_secret"]
if userKey == nil || userSecretHash == nil || sessionKey == nil || sessionSecret == nil {
Logger.D("Auth Fail")
return just(false)
}
/* You can store session key here */
return just(true)
}
return helloRequest
.shareReplay(1)
.observeOn(RxScheduler.sharedInstance.mainScheduler)
.flatMap(jointAuthRequest)
.flatMap(jointAuthNonceRequest)
.flatMap(jointResult)
This is the 'makeRequest' method.
public func makeRequest(verb verb: Alamofire.Method, url: NSURL, parameters: [String : String]?, headers: [String : String]?) -> Observable<RxRequestResult> {
return create { observer in
Alamofire.request(verb, url, parameters: nil, encoding: ParameterEncoding.URL, headers: headers)
.response { request, response, data, error in
observer.onNext((request, response, data, error))
}
return AnonymousDisposable {
// when disposed
}
}
}

Related

Swift Alamofire Async Issue

new to swift here.
I'm trying to make an AF.request call inside another AF.request call and everything works fine.
The issue is that the fetchAllUsers() gets called after everything loads up. So instead of getting all the users right away, I have to refresh the page in order to get the fetchAllUsers() to execute.
I thought using closures would avoid this problem but it's still occurring.
Am I doing something wrong?
func fetchAllUsers(completionHandler: #escaping ([User]) -> Void) {
let allUsersUrl = baseUrl + "users/"
let headers: HTTPHeaders = [
.authorization(bearerToken: NetworkManager.authToken)
]
if let url = URL(string: allUsersUrl) {
AF.request(url, headers: headers).responseDecodable(of: [User].self) { response in
if let users = response.value {
completionHandler(users)
} else {
print("Users is empty")
}
}
} else {
print("URL error")
}
}
func login(param: [String:String], completionHandler: #escaping (Int) -> Void) {
let loginUrl = baseUrl + "auth/login"
// Sets the Logged In User
AF.request(loginUrl, method: .post, parameters: param, encoding: JSONEncoding.default, headers: nil).responseString { response in
if let data = response.data {
let authTokenString = String(decoding: data, as:UTF8.self).components(separatedBy: " ")
// Sets the authentication token
NetworkManager.authToken = authTokenString[1]
self.fetchAllUsers { users in
for user in users {
if param["username"] == user.username {
HomeViewModel.loggedInUser = user
}
}
}
completionHandler(response.response!.statusCode)
}
}
}
Pass completion handler of main function and status code to fetchAllUsers and call it there after it's own completion handler
completionHandler(response.response!.statusCode) was being executed before self.fetchAllUsers closure because it was waiting for api response to complete. completionHandler(response.response!.statusCode) was destroying self.fetchAllUsers closure before it is executed, so I called completionHandler(response.response!.statusCode) inside self.fetchAllUsers after it's closure
completionHandler(users)
mainCompletion(statusCode)
Complete code works following
func fetchAllUsers(statusCode:Int, mainCompletion: #escaping (Int) -> Void, completionHandler: #escaping ([User]) -> Void) {
let allUsersUrl = baseUrl + "users/"
let headers: HTTPHeaders = [
.authorization(bearerToken: NetworkManager.authToken)
]
if let url = URL(string: allUsersUrl) {
AF.request(url, headers: headers).responseDecodable(of: [User].self) { response in
if let users = response.value {
completionHandler(users)
mainCompletion(statusCode)
} else {
print("Users is empty")
}
}
} else {
print("URL error")
}
}
func login(param: [String:String], completionHandler: #escaping (Int) -> Void) {
let loginUrl = baseUrl + "auth/login"
// Sets the Logged In User
AF.request(loginUrl, method: .post, parameters: param, encoding: JSONEncoding.default, headers: nil).responseString { response in
if let data = response.data {
let authTokenString = String(decoding: data, as:UTF8.self).components(separatedBy: " ")
// Sets the authentication token
NetworkManager.authToken = authTokenString[1]
self.fetchAllUsers(statusCode: response.response!.statusCode, mainCompletion: completionHandler) { users in
for user in users {
if param["username"] == user.username {
HomeViewModel.loggedInUser = user
}
}
}
}
}
}

Returning data from async call that takes multiple params in Swift function

I was trying to create a post method so I could reuse it further in my code.
I saw this example Returning data from async call in Swift function that gives partial solution to my problem but don't know how to call the function once I define it.
This is the function I am trying to call:
class func postRequest(url: URL, request: URLRequest, saveCookie: Bool, completionHandler: #escaping (_ postRequestStatus: [String:Any]) -> ()) {
let session = URLSession.shared
//So now no need of type conversion
let task = session.dataTask(with: request) {
(data, response, error) in
func displayError(_ error: String) {
print(error)
}
/* GUARD: Was there an error? */
guard (error == nil) else {
displayError("There was an error with your request: \(String(describing: error))")
return
}
guard let statusCode = (response as? HTTPURLResponse)?.statusCode, statusCode >= 200 && statusCode <= 299 else {
displayError("Your request returned a status code other than 2xx!")
return
}
/* GUARD: Was there any data returned? */
guard let data = data else {
displayError("No data was returned by the request!")
return
}
/* Since the incoming cookies will be stored in one of the header fields in the HTTP Response,parse through the header fields to find the cookie field and save the data */
if saveCookie{
let httpResponse: HTTPURLResponse = response as! HTTPURLResponse
let cookies = HTTPCookie.cookies(withResponseHeaderFields: httpResponse.allHeaderFields as! [String : String], for: (response?.url!)!)
HTTPCookieStorage.shared.setCookies(cookies as [AnyObject] as! [HTTPCookie], for: response?.url!, mainDocumentURL: nil)
}
let json: [String:Any]?
do
{
json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:Any] ?? [:]
}
catch
{
displayError("Could not parse the data as JSON: '\(data)'")
return
}
guard let server_response = json else
{
displayError("Could not parse the data as JSON: '\(data)'")
return
}
if let userID = server_response["UserID"] as? Int64 {
print(userID)
completionHandler(server_response)
}else{
displayError("Username or password incorrect.")
}
}
return task.resume()
}
This is the caller function:
class func loginPostRequest(post_data: [String:Any], completionHandler: #escaping (_ postRequestStatus: [String:Any]) -> ()){
let url = URL(string: HTTPConstant.Login.Url)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
var paramString = ""
for (key, value) in post_data
{
paramString = paramString + (key) + "=" + (value as! String) + "&"
}
request.httpBody = paramString.data(using: .utf8)
//in the line below I get the error message, extra argument "request" in call.
postRequest(url: url, request: request, saveCookie: true, completionHandler: { postRequestStatus in
completionHandler(postRequestStatus)
})
}
You cannot make loginPostRequest return NSDictionary because you are making async call with what you need is to create completion block same way you have create with postRequest method also from Swift 3 you need to use URLRequest with mutable var object instead of NSMutableURLRequest you need to also change the postRequest function's request argument type to URLRequest so latter no need to convert NSMutableURLRequest to URLRequest and use Swift type dictionary instead of NSDictionary
class func loginPostRequest(post_data: [String:Any], completionHandler: #escaping (_ postRequestStatus: [String:Any]) -> ()){
let url = URL(string: HTTPConstant.Login.Url)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
var paramString = ""
for (key, value) in post_data
{
paramString = paramString + (key as! String) + "=" + (value as! String) + "&"
}
request.httpBody = paramString.data(using: .utf8)
postRequest(url: url, request: request, saveCookie: true, completionHandler: { postRequestStatus in
completionHandler(postRequestStatus)
})
}
Now simply changed the argument type of request to URLRequest from NSMutableURLRequest in method postRequest
class func postRequest(url: URL, request: URLRequest, saveCookie: Bool, completionHandler: #escaping (_ postRequestStatus: [String:Any]) -> ()) {
let session = URLSession.shared
//So now no need of type conversion
let task = session.dataTask(with: request) { (data, response, error) in
func displayError(_ error: String) {
print(error)
}
/* GUARD: Was there an error? */
guard (error == nil) else {
displayError("There was an error with your request: \(String(describing: error))")
return
}
guard let statusCode = (response as? HTTPURLResponse)?.statusCode, statusCode >= 200 && statusCode <= 299 else {
displayError("Your request returned a status code other than 2xx!")
return
}
/* GUARD: Was there any data returned? */
guard let data = data else {
displayError("No data was returned by the request!")
return
}
/* Since the incoming cookies will be stored in one of the header fields in the HTTP Response,parse through the header fields to find the cookie field and save the data */
if saveCookie{
let httpResponse: HTTPURLResponse = response as! HTTPURLResponse
let cookies = HTTPCookie.cookies(withResponseHeaderFields: httpResponse.allHeaderFields as! [String : String], for: (response?.url!)!)
HTTPCookieStorage.shared.setCookies(cookies as [AnyObject] as! [HTTPCookie], for: response?.url!, mainDocumentURL: nil)
}
let json: [String:Any]?
do
{
json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:Any] ?? [:]
}
catch
{
displayError("Could not parse the data as JSON: '\(data)'")
return
}
guard let server_response = json else
{
displayError("Could not parse the data as JSON: '\(data)'")
return
}
if let userID = server_response["UserID"] as? Int64 {
print(userID)
completionHandler(server_response)
}else{
displayError("Username or password incorrect.")
}
}
return task.resume()
}
Now when you call this loginPostRequest you are having response in completion block of it.
Functions that receive a closure as parameter can be called like any other functions:
postRequest(url: yourUrlObject, request: yourUrlRequest, saveCookie: true/false, completionHandler: { postRequestStatus in
// ... code that will run once the request is done
})
If the closure is the last parameter you can pass it outside the parenthesis:
postRequest(url: yourUrlObject, request: yourUrlRequest, saveCookie: true/false) { postRequestStatus in
// ... code that will run once the request is done
})
You can check the Swift book to learn more about closures and functions.
By the way, your postRequest method looks weird, I haven't checked deeply into it, but for instance I believe although url is one of the parameters it isn't actually used. Some other answer pointed other problems into that function.

Swift http request use urlSession

I want to write func for HTTP Request to my server and get some data, when i print it (print(responseString)) it looks good, but when i try to return data, its always empty
public func HTTPRequest(dir: String, param: [String:String]?) -> String{
var urlString = HOST + dir + "?"
var responseString = ""
if param != nil{
for currentParam in param!{
urlString += currentParam.key + "=" + currentParam.value + "&"
}
}
let url = URL(string: urlString)
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
guard error == nil else {
print("ERROR: HTTP REQUEST ERROR!")
return
}
guard let data = data else {
print("ERROR: Empty data!")
return
}
responseString = NSString(data: data,encoding: String.Encoding.utf8.rawValue) as! String
print(responseString)
}
task.resume()
return responseString
}
As mentioned in Rob's comments, the dataTask closure is run asynchronously. Instead of returning the value immediately, you would want to provide a completion closure and then call it when dataTask completes.
Here is an example (for testing, can be pasted to Xcode Playground as-is):
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let HOST = "http://example.org"
public func HTTPRequest(dir: String, param: [String: String]?, completion: #escaping (String) -> Void) {
var urlString = HOST + dir + "?"
if param != nil{
for currentParam in param! {
urlString += currentParam.key + "=" + currentParam.value + "&"
}
}
let url = URL(string: urlString)
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
guard error == nil else {
print("ERROR: HTTP REQUEST ERROR!")
return
}
guard let data = data else {
print("ERROR: Empty data!")
return
}
let responseString = NSString(data: data,encoding: String.Encoding.utf8.rawValue) as! String
completion(responseString)
}
task.resume()
}
let completion: (String) -> Void = { responseString in
print(responseString)
}
HTTPRequest(dir: "", param: nil, completion: completion)
You need to use completion block instead of returning value because the dataTask closure is run asynchronously, i.e. later, well after you return from your method. You don't want to try to return the value immediately (because you won't have it yet). You want to (a) change this function to not return anything, but (b) supply a completion handler closure, which you will call inside the dataTask closure, where you build responseString.
For example, you might define it like so:
public func HTTPRequest(dir: String, param: [String:String]? = nil, completionHandler: #escaping (String?, Error?) -> Void) {
var urlString = HOST + dir
if let param = param {
let parameters = param.map { return $0.key.percentEscaped() + "=" + $0.value.percentEscaped() }
urlString += "?" + parameters.joined(separator: "&")
}
let url = URL(string: urlString)
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
guard let data = data, error == nil else {
completionHandler(nil, error)
return
}
let responseString = String(data: data, encoding: .utf8)
completionHandler(responseString, nil)
}
task.resume()
}
Note, I'm percent escaping the values in the parameters dictionary using something like:
extension String {
/// Percent escapes values to be added to a URL query as specified in RFC 3986
///
/// This percent-escapes all characters besides the alphanumeric character set and "-", ".", "_", and "~".
///
/// http://www.ietf.org/rfc/rfc3986.txt
///
/// - Returns: Returns percent-escaped string.
func percentEscaped() -> String {
let allowedCharacters = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~")
return self.addingPercentEncoding(withAllowedCharacters: allowedCharacters)!
}
}
And then you'd call it like so:
HTTPRequest(dir: directory, param: parameterDictionary) { responseString, error in
guard let responseString = responseString else {
// handle the error here
print("error: \(error)")
return
}
// use `responseString` here
DispatchQueue.main.async {
// because this is called on background thread, if updating
// UI, make sure to dispatch that back to the main queue.
}
}
// but don't try to use `responseString` here

Alamofire 3 Custom Encoding To Alamofire 4 Custom Encoding

I have method writing in Alamofire 3 with customParameterEncoding. This custom encoding just replaces "[]=" with "=" in queryString and returns it.
Alamofire.request(.GET, SearchURL, parameters: params, encoding: customEncoding, headers: headers).validate().responseJSON {
response in
switch response.result {
case .success:
print("success")
break
case .failure(let error):
print("Error: " + error.localizedDescription)
break
}
}
and custom encoding parameter
let customEncoding = ParameterEncoding.Custom { requestConvertible, parameters in
let (mutableRequest, error) = ParameterEncoding.URL.encode(requestConvertible, parameters: parameters)
mutableRequest.URL = NSURL(string: mutableRequest.URLString.stringByReplacingOccurrencesOfString("%5B%5D=", withString: "="))
return (mutableRequest, error)
}
How to convert customEncoding to Alamofire 4 version?
Just separating structs for GET and POST also works.
http://matsue.github.io/post/how-to-remove-square-brackets-with-alamofire/
Alamofire 4 with Swift 3
// Remove square brackets for GET request
struct CustomGetEncoding: ParameterEncoding {
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var request = try URLEncoding().encode(urlRequest, with: parameters)
request.url = URL(string: request.url!.absoluteString.replacingOccurrences(of: "%5B%5D=", with: "="))
return request
}
}
// Remove square brackets for POST request
struct CustomPostEncoding: ParameterEncoding {
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var request = try URLEncoding().encode(urlRequest, with: parameters)
let httpBody = NSString(data: request.httpBody!, encoding: String.Encoding.utf8.rawValue)!
request.httpBody = httpBody.replacingOccurrences(of: "%5B%5D=", with: "=").data(using: .utf8)
return request
}
}
// Use structs for requests
Alamofire.request("http://example.com", method: .get, parameters: ["foo": ["bar1", "bar2"]], encoding: CustomGetEncoding())
Alamofire.request("http://example.com", method: .post, parameters: ["foo": ["bar1", "bar2"]], encoding: CustomPostEncoding())
In Alamofire 4.0 you should use ParameterEncoding.
struct CustomEncoding: ParameterEncoding {
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var request = try! URLEncoding().encode(urlRequest, with: parameters)
let urlString = request.url?.absoluteString.replacingOccurrences(of: "%5B%5D=", with: "=")
request.url = URL(string: urlString!)
return request
}
}
Looks like a bug by design in Alamofire: https://github.com/Alamofire/Alamofire/issues/329
This is my solution in Swift3 with both get & post method:
extension NSNumber {
fileprivate var isBool: Bool {
return CFBooleanGetTypeID() == CFGetTypeID(self)
}
}
struct CustomEncoding: ParameterEncoding {
fileprivate func escape(_ string: String) -> String {
let generalDelimitersToEncode = ":#[]#" // does not include "?" or "/" due to RFC 3986 - Section 3.4
let subDelimitersToEncode = "!$&'()*+,;="
var allowedCharacterSet = CharacterSet.urlQueryAllowed
allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
var escaped = ""
if #available(iOS 8.3, *) {
escaped = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string
} else {
let batchSize = 50
var index = string.startIndex
while index != string.endIndex {
let startIndex = index
let endIndex = string.index(index, offsetBy: batchSize, limitedBy: string.endIndex) ?? string.endIndex
let range = startIndex..<endIndex
let substring = string.substring(with: range)
escaped += substring.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? substring
index = endIndex
}
}
return escaped
}
fileprivate func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {
var components: [(String, String)] = []
if let dictionary = value as? [String: Any] {
for (nestedKey, value) in dictionary {
components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
}
} else if let array = value as? [Any] {
for value in array {
components += queryComponents(fromKey: "\(key)[]", value: value)
}
} else if let value = value as? NSNumber {
if value.isBool {
components.append((escape(key), escape((value.boolValue ? "1" : "0"))))
} else {
components.append((escape(key), escape("\(value)")))
}
} else if let bool = value as? Bool {
components.append((escape(key), escape((bool ? "1" : "0"))))
} else {
components.append((escape(key), escape("\(value)")))
}
return components
}
fileprivate func query(_ parameters: [String: Any]) -> String {
var components: [(String, String)] = []
for key in parameters.keys.sorted(by: <) {
let value = parameters[key]!
components += queryComponents(fromKey: key, value: value)
}
return components.map { "\($0)=\($1)" }.joined(separator: "&")
}
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var request: URLRequest = try urlRequest.asURLRequest()
guard let parameters = parameters else { return request }
guard let mutableRequest = (request as NSURLRequest).mutableCopy() as? NSMutableURLRequest else {
// Handle the error
return request
}
if request.urlRequest?.httpMethod == "GET" {
mutableRequest.url = URL(string: (mutableRequest.url?.absoluteString.replacingOccurrences(of: "%5B%5D=", with: "="))!)
}
if request.urlRequest?.httpMethod == "POST" {
mutableRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)
if mutableRequest.httpBody != nil {
let httpBody = NSString(data: mutableRequest.httpBody!, encoding: String.Encoding.utf8.rawValue)!
mutableRequest.httpBody = httpBody.replacingOccurrences(of: "%5B%5D=", with: "=").data(using: String.Encoding.utf8)
}
}
request = mutableRequest as URLRequest
return request
}
}
then send request:
let request = Alamofire.request(URLString, method: method, parameters: parameters, encoding: CustomEncoding(), headers: headers)
request.responseData(queue: self.netWorkQueue) { (response) in
//......//
}
Try Use URLEncoding(arrayEncoding: .noBrackets)
Alamofire.request(.GET, SearchURL, parameters: params, encoding: URLEncoding(arrayEncoding: .noBrackets), headers: headers).validate().responseJSON {
response in
switch response.result {
case .success:
print("success")
break
case .failure(let error):
print("Error: " + error.localizedDescription)
break
}
}

Requests and handlers in swift

I need to get data from url. For it I have get method in "HTTPClient" class.
func getRequest(url: String, parameters: String = "", completion: (NSData) -> ()) {
var request = NSMutableURLRequest(URL: NSURL(string: url)!)
if parameters != "" {
request = NSMutableURLRequest(URL: NSURL(string: url + "?" + parameters)!)
}
request.HTTPMethod = "GET"
request.HTTPShouldHandleCookies = true
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
(data, response, error) in
if error != nil {
print(error?.localizedDescription)
return
} else {
completion(data!)
}
}
task.resume()
}
But when I call it from "MainService" class I don't get data in handler. (I don't come in handler)
func getAvailableCoins() -> [Coin]? {
var coins = [Coin]?()
httpClient.getRequest("http://shapeshift.io/getcoins", completion: { data in
coins = self.shapeShiftService.availableCoins(data)
})
return coins
}
What problem can be there?
I just realised that you return from your getAvailableCoins() method BEFORE your handler gets a chance to be called. Consequently you return an empty array.
So basically the thing is that getAvailableCoins() is an asynchronous operation and you can't use return statement to return result. What you could do is declare a special method and call it when your coins are fetched:
func getAvailableCoins() {
var coins = [Coin]?()
httpClient.getRequest("http://shapeshift.io/getcoins", completion: { data in
coins = self.shapeShiftService.availableCoins(data)
coinsAreReady(coins)
})
}
func coinsAreReady(coins: [Coin]?) {
//do what you need with your coins here
}
Now coinsAreReady will be called when your data is loaded and you can work with it from there.