AFNetworking and Swift - Save json response - json

I want to make a GET request in swift to get some Json data.
I tried to use AFNetworking and it works, but I don't know how to return the Json I get.
I tried with a return but it's made before the GET so I get nothing...
func makeGet(place:String) -> String
{
var str:String = ""
let manager = AFHTTPRequestOperationManager()
manager.requestSerializer.setValue("608c6c08443c6d933576b90966b727358d0066b4", forHTTPHeaderField: "X-Auth-Token")
manager.GET("http://something.com/api/\(place)",
parameters: nil,
success: { (operation: AFHTTPRequestOperation!,responseObject: AnyObject!) in
str = "JSON: \(responseObject.description)"
println(str) //print the good thing
},
failure: { (operation: AFHTTPRequestOperation!,error: NSError!) in
str = "Error: \(error.localizedDescription)"
})
return str //return ""
}
Can you help me ?

Since you want to return the value after the webservice request is completed you have to pass the data via a delegate or a block(In swift it is called closures)
I see blocks useful here
//Above your class file create a handler alias
typealias SomeHandler = (String! , Bool!) -> Void
func makeGet(place:String , completionHandler: SomeHandler!)
{
var str:String = ""
let manager = AFHTTPRequestOperationManager()
manager.requestSerializer.setValue("608c6c08443c6d933576b90966b727358d0066b4", forHTTPHeaderField: "X-Auth-Token")
manager.GET("http://something.com/api/\(place)",
parameters: nil,
success: { (operation: AFHTTPRequestOperation!,responseObject: AnyObject!) in
str = "JSON: \(responseObject.description)"
println(str) //print the good thing
completionHandler(str,false) //str as response json, false as error value
},
failure: { (operation: AFHTTPRequestOperation!,error: NSError!) in
str = "Error: \(error.localizedDescription)"
completionHandler("Error",true)
})
//return str //return "" You don't want to return anything here
}
When you want to call the method get the values like this
makeGet(){
yourJSONString , errorValue in //Here the value will be passed after you get the response
if !errorValue {
println("The End."
}
}
More about swift closures
FYI: AFNetworking owner has created a new Networking layer for swift and it is called Alamofire (AF from AFNetworking is Alamofire :])

You're not getting a response from that function, because the GET operation happens asynchronously. That is, the order of execution looks like this:
You call makeGet
makeGet creates manager, which fires off a GET request
makeGet finishes executing and returns an empty string
(some time later) manager receives a value back from the server and executes either the success or failure block.
So the only time you have access to the JSON that comes back from the server is in step 4, and you need to find a way of storing that value so you can parse it or use it or whatever. There are a variety of options here -- one is to define closures that call event handlers on your class instance, like this:
class MyClass {
func jsonLoaded(json: String) {
println("JSON: \(json)")
}
func jsonFailed(error: NSError) {
println("Error: \(error.localizedDescription)")
}
func makeGet(place:String) {
let manager = AFHTTPRequestOperationManager()
manager.requestSerializer.setValue("608c6c08443c6d933576b90966b727358d0066b4", forHTTPHeaderField: "X-Auth-Token")
manager.GET("http://something.com/api/\(place)",
parameters: nil,
success: { (operation: AFHTTPRequestOperation!, responseObject: AnyObject!) in
self.jsonLoaded(responseObject.description)
},
failure: { (operation: AFHTTPRequestOperation!, error: NSError!) in
self.jsonFailed(error)
}
)
}
}

let manager = AFHTTPSessionManager()
manager.GET("http://api.androidhive.info/json/movies.json", parameters: nil, success: { (operation, responseObject) -> Void in
print(responseObject)
}, failure: nil)
AFHTTPRequestOperationManager is not available in latest AFnetworking library, it has replaced with AFHTTPSessionManager.
This is a simple example of getting response object.

Related

Parse JSON when I get a error response in Alamofire

I've been working on API requests using Alamofire, and I want to know something for parsing the Error and to get the JSON data from the server instead of accessing the AFErrors. The code below works fine, but since I've been using .responseDecodable for decoding the response and also the MyError inherits Decodable, I was wondering if there is a similar way to decode data when I get the error response back, instead of using JSONDecoder().decode.
When I get the error response, it returns a JSON object with one value in it (message) from the server.
static let sessionManager: Session = {
let configuration = URLSessionConfiguration.af.default
configuration.timeoutIntervalForRequest = 30
return Session(configuration: configuration)
}()
Request.sessionManager.request(endpoint, method: httpMethod, parameters: params, headers: headers)
.responseDecodable(of: resDecodeType.self) { response in
switch response.result {
case .success(let successResponse):
completed(.success(successResponse))
case .failure(let error):
let errorStatusCode = response.response?.statusCode
do {
let data = try JSONDecoder().decode(MyError.self, from: response.data!) // -> this part...
} catch {
print(error)
}
}
}
}
struct MyError: Decodable {
let message: String
}

Swift 3: how to return values out of session.uploadTask

Please can you tell me how to return [data] out of a session.uploadTask in oder to make [data] available for multiple ViewControllers?
My classes:
class data {
var name1: String = ""
var name2: String = ""
...
}
class fetchData {
var url: String = ""
var body: String = ""
...
func getDataFromServer (apiParrameters,...) -> [data] {
var dataArray = [data]()
session.uploadTask() {
// fetching the [data
...
// adding [data]
... dataArray.append(data(name1: name1String, name2: name2String)) ...
//Where to retun(dataArray)? In session.uploadTask it is not possible/accespted
/* in OperationQueue.main.addOperation({
//return(dataArray) is not possible
})*/
}
resume(uploadTask)
//return(dataArray) at this point results in an empty dataArray
}
}
The completion handler to call when the load request is complete. This handler is executed on the delegate queue.This completion handler takes the following parameters:
data
The data returned by the server.
response
An object that provides response metadata, such as HTTP headers and status code. If you are making an HTTP or HTTPS request, the returned object is actually an HTTPURLResponse object.
error
An error object that indicates why the request failed, or nil if the request was successful.

Unit Testing a method that uses REST calls in Swift

Let me start out by stating I'm still unfamiliar with what I'm trying to do, but striving to get better!
I'm working on a project that I'm writing unit tests for and I'm having some trouble with how to approach the problem.
The method I'm testing utilizes a RESTAPI call to verify a users credentials. I'm not sure what the best way to unit test would be.
Here is the method I'm looking to make the Unit test for:
#IBAction func loginBtnActivate(sender: UIButton) {
let enteredEmail: String = emailField.text!
let enteredPassword: String = passwordField.text!
let testInfo:[String: AnyObject] = ["User": enteredEmail, "Password": enteredPassword]
RestApiManager.sharedInstance.postLogin(testInfo) { (json, statusCode) in
if statusCode == 200 {
let AuthToken: TokenObject = (TokenObject(json: json))
try! self.keychain.set(AuthToken.Authorization, key:"Authorization")
try! self.keychain.set(AuthToken.LifeTime, key: "LifeTime")
try! self.keychain.set(AuthToken.UUID, key: "UUID")
NSOperationQueue.mainQueue().addOperationWithBlock {
self.performSegueWithIdentifier("loginToMyHealth", sender: nil)
}
} else if statusCode == 401 {
self.incorrectLoginAlert()
} else if statusCode == 503 {
print("Service Unavailable Please Try Again Later")
}
}
}
This is currently the approach I'm taking:
func testLoginInfoMatchesDataOnServer(){
let enteredEmail: String = "user"
let enteredPassword: String = "password"
let testInfo:[String: AnyObject] = ["User": enteredEmail, "Password": enteredPassword]
RestApiManager.sharedInstance.postLogin(testInfo) { (json, statusCode) in
XCTAssert(statusCode == 200, "statusCode is not matching the server data")
}
I'm simply verifying that the Rest call is successful and that the credentials are matching the JSON. The XCTAssert call doesn't appear to be working correctly. No matter what I put as the first parameter, XCTAssert doesn't affect whether the test is successful or not.
Example, if I put:
XCTAssert(false, "statusCode is not matching the server data")
The test will still pass regardless of what I put. If I place the Assert function outside the brackets then it appears the variable "statusCode" is out of scope so I'm stuck with a
Use of unresolved identifier 'statusCode'.
func testLoginInfoMatchesDataOnServer(){
let enteredEmail: String = "user"
let enteredPassword: String = "password"
let testInfo:[String: AnyObject] = ["User": enteredEmail, "Password": enteredPassword]
RestApiManager.sharedInstance.postLogin(testInfo) { (json, statusCode) in
}
XCTAssert(statusCode == 200, "statusCode is not matching the server data")
}
I was looking at this guide for help.. Would this be a better approach for what I'm trying to do?
http://roadfiresoftware.com/2016/06/how-do-you-unit-test-rest-calls-in-swift/
Again my understanding of some core concepts might be entirely off so I really appreciate your advice here!
Thanks in advance!
Sean W.
First Few problems with your code
function test(){
RestApiManager.sharedInstance.postLogin(testInfo) { (json, statusCode) in
}//PostLogin call ends
XCTAssert(statusCode == 200, "statusCode is not matching the server data") // Here StatusCode is not accessible as outside the block
}// end function
If you want to use Status Code you should do
function test(){
RestApiManager.sharedInstance.postLogin(testInfo) { (json, statusCode) in
XCTAssert(statusCode == 200, "statusCode is not matching the server data") // Here StatusCode is accessible inside the block
}//PostLogin call ends
}// end function
But this will fail since you need to wait for the response. That can be done using
waitForExpectationsWithTimeout(5)
So proper way to call this would be --
function test(){
let URL = NSURL(string: "http://google.com/")!
let expectation = expectationWithDescription("GET \(URL)")
RestApiManager.sharedInstance.postLogin(testInfo) { (json, statusCode) in
XCTAssert(statusCode == 200, "statusCode is not matching the server data") // Here StatusCode is accessible inside the block
expectation.fulfill()
}//PostLogin call ends
waitForExpectationsWithTimeout(5){ error in
if let error = error {
print("Error: \(error.localizedDescription)")
}
}//Wait block end
}// end function
Important lines here are
expectation.fulfill() // tells process is complete
and
waitForExpectationsWithTimeout(5){} //tells wait for 5secs or throw error
For more info

Alamofire POST request with Swift 2

I am trying to make a POST request in Alamofire to return a JSON object. This code worked in Swift 1, but in swift 2 I get this invalid parameter issue:
Tuple types '(NSURLRequest?, NSHTTPURLResponse?, Result<AnyObject>)' (aka '(Optional<NSURLRequest>, Optional<NSHTTPURLResponse>, Result<AnyObject>)') and '(_, _, _, _)' have a different number of elements (3 vs. 4)
It seems like the error parameter was removed, but I am using the error parameter inside the function to check for errors, so how would I do that without an error param?
Here's my code for the POST request:
let response = Alamofire.request(.POST, urlPath, parameters: parameters, encoding: .URL)
.responseJSON { (request, response, data, error) in
if let anError = error
{
// got an error in getting the data, need to handle it
print("error calling POST on /posts")
print(error)
}
else if let data: AnyObject = data
{
// handle the results as JSON, without a bunch of nested if loops
let post = JSON(data)
// to make sure it posted, print the results
print("The post is: " + post.description)
}
}
If you see the documentation in the branch Swift2.0 you can see that the responseJSON function has changed as the error says, it have now three parameters but you can catch the error too, lets take a look:
public func responseJSON(
options options: NSJSONReadingOptions = .AllowFragments,
completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result<AnyObject>) -> Void)
-> Self
{
return response(
responseSerializer: Request.JSONResponseSerializer(options: options),
completionHandler: completionHandler
)
}
Now it returns an enum Result<AnyObject> and according to the doc :
Used to represent whether a request was successful or encountered an error.
- Success: The request and all post processing operations were successful resulting in the serialization of the
provided associated value.
- Failure: The request encountered an error resulting in a failure. The associated values are the original data
provided by the server as well as the error that caused the failure.
And it have inside an property entitled error, with the following doc:
/// Returns the associated error value if the result is a failure, `nil` otherwise.
public var error: ErrorType? {
switch self {
case .Success:
return nil
case .Failure(_, let error):
return error
}
}
Then if you follow this test case inside Alamofire you can see how to get the error properly:
func testThatResponseJSONReturnsSuccessResultWithValidJSON() {
// Given
let URLString = "https://httpbin.org/get"
let expectation = expectationWithDescription("request should succeed")
var request: NSURLRequest?
var response: NSHTTPURLResponse?
var result: Result<AnyObject>!
// When
Alamofire.request(.GET, URLString, parameters: ["foo": "bar"])
.responseJSON { responseRequest, responseResponse, responseResult in
request = responseRequest
response = responseResponse
result = responseResult
expectation.fulfill()
}
waitForExpectationsWithTimeout(defaultTimeout, handler: nil)
// Then
XCTAssertNotNil(request, "request should not be nil")
XCTAssertNotNil(response, "response should not be nil")
XCTAssertTrue(result.isSuccess, "result should be success")
XCTAssertNotNil(result.value, "result value should not be nil")
XCTAssertNil(result.data, "result data should be nil")
XCTAssertTrue(result.error == nil, "result error should be nil")
}
UPDATE:
Alamofire 3.0.0 introduces a Response struct. All response serializers (with the exception of response) return a generic Response struct.
public struct Response<Value, Error: ErrorType> {
/// The URL request sent to the server.
public let request: NSURLRequest?
/// The server's response to the URL request.
public let response: NSHTTPURLResponse?
/// The data returned by the server.
public let data: NSData?
/// The result of response serialization.
public let result: Result<Value, Error>
}
So you can call it like the following way:
Alamofire.request(.GET, "http://httpbin.org/get")
.responseJSON { response in
debugPrint(response)
}
You can read more about the migration process in the Alamofire 3.0 Migration Guide.
I hope this help you.
Have you tried using three parameters in the .responseJSON and inserting a try catch block around the areas you want error logging, check out this link if you need help with swift 2 try catchs

Trouble parsing JSON with Swift using SwiftyJSON

I am new to swift and I am trying to parse some simple JSON data I am retrieving from a private API that I have. I am using the SwiftJSON library.
No matter what I do I cannot assign the variable "videoUploadId" with the value of "video_upload_id" coming from the JSON response. I hope i provided enough info to get some help. Thanks
Here is a segment of the code
let task = session.dataTaskWithRequest(request, completionHandler: { (data : NSData!, response, error : NSError!) -> Void in
if (error == nil) {
// Success
let statusCode = (response as NSHTTPURLResponse).statusCode
println("Status Code: \(statusCode)\r\n")
println("Response: \(response)\r\n")
println("Data: \(data)\r\n")
let dataContent = NSString(data: data!, encoding: NSUTF8StringEncoding)!
println("UTF8 Data: \(dataContent)\r\n")
let json = JSON(dataContent)
if let videoUploadId = json["video_upload_id"].int {
println("Video Upload ID (Dict: int): \(videoUploadId)")
}
else if let videoUploadId = json["video_upload_id"].string {
println("Video Upload ID (Dict: string): \(videoUploadId)")
}
else if let videoUploadId = json[0].int {
println("Video Upload ID (Array: int): \(videoUploadId)")
}
else if let videoUploadId = json[0].string {
println("Video Upload ID (Array: string): \(videoUploadId)")
}
else {
println(json["video_upload_id"].error)
}
}
else {
// Failure
println("URL Session Task Failed: %#", error.localizedDescription);
}
})
task.resume()
This is what I am receiving from my console:
Login Response: HTTP 200
Status Code: 201
Response: { URL: https:////videos/uploads/ } { status code: 201, headers {
Connection = "Keep-alive";
"Content-Length" = 24;
"Content-Type" = "application/json";
Date = "Sun, 25 Jan 2015 01:02:42 GMT";
Location = "https:////videos/uploads/";
Server = "Apache/2.2.15 (CentOS)";
"Set-Cookie" = "session=eyJzZXNzaW9uX3Rva2VuIjp7IiBiIjoiUzAxWGFYRnlVVGM1YjBsa1kxWkJiV2xrYVZwcFZXdDFiR0ZLYW5GQ1VqRjFjbk5GIn19.B6XSMg.HXatQ76ZFaoZEQsnNu1BgsVECKA; HttpOnly; Path=/";
} }
Data: <7b227669 64656f5f 75706c6f 61645f69 64223a20 3736307d>
UTF8 Data: {"video_upload_id": 760}
Optional(Error Domain=SwiftyJSONErrorDomain Code=901 "Dictionary["video_upload_id"] failure, It is not an dictionary" UserInfo=0x170238620 {NSLocalizedDescription=Dictionary["video_upload_id"] failure, It is not an dictionary})
As you can see from the code and console output, I am attempting to set the variable in several different ways all of which seem to fail. I am receiving the error "Dictionary["video_upload_id"] failure, It is not an dictionary" I even tried prepending "[" and appending "]" to try to see if its a formatting issue.
Any clues?
You are doing the initialization wrong. You should use:
let json = JSON(data:data) // data is NSData!
Converting NSData to NSString is not necessary, and somehow wrong for this. SwiftyJSON can only be initialized with NSData or Swift object.