Requests and handlers in swift - 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.

Related

Alamofire synchronous request

I'm trying to make a Log In Call to the backend using Alamofire 5. The problem is when I make the call I need a value to return to the Controller to validate the credentials.
So, the problem is Alamofire only make asynchronous calls so I need to make it synchronous. I saw a solution using semaphore but I don't know how implement it.
This is the solution that I found:
func syncRequest(_ url: String, method: Method) -> (Data?, Error?) {
var data: Data?
var error: Error?
let url = URL(string: url)!
var request = URLRequest(url: url)
request.httpMethod = method.rawValue
let semaphore = DispatchSemaphore(value: 0)
let dataTask = URLSession.shared.dataTask(with: request) {
data = $0
error = $2
semaphore.signal()
}
dataTask.resume()
_ = semaphore.wait(timeout: .distantFuture)
return (data, error)
}
And, this is my request code:
AF.request(request)
.uploadProgress { progress in
}
.response(responseSerializer: serializer) { response in
if response.error == nil {
if response.data != nil {
do {
try decoder.decode(LogInSuccessful.self, from: response.data!)
} catch {
do {
try decoder.decode(LogInError.self, from: response.data!)
} catch {
}
}
}
statusCode = response.response!.statusCode
}
}

Get header when enumerate an array

i try to get header informations about remote files which url are located in an array.
My function to get header works perfectly when called alone but doesn't when inside array enumerate.
Here my functions:
func getHeaderInformations (myUrl: URL, completion: #escaping (_ content: String?) -> ()) {
var request = URLRequest(url: myUrl)
request.httpMethod = "HEAD"
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil, let reponse = response as? HTTPURLResponse, let contentType = reponse.allHeaderFields["Content-Type"],let contentLength = reponse.allHeaderFields["Content-Length"]
else{
completion(nil)
return
}
let content = String(describing: contentType) + "/" + String(describing: contentLength)
completion(content)
}
task.resume()
}
// download picture
func downloadPic (){
let minimumSize=UserDefaults.standard.object(forKey: "Minimum_size") as! Int64
var imgExtension:String=""
var type:String=""
var size:Int64=0
for (_,item) in LiensImgArrayURL.enumerated() {
getHeaderInformations(myUrl: item, completion: { content in
let myInfoArray = content?.components(separatedBy: "/")
type=(myInfoArray?[0])!
imgExtension=(myInfoArray?[1])!
size=Int64((myInfoArray?[2])!)!
})
if (type=="image" && size>=minimumSize) {
SaveFileToDirectory(myRemoteUrl: item, myExtension: imgExtension)
}
}
}
How can i write well this code for "getHeaderInformations" works and return good values inside the func "downLoadPic"?
Thanks for your answers...
Since getHeaderInformations works asynchronously put the code to save the file in the closure.
If you don't need the index in the for loop you don't need enumerated() either.
I tweaked the code bit to get rid of ugly question and exclamation marks and parentheses.
for item in LiensImgArrayURL {
getHeaderInformations(myUrl: item, completion: { content in
if let myInfoArray = content?.components(separatedBy: "/") {
type = myInfoArray[0]
imgExtension = myInfoArray[1]
size = Int64(myInfoArray[2])!
if type == "image" && size >= minimumSize {
SaveFileToDirectory(myRemoteUrl: item, myExtension: imgExtension)
}
}
})
}

Return values from completion handler

I want to return the values from an api call.
The call to my api class (I want to get the values in res):
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let t = Api_test();
let res = t.getSomething();
print(res)
}
}
The api class:
import Foundation
class Api_test {
func getAllStations(completionHandler: (response : XMLIndexer) -> ()) {
getRequest { result in
completionHandler(response: SWXMLHash.parse(result))
};
}
func getRequest(completionHandler: (result: NSData) -> ()) {
let baseUrl = "http://api.test.com"
let request = NSMutableURLRequest(URL: NSURL(string: baseUrl)!)
let session = NSURLSession.sharedSession()
request.HTTPMethod = "GET"
let task = session.dataTaskWithRequest(request) {
(data, response, error) in
if data == nil {
print("dataTaskWithRequest error: \(error)")
return
} else {
completionHandler(result: data!)
}
}
task.resume()
}
}
Everything works as thought, but I'm stuck at the point to return the values back to the getSomething function. The data is in xml format. How can I get the result set as returned values in the res (viewDidLoad)?
NSURLSession is a fully asynchronous networking API so ideally your view controller should operate correctly and not wait for the data to be returned from the network.
You have three options here:
You can pass a completion block to getSomething and have it pass the result to the block:
func getSomething(completionHandler: (result: XMLIndexer) -> ()) {
getRequest { result in
completionHandler(result: SWXMLHash.parse(result))
}
}
override func viewDidLoad() {
...
t.getSomething { res in
print(res)
}
}
If you desperately need the XML data in hand before view is displayed onto screen, you can make the main thread wait till network operation finishes executing. You can use dispatch_semaphore_t:
func getSomething() -> XMLIndexer? {
var xml: XMLIndexer? = nil
let semaphore: dispatch_semaphore_t = dispatch_semaphore_create(0)
getRequest { result in
xml = SWXMLHash.parse(result)
dispatch_semaphore_signal(semaphore)
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
return xml
}
Last option is, you can use another 3rd party that does the parsing synchronously. There is a great one named Ono:
var error: NSError?
let xml = ONOXMLDocument(data: result, error: &error)

Sequence of actions with RxSwift

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
}
}
}

Return data out of http sendAsyncRequest call in swift

Ok so first of all, I've read several other post on the subject and I know that what Im going to describe could be a synchronous call but what I really need is to get the data and work with it, so if I should look in some other direction let me know:
I have to make calls to an api to retrieve json objects and use them to populate a table view. my problem is that I can't manage to pass / return the data to work with it:
in the controller:
callProjectTypes()
var testjson = JSON(data:test)
println("should print some data")
println(testjson[0])
the call functions:
func callProjectTypes() -> (NSData) {
var data = NSData()
serverCall("http://url/to/project/types") {
responseData, error in
if responseString == nil {
println("Error during post: \(error)")
return
}
// use responseData here
data = responseData
}
return data
}
func serverCall(url: String, completionHandler: (responseData: NSData!, error: NSError!) -> ()) {
var URL: NSURL = NSURL(string: url)!
var request:NSMutableURLRequest = NSMutableURLRequest(URL:URL)
request.HTTPMethod = "GET";
var creds = encodeCredentials()
request.addValue("\(creds)", forHTTPHeaderField: "Authorization")
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()){
response, data, error in
var output: NSData!
if data != nil {
output = data
}
completionHandler(responseString: output, error: error)
}
}
println(testjson[0]) always return "nill" so I assume the "return data" from my callProjectTypes function is coming back before the data arrived.
What should I do to make this work?
To return values asynchronously you cannot use a return statement because you have no guarantee that 'serverCall' will have finished when the return statement is executed. Instead, you have to return the 'data' value in an asynchronous fashion, such as by providing a callback to 'callProjectTypes' itself. Example:
callProjectTypes()
{
(data : NSData) in
let testjson = JSON(data:data)
println("should print some data")
println(testjson[0])
}
The async function must take and execute a callback:
func callProjectTypes(callback: (data : NSData)->Void)
{
serverCall("http://url/to/project/types") {
responseData, error in
callback(responseData)
}
Now you can guarantee that the code in callback will only be executed after the data has been returned by 'serverCall'.
You cant return that way using async.
You have to do everything you need with the data inside the closure.
You have to do this:
func callProjectTypes(action: NSData->()) {
serverCall("http://url/to/project/types") { responseData, error in
if responseData == nil {
println("Error during post: \(error)")
return
}
action(responseData)
}
}
callProjectTypes { data in
let testjson = JSON(data:data)
println("should print some data")
println(testjson[0])
}