How to Synchronize iOS class object (Swift / iOS 9) - ios

I have a class method, This method will take some time to get data from an http request.
Related part of the method:
do{
request.HTTPBody = try NSJSONSerialization.dataWithJSONObject(params, options: NSJSONWritingOptions())
} catch {
print("Cound not serilize")
}
let task = session.dataTaskWithRequest(request) {
data, response, error in
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions()) as? NSDictionary
self.result = json! as? [String : AnyObject]
print(self.result)
It will print correct result from JSON, but in other side the object instance authGuestAPI will not:
let authGuestAPI = API(url: apiUrl, params: params, method: "POST")
authGuestAPI.run()
print("RESULT: \(authGuestAPI.result)")
It will print "RESULT: nil"
but If I put NSThread.sleepForTimeInterval(2) before print("RESULT: \(authGuestAPI.result)") It will print correct json data.

you need to create a function when you call the api request. Then implement a completion handler with that function. Add the code in the completion handler that you want to be executed when the value is eventually returned from the api.

I use SwiftyJSON and Alamofire to make download method with completionHandler to fix this issue.
func download(completionHandler: (JSON?, NSError?) -> ()) -> (){
Alamofire.request(.POST, url, parameters: self.params, encoding: ParameterEncoding.URLEncodedInURL , headers: self.header).responseJSON { (result: Response<AnyObject, NSError>) -> Void in
if result.result.isSuccess {
self.hasError = false
self.result = JSON(result.result.value!)
} else if result.result.isFailure {
self.errorCode = result.result.error!.code
self.errorMessage = result.result.error!.localizedDescription
print(result.result.error?.localizedDescription)
self.hasError = true
}
completionHandler(self.result, result.result.error)
}
}

Related

Alamofire doesnt allow to send object directly

My API only accepts object as the body , but alamofire only sends Dictionary as an object, which my server is not accepting requesting help
I have to call an API which is a post api using alamofire
as soon as i convert the model to dictionary and dicitionary to json
and post it Alamofire does not allow me to post a string
it allows me to send a dictionary which my api does not accept
["key":"value"]- Not acceptable
{"key":"value"}- Acceptable
Can anyone share any solution?
I am using Swift 5 , Xcode 10, Alamofire 4.8.2
do{
let d = try data.asDictionary()
jsonString = DictionaryToJSON(data: dictionary)
} catch {
print(error)
}
Alamofire.request(url, method: .post, parameters: jsonString, encoding: .utf8, headers: [: ]).responseJSON { (res) in
print(res.result)
print("Request Data \(res.request) \n Dictionary \(jsonString)")
do {
let d = try JSONDecoder().decode([OTPMessage].self, from: res.data!)
print(d[0].message)
} catch {
print(error)
}
}
// Dictionary to JSON
func DictionaryToJSON(data: [String:Any])->String {
if let theJSONData = try? JSONSerialization.data(
withJSONObject: data,
options: .prettyPrinted
),
let theJSONText = String(data: theJSONData, encoding: String.Encoding.ascii) {
print("JSON string = \n\(theJSONText)")
return theJSONText
}
else {
return ""
}
}
// Object to Dictionary
extension Encodable {
func asDictionary() throws -> [String: Any] {
let data = try JSONEncoder().encode(self)
guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
throw NSError()
}
return dictionary
}
}
//Struct
struct OTPMessage:Codable {
var message = String()
}
You don't have to convert your dictionary to a JSON String because Alamofire can do the encoding, see this example.
I suggest you to change your code to something like this
do{
let dictionary = try data.asDictionary()
Alamofire.request(url, method: .post, parameters: dictionary, encoding: .JSON, headers: [:]).responseJSON { (res) in
print(res.result)
print("Request Data \(res.request) \n Dictionary \(jsonString)")
do{
let d = try JSONDecoder().decode([OTPMessage].self, from: res.data!)
print(d[0].message)
}catch{
print(error)
}
}
} catch{
print(error)
}
With Alamofire you can not do this. What you need to do is creating a URLRequest object and setting the httpBody property of it and then passing it to Alamofire.
URLRequest allows you to have Data as POST body.
var request = URLRequest(url: urlFinal)
request.httpMethod = HTTPMethod.post.rawValue
request.allHTTPHeaderFields = dictHeader
request.timeoutInterval = 10
request.httpBody = newPassword.data(using: String.Encoding.utf8)
Alamofire.request(request).responseString { (response) in
if response.response!.statusCode >= 200 && response.response!.statusCode <= 300 {
completion("success")
}else {
completion("failed")
}
}
here newPassword is string. from there I created the Data object. You have to convert your Custom class object to Data object.

How to get the value of the variable that is assigned in a closure (Swift)

I'm using the Twitter REST API in Swift, and I am trying to get the value of a variable that is assigned inside of a Twitter Request closure, so that I can use that value outside of the closure.
I acquired this code from the Twitter REST API tutorial for Swift, located at: https://dev.twitter.com/twitterkit/ios/access-rest-api
func jsonAvailable() -> Bool {
// Swift
let client = TWTRAPIClient()
let statusesShowEndpoint = "https://api.twitter.com/1.1/statuses/show.json"
let params = ["id": "20"]
var clientError : NSError?
var jsonAvailable: Bool = false
let request = client.urlRequest(withMethod: "GET", url:
statusesShowEndpoint, parameters: params, error: &clientError)
client.sendTwitterRequest(request) { (response, data, connectionError)-> Void in
if connectionError != nil {
print("Error: \(connectionError)")
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: [])
print("json: \(json)")
jsonAvailable = true
} catch let jsonError as NSError {
print("json error: \(jsonError.localizedDescription)")
}
print("Value of jsonAvailable: \(jsonAvailable)")
return jsonAvailable
//always returns false, even if it is changed to true inside of the closure
}
In the last line, jsonAvailable is always false, even when it is changed to true inside of the closure. How can I get the value of jsonAvailable at the end of the function, even as it is modified inside of the sendTwitterRequest closure?
I have tried writing this closure in a separate function and then calling the function to get the value, but because it is a custom closure that requires the client to be called by "sendTwitterRequest" I have found it difficult to pass all these required parameters to fit the API.
Thanks for the help!
Your closure is async. What happens is that you go through all the function body before sendTwitterRequest assigns true to jsonAvailable, resulting in jsonAvailable being false. What you need to do is having a callback instead, providing the json status if you'd like (or the json itself as a nillable object).
EDIT: You could have something like this
func jsonAvailable(callback: ((_ isJsonAvailable: Bool) -> Void)) {
client.sendTwitterRequest(request) { (response, data, connectionError)-> Void in {
do {
let json = try JSONSerialization.jsonObject(with: data!, options: [])
print("json: \(json)")
callback(true)
} catch let jsonError as NSError {
print("json error: \(jsonError.localizedDescription)")
callback(false)
}
}
}
jsonAvailable(callback: { (_ isJsonAvailable: Bool) in
print(isJsonAvailable)
})

Validate JSON response with Alamofire

Is it possible to have a Alamofire validator that gets the parsed JSON response, check a property and return true / false depending on that value?
I have an API that always returns 200 response codes, but the response has a success property.
I would like to check this property before the responseJSON callback is fired and only call responseJSON if success == true.
Is this possible with custom validators?
Found a solution I feel ok with. First I created extension methods that check for errors and extract the data I'm interested in. I have one success callback and one error callback.
import Foundation
import Alamofire
extension Request {
public func apiSuccess(
queue queue: dispatch_queue_t? = nil,
options: NSJSONReadingOptions = .AllowFragments,
completionHandler: [String:AnyObject] -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: Request.JSONResponseSerializer(options: options),
completionHandler: { response in
if let jsonValue = response.result.value as? [String:AnyObject] {
let success = jsonValue["success"] as! Bool
if (success) {
completionHandler(jsonValue["object"] as! [String:AnyObject])
}
}
}
)
}
public func apiError(
queue queue: dispatch_queue_t? = nil,
options: NSJSONReadingOptions = .AllowFragments,
completionHandler: [String] -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: Request.JSONResponseSerializer(options: options),
completionHandler: { response in
if let jsonValue = response.result.value as? [String:AnyObject] {
let success = jsonValue["success"] as! Bool
if (!success) {
let errorDict = jsonValue["errors"] as! [String:[String]]
var errors : [String] = []
errorDict.keys.forEach { key in
errors += errorDict[key] as [String]!
}
completionHandler(errors)
}
}
}
)
}
}
Then I can use it like this:
Alamofire.request(.POST, url,
parameters: parameters,
encoding: .JSON)
.apiSuccess { response in
print("Success Callback", response)
}
.apiError { errors in
print("Errors ", errors)
}
I don't think it is. Validator blocks don't receive the response data as arguments, only headers and such.

unexpectedly found nil while unwrapping an Optional value on return

I am calling Url which will give me Json in get() function.
I am calling get() function from another class and try to return result of Json in Array format. but it shows Found null error on return statement . when I tried to print values of Json it writing correctly.
This is my code in swift.
func get() -> NSArray
{
let postEndpoint: String = "Link_For_JSON_Data"
let session = NSURLSession.sharedSession()
let url = NSURL(string: postEndpoint)!
var jsonArray : NSArray?
var jsonArray1 : NSArray?
session.dataTaskWithURL(url, completionHandler: { ( data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
// Make sure we get an OK response
guard let realResponse = response as? NSHTTPURLResponse where
realResponse.statusCode == 200 else
{
print("Not a 200 response")
return
}
// Read the JSON
do
{
if let contentString = NSString(data:data!, encoding: NSUTF8StringEncoding)
{
// Print what we got from the call
jsonArray = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSArray
print("jsonArray here", jsonArray)
// Update the label
dispatch_async(dispatch_get_main_queue())
{ () -> Void in
self.getDataFormREST(jsonArray!)
}
}
}
catch
{
print("bad things happened")
}
}).resume()
return jsonArray!
}
func getDataFormREST(resultArray: NSArray) //-> NSArray
{
// let resultDictionary = resultArray[(searchDetails)!-1] as! NSDictionary
testArray = resultArray
print("TESTArray ON ",testArray)
}
You can't write a function that does an async call and then returns the results as the function result. That's not how async code works. Your function queues up the async dataTaskWithURL request, and then returns before it has even had a chance to send the request, much less receive the results.
You have to rewrite your get() function to be a void function (no result returned) but take a completion block. Then, in your data task's completion handler you get the data from the jsonArray and call the get() function's completion block, passing it the jsonArray.
See this project I posted on Github that illustrates what I'm talking about:
SwiftCompletionHandlers on Github

Use Type T as parameter in completion handler

I have written a function for a URL request. This contains a completion handler that returns a dictionary of [String: AnyObject] that is fetched from the URL.
The code for this is:
func getDataAsyncFromURLRequest(url: NSURL, completion: ([String : AnyObject]) -> ()) {
let request = NSMutableURLRequest(URL: url)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error != nil {
print("error=\(error)")
return
}
else {
let datastring = NSString(data: data!, encoding: NSUTF8StringEncoding)
if let data = datastring!.dataUsingEncoding(NSUTF8StringEncoding) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as! [String : AnyObject]
completion(json)
} catch {
print("json error: \(error)")
}
}
}
}
task.resume()
}
In some cases, however, I will receive an array of [String : AnyObject] and not the dictionary. So instead of making a duplicate function that takes the array of dictionaries as parameter for the completion handler, I though it was possible to do like this
func getDataAsyncFromURLRequest<T>(url: NSURL, completion: (T) -> ()) {
// code here
}
... and then do like this let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as! T, but that gives me this error: Cannot invoke 'getDataAsyncFromURLRequest' with an argument list of type '(NSURL, completion: (_) -> ())'
What would be the best way to make the completion handler accept a parameter with a type decided at runtime, if possible at all?
It's very easy why don't you use AnyObject
func getDataAsyncFromURLRequest(url: NSURL, completion: (AnyObject) -> ()) {
let request = NSMutableURLRequest(URL: url)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error != nil {
print("error=\(error)")
return
}
else {
let datastring = NSString(data: data!, encoding: NSUTF8StringEncoding)
if let data = datastring!.dataUsingEncoding(NSUTF8StringEncoding) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions())
completion(json)
} catch {
print("json error: \(error)")
}
}
}
}
task.resume()
}
And result of JSONObjectWithData can be [AnyObject] (Array) or [String:AnyObject] and tree of those items.
So after got result, you can also check type of result in completion block
Like this
if result is [String:AnyObject]
...
else if result is [AnyObject]
...
else
//throw error : because then it is not JSON

Resources