iOS: pass data to another class - ios

I have my class named "Service" where inside I do a lot of GET/POST request with Alamofire, an example of request id this
func requestDocuments(){
request(.POST, "http://example.com/json/docs")
.responseJSON { (_, _, JSON, error) in
if error == nil{
var response = JSON as NSArray
println("array document: \(response)")
//**** HERE I WANT PASS VALUE TO MY VIEW CONTROLLER
}
else{
}
}
}
and from my viewcontroller:
let service = Service.sharedInstance
service.requestDocuments()
What can I use? delegate method? or what?
what is the best solution in swift?

func requestDocuments(completion:(data:NSArray?)){
request(.POST, "http://example.com/json/docs")
.responseJSON { (_, _, JSON, error) in
if error == nil{
var response = JSON as NSArray
println("array document: \(response)")
//**** HERE I WANT PASS VALUE TO MY VIEW CONTROLLER
completion(data:response)
}
else{
completion(data:nil)
}
}
}
var reqDoc = requestDocuments(){ (data) -> Void in
if let _data = data {
dispatch_async(dispatch_get_main_queue()) {
//Do something with data
}
}
}
I think closures is the best solution.

Yes. There are 3 main ways to do this. The idea is you want to send off a call to a class, in this case, for networking, and have it come back sometime later and do something.
Delegates+Protocols are fantastic for this:
http://iosdevelopertips.com/objective-c/the-basics-of-protocols-and-delegates.html
As are Blocks
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html
The other popular way is KVO but that is not ideal for your example.

I will use closure in swift,
For example
class Service{
func requestDocuments(completion:(response:AnyObject)->()){
//After network is done
completion(response:data)
}
}
Then here to use
service.requestDocuments { (response) -> () in
//Here you can get response async
}

Use Delegate its the best approach.
Please see below example where delegation approach is demostrated
AFNetworking 2.0 - How to pass response to another class on success
Another approach would be NSNotification

Related

Using a Swift closure to capture a variable

I have this bit of code and it obviously errors out because when I use FOO in the return statement it's outside of the scope of the function. I know (I think I know) I need to use a closure to capture the variable but I can't figure out how to do that. Using Alamofire & SwiftyJSON. Any help would be great! Thanks!
func getPlayerID(named: String) -> String {
Alamofire.request(.GET, "URL", headers: headers)
.responseJSON { response in
let json = JSON.self(response.result.value!)
for var index = 0; index < json.count; index++ {
if json[index]["Name"].stringValue == named {
var FOO = json[index]["FOO"].stringValue
} // If Statement End
} // For Loop End
} // Alamofire Request End
// Return Statement for getPLayerID Function
return FOO
} // getPlayerID Function End
} // Player Struct End
The basic idea is that getPlayerID should not return anything, but rather should just have a parameter which is a closure, and once you retrieve the value you want to "return", you call the closure using that value as a parameter.
But, I'd suggest all sorts of other refinements here:
Build an array of the strings and return that
Check to see if result is a .Failure because you have no control over what various server/network issues may arise
Change the closure to detect and report errors
But hopefully this illustrates the basic idea:
Personally, I'd (a) make the String parameter to the completionHandler optional; (b) add another optional error parameter; and (c) add error handling to the getPlayerID:
func getPlayerID(completionHandler: ([String]?, ErrorType?) -> Void) {
Alamofire.request(.GET, "URL", headers: headers)
.responseJSON { request, response, result in
switch (result) {
case .Success(let value):
let json = JSON.self(value)
// variable to hold all of the results
var strings = [String]()
// populate the array of strings
for var index = 0; index < json.count; index++ {
if json[index]["Name"].stringValue == named {
strings.append(json[index]["FOO"].stringValue)
}
}
// call the completion handler with the strings
completionHandler(strings, nil)
case .Failure(_, let error):
completionHandler(nil, error)
}
}
}
And then, when you want to call it:
getPlayerID() { strings, error in
// use `strings` here
}
// but not here
If you make an asynchronous request you can not return a value received in response in the same function cause it needs time for request to be sent over network to the server and back. The best way to solve this out is to add callback parameter to your function instead of return value.
func getPlayerID(named: String, callback:(foo:String)->()) {
Alamofire.request(.GET, "URL", headers: headers)
.responseJSON { response in
let json = JSON.self(response.result.value!)
for var index = 0; index < json.count; index++ {
if json[index]["Name"].stringValue == named {
var FOO = json[index]["FOO"].stringValue
callback(foo: FOO) // you fire callback here..
} // If Statement End
} // For Loop End
} // Alamofire Request End
} // getPlayerID Function End
Callback is a block object that will be fired when your response will be received. So if response is not coming (for example, internet connection went down) callback will never fired.
Example how to use this:
self.getPlayerID("ototo") { (foo) -> () in
print("foo received = \(foo)")
}
Also there is a time span between sending the request and receiving the response. So it is a good practice to add UIActivityIndicatorView in UI of your app until response is arrived (and handle timeout if internet connection suddenly went down).

Swift dispatch_async from function

How can i wait until function get all data from alamofire get request?
GetData.swift file:
import Foundation
import Alamofire
import SwiftyJSON
import ObjectMapper
func getStartData() -> Void {
let sharedBranch = BranchSingleton.sharedInstance
let sharedArticle = ArticleSingleton.sharedInstance
Alamofire.request(.GET, Config().apiBranch)
.responseJSON { request, response, result in
let jsonObj = SwiftyJSON.JSON(result.value!)
for obj in jsonObj {
let branch = Mapper<Branch>().map(obj.1.rawString()!)
sharedBranch.addBranch(branch!)
}
}
Alamofire.request(.GET, Config().apiArticle)
.responseJSON { request, response, result in
let jsonObj = SwiftyJSON.JSON(result.value!)
for obj in jsonObj {
let article = Mapper<Article>().map(obj.1.rawString()!)
sharedArticle.addArticle(article!)
}
}
}
ViewController.swift file:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
getStartData() // need to wait until all requests are finished then do print
print(sharedArticle.articleList)
}
}
SingletonObj.swift file:
import Foundation
class BranchSingleton {
var branchList: [Branch] = []
class var sharedInstance: BranchSingleton {
struct Static {
static let instance: BranchSingleton = BranchSingleton()
}
return Static.instance
}
func addBranch(branch: Branch) {
branchList.append(branch)
}
}
class ArticleSingleton {
var articleList: [Article] = []
class var sharedInstance: ArticleSingleton {
struct Static {
static let instance: ArticleSingleton = ArticleSingleton()
}
return Static.instance
}
func addArticle(article: Article) {
articleList.append(article)
}
}
i need to wait until getStartData() finish, then pring singleton array..
How can i do that?
This getStartData contains more than 2 requests, but i just gave example with 2..
You're asking a non-question. There is no reason to "wait". Nor can you. You just do what you do, asynchronously. Meanwhile the interface must stay active; the user must be able to continue to work. Thus there is nothing to "wait" for.
Now, if the question is, how can you send a signal in some elegant way to the rest of your app when all of the requests are done, one good answer is to use NSProgress. All the different requests can contribute to a common NSProgress object. The nice thing is that its fractionCompleted is observable with KVO, so when it comes greater-than-or-equal-to 1.0, you're done.
But you don't actually need the NSProgress; you could just increment or decrement an instance variable that's KVO-observable (being careful about threading, of course). If you know there are n processes, then you could just start a variable at n and have each process decrement it when it completes; a didSet observer on the variable can then take action when we hit zero.
The point is: you don't "wait": you just have all the different activities contribute to some common central value that "knows" when this means we've "finished" and can then take action.
As #Matt says, you can't, and shouldn't, try to wait until Alamofire is done with your request. That's like hiring somebody to run an errand for so you can work and then stopping everything and sitting by the door until they get back. You might as well have run the errand yourself.
Dropping the analogy, you might as well have performed the task synchronously. However, synchronous networking is a very bad idea. It freezes the UI until the network request is complete, which can be a very long wait if something goes wrong.
An async method like Alamofire's request method takes a completion block, a block of code that should be run when the work is finished.
The request method returns immediately, before the request has even been sent to the server, much less completed.
Instead of waiting around for the request to complete, you should refactor your getStartData method to take a completion handler, and use that to respond once the work is done:
func getStartData(completion: () -> void) -> Void {
let sharedBranch = BranchSingleton.sharedInstance
let sharedArticle = ArticleSingleton.sharedInstance
Alamofire.request(.GET, Config().apiBranch)
.responseJSON { request, response, result in
let jsonObj = SwiftyJSON.JSON(result.value!)
for obj in jsonObj {
let branch = Mapper<Branch>().map(obj.1.rawString()!)
sharedBranch.addBranch(branch!)
}
}
Alamofire.request(.GET, Config().apiArticle)
.responseJSON { request, response, result in
let jsonObj = SwiftyJSON.JSON(result.value!)
for obj in jsonObj {
let article = Mapper<Article>().map(obj.1.rawString()!)
sharedArticle.addArticle(article!)
}
//At this point the Alamofire .GET request for Config().apiArticle
//is complete. Call our completion block (passed in as a parameter)
completion()
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
getStartData()
{
//This is a "trailing closure", a block of code passed to getStartData
print("At this point, we've finished getting our data from Alamofire.")
print(sharedArticle.articleList)
}
}
}
Note that your getStartData method makes 2 Alamofire.request() commands in a row. If the second request requires that the first request be finished then you will need to restructure that code so that the second Alamofire request is inside the completion block for the first call. (That's more editing than I'm in the mood to do at the moment.)

How to mock response from AFNetworking with OCMock in Swift?

This Is how i get the instance of my network client:
let networkClient = DBNetworkClient(baseURL: NSURL(string: "http://mysite.pl/api"))
I also have one method:
func citiesWithParameters(parameters: [String: String], completionBlock: DBSearchOptionHandler) {
GET("cities", parameters: parameters, success: { operation, response in
if let error = NSError(response: response) {
completionBlock([], error)
} else {
let cities = DBCity.parseCitiesWithDictionary(response as! NSDictionary)
completionBlock(cities, nil)
}
}) { operation, error in
completionBlock([], error)
}
}
This is how I call this method:
networkClient.citiesWithParameters(parameters, completionBlock: { cities, error in
//do sth
})
This way I pass some parameters, and get the REAL response from server. I would like to mock THAT response when I ask for this. How to do this?
func testCities() {
let mockNetworkClient = OCMockObject.mockForClass(DBNetworkClient.classForCoder())
//what should I perform here to be able to do sth like this:
let expectation = expectationWithDescription("")
mockNetworkClient.citiesWithParameters(["a": "b"]) { cities, error in
expectation.fulfill()
XCTAssertNotNil(cities)
XCTAssertNil(error) //since I know what is the response, because I mocked this
}
waitForExpectationsWithTimeout(10, handler: nil)
}
And this is how my method GET is defined within DBNetworkClient:
override func GET(URLString: String!, parameters: AnyObject!, success: ((AFHTTPRequestOperation!, AnyObject!) -> Void)!, failure: ((AFHTTPRequestOperation!, NSError!) -> Void)!) -> AFHTTPRequestOperation! {
return super.GET(URLString, parameters: parameters, success: { (operation, response) in
print("GET \(operation.request.URL)")
print("GET \(response)")
success(operation, response)
}, failure: { (operation, error) in
print("GET \(operation.request.URL)")
print("GET \(operation.responseObject)")
failure(operation, error)
})
}
Once I will be able I will award 50 bounty for the one, who help me do this.
Writing mock tests for AFNetworking is unfortunetaly not helpful. It is not working for me.
I do not have experience with Alamofire therefore I don't know where is declared your GET method but you should definitely stub this method instead of citiesWithParameters.
For example if it's declared in your DBNetworkClient:
func testCities()
{
//1
class DBNetworkClientMocked: DBNetworkClient
{
//2
override func GET(URLString: String!, parameters: AnyObject!, success: ((AFHTTPRequestOperation!, AnyObject!) -> Void)!, failure: ((AFHTTPRequestOperation!, NSError!) -> Void)!) -> AFHTTPRequestOperation! {
//3
success(nil, ["San Francisco", "London", "Sofia"])
}
}
//4
let sut = DBNetworkClientMocked()
sut.citiesWithParameters(["a":"b"]) { cities, error in
//5
XCTAssertNotNil(cities)
XCTAssertNil(error)
}
}
So what happens here:
You define class that is children to DBNetworkClient, therefore making a 'Mock' for it as described in article you posted. In that class we will override only methods that we want to change(stub) and leave others unchanged. This was previously done with OCMock and was called Partial Mock.
Now you stub it's GET method in order to return specific data, not actual data from the server
Here you can define what to return your stubbed server. It can be success failure etc.
Now after we are ready with mocks and stubs, we create so called Subject Under Test(sut). Please note that if DBNetworkClient has specific constructor you must call this constructor instead of default one - ().
We execute method that we want to test. Inside it's callback we put all our assertions.
So far so good. However if GET method is part of Alamofire you need to use a technique called Dependency Injection(you could google it for more info).
So if GET is declared inside another class and is only referenced in citiesWithParameters, how we can stub it? Let's look at this:
//1
class DBNetworkClient
{
//2
func citiesWithParameters(parameters: [String: String], networkWorker: Alamofire = Alamofire(), completionBlock: DBSearchOptionHandler) {
//3
networkWorker.GET("cities", parameters: parameters, success: { operation, response in
if let error = NSError(response: response) {
completionBlock([], error)
} else {
let cities = DBCity.parseCitiesWithDictionary(response as! NSDictionary)
completionBlock(cities, nil)
}
}) { operation, error in
completionBlock([], error)
}
}
}
func testCities()
{
//4
class AlamofireMock: Alamofire
{
override func GET(URLString: String!, parameters: AnyObject!, success: ((AFHTTPRequestOperation!, AnyObject!) -> Void)!, failure: ((AFHTTPRequestOperation!, NSError!) -> Void)!) -> AFHTTPRequestOperation! {
success(nil, ["San Francisco", "London", "Sofia"])
}
}
//5
let sut = DBNetworkClient()
sut.citiesWithParameters(["a":"b"], networkWorker: AlamofireMock()) { cities, error in
//6
XCTAssertNotNil(cities)
XCTAssertNil(error)
}
}
First we have to slightly change our citiesWithParameters to receive one more parameter. This parameter is our dependency injection. It is the object that have GET method. In real life example it will be better this to be only protocol as citiesWithParameters doesn't have to know anything more than this object is capable of making requests.
I've set networkWorker parameter a default value, otherwise you need to change all your call to citiesWithParameters to fulfill new parameters requirement.
We leave all the implementation the same, just now we call our injected object GET method
Now back in tests, we will mock Alamofire this time. We made exactly the same thing as in previous example, just this time mocked class is Alamofire instead of DBNetworkClient
When we call citiesWithParameters we pass our mocked object as networkWorker. This way our stubbed GET method will be called and we will get our expected data from 'fake' server.
Our assertions are kept the same
Please note that those two examples do not use OCMock, instead they rely entirely on Swift power! OCMock is a wonderful tool that we used in great dynamic language - Objective-C. However on Swift dynamism and reflection are almost entirely missing. Thats why even on official OCMock page we had following statement:
Will there be a mock framework for Swift written in Swift? Maybe. As
of now it doesn't look too likely, though, because mock frameworks
depend heavily on access to the language runtime, and Swift does not
seem to provide any.
The thing that is missing with in both implementations provided is verifying GET method is called. I'll leave it like this because that was not original question about, and you can easily implement it with a boolean value declared in mocked class.
One more important thing is that I assume GET method in Alamofire is instance method, not a class method. If that's not true you can declare new method inside DBNetworkClient which simply calls Alamofire.GET and use that method inside citiesWithParameters. Then you can stub this method as in first example and everything will be fine.

Error handling in Alamofire

I have the HTTP code in an AngularJS controller:
$http.post('/api/users/authenticate', {email: $scope.email, password: $scope.password})
.success(function (data, status, headers, config) {
authService.login($scope.email);
$state.go('home');
})
.error(function (data, status, headers, config) {
$scope.errorMessages = data;
$scope.password = "";
});
In the success case, the server will respond with a JSON representation of a user. In the error case the server will respond with a simple string such as User not found which can be accessed through the data parameter.
I'm having trouble figuring out how to do something similar in Alamofire. Here's what I have right now:
#IBAction func LoginPressed(sender: AnyObject) {
let params: Dictionary<String,AnyObject> = ["email": emailField.text, "password": passwordField.text]
Alamofire.request(.POST, "http://localhost:3000/api/users/authenticate", parameters: params)
.responseJSON {(request, response, data, error) in
if error == nil {
dispatch_async(dispatch_get_main_queue(), {
let welcome = self.storyboard?.instantiateViewControllerWithIdentifier("login") as UINavigationController;
self.presentViewController(welcome, animated: true, completion: nil);
})
}
else{
dispatch_async(dispatch_get_main_queue(), {
// I want to set the error label to the simple message which I know the server will return
self.errorLabel.text = "something went wrong"
});
}
}
}
I have no idea if I'm handling the non-error case correctly either and would appreciate input on that as well.
You are are on the right track, but you are going to run into some crucial issues with your current implementation. There are some low level Alamofire things that are going to trip you up that I want to help you out with. Here's an alternative version of your code sample that will be much more effective.
#IBAction func loginPressed(sender: AnyObject) {
let params: [String: AnyObject] = ["email": emailField.text, "password": passwordField.text]
let request = Alamofire.request(.POST, "http://localhost:3000/api/users/authenticate", parameters: params)
request.validate()
request.response { [weak self] request, response, data, error in
if let strongSelf = self {
let data = data as? NSData
if data == nil {
println("Why didn't I get any data back?")
strongSelf.errorLabel.text = "something went wrong"
return
} else if let error = error {
let resultText = NSString(data: data!, encoding: NSUTF8StringEncoding)
println(resultText)
strongSelf.errorLabel.text = "something went wrong"
return
}
var serializationError: NSError?
if let json: AnyObject = NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments, error: &serializationError) {
println("JSON: \(json)")
let welcome = self.storyboard?.instantiateViewControllerWithIdentifier("login") as UINavigationController
self.presentViewController(welcome, animated: true, completion: nil)
} else {
println("Failed to serialize json: \(serializationError)")
}
}
}
}
Validation
First off, the validate function on the request will validate the following:
HTTPStatusCode - Has to be 200...299
Content-Type - This header in the response must match the Accept header in the original request
You can find more information about the validation in Alamofire in the README.
Weakify / Strongify
Make sure to weak self and strong self your closure to make sure you don't end up creating a retain cycle.
Dispatch to Main Queue
Your dispatch calls back to the main queue are not necessary. Alamofire guarantees that your completion handler in the response and responseJSON serializers is called on the main queue already. You can actually provide your own dispatch queue to run the serializers on if you wish, but neither your solution or mine are currently doing so making the dispatch calls to the main queue completely unnecessary.
Response Serializer
In your particular case, you don't actually want to use the responseJSON serializer. If you do, you won't end up getting any data back if you don't pass validation. The reason is that the response from the JSON serialization is what will be returned as the AnyObject. If serialization fails, the AnyObject will be nil and you won't be able to read out the data.
Instead, use the response serializer and try to parse the data manually with NSJSONSerialization. If that fails, then you can rely on the good ole NSString(data:encoding:) method to print out the data.
Hopefully this helps shed some light on some fairly complicated ways to get tripped up.
So Alamofire treats all requests successful. This really comes down to the API server http headers being returned.
You could use Alamofire.Request.validate()
It'll allow you to validate http headers, etc. Check out the example
https://github.com/Alamofire/Alamofire#validation
I am assuming the the error message will be in the data object.
to access the values from data you could do something like
I am not really sure about your api response looks but in this example
{
"message": "Could not authenticate"
}
let message: String? = data?.valueForKey("message") as String

handle JSON response with SwiftyJSON

I tried a long time to handle a JSON response with SwiftyJSON, but I don't know how to parse the response. Here's my code:
var jsonString:String = ""
Alamofire
.request(.GET, url + "/HMServer/rest/administration/version")
.responseJSON {
(request, response, data, error) -> Void in
let json = JSON(object: data!)
//here I want to do something with parsing
}
The requests I do with Alamofire and get back a JSON response. The response look like:
[message: [SERVER_VERSION: 0.1, INTERFACE_VERSION: 0.1], type: success]
I want to save all elements in strings and give them back. How can I parse the JSON response saved in the let let json? I tried to use Alamofire-SwiftJSON but the code does not work. All examples I found are too old because the SwiftyJSON code was refactored a few days ago.
THX!
I have fixed Alamofire-SwiftJSON's issue,
but you can do it by yourself in responseJSON's closure like:
Alamofire.request(.GET, url + "/HMServer/rest/administration/version")
.responseJSON { (request, response, data, error) -> Void in
if error != nil {
self.swiftyJSON = SwiftyJSON.JSON.Null(error)
} else if object != nil {
self.swiftyJSON = SwiftyJSON.JSON(object: object!)
} else {
self.swiftyJSON = SwiftyJSON.JSON.Null(nil)
}
}
Above code is not like Alamofire-SwiftJSON in the global queue, the initialization (AnyObject to SwiftyJSON) is running in the main queue.

Resources