Using a Swift closure to capture a variable - ios

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).

Related

Return a value from a Alamofire closure

i have this function called sendRequest in a class called A and this sendRequest function returns a Any object and in first line i created a variable like this var serverResponse = JSON() and i have this code to request
upload.uploadProgress(closure: { (Progress) in
})
upload.responseJSON { response in
print(response.response?.statusCode as Any)
if(response.result.isSuccess){
serverResponse = JSON(response.result.value!)
print("Success\(serverResponse)")
}else{
serverResponse = JSON(response.result.value!)
print("No Success\(serverResponse)")
}
}
the above code is inside the sendRequest function and at the end/outside of this closure i have a return statement returning the server response return serverResponse
and i am accessing this function from class B i created an object of class A now i have this code in class B
var response: Any = request.sendRequest("url", parameters: body, headers: [:])
now the problem is the response variable here will always have an empty JSON object and i think thats because in the closure the api call is being processed in the background so before the result comes from the server the return statement gets executed giving me an empty JSON object on class B
and i tried returning the serverResponse in the if statement where i checked if its successful or not and gives me an error like this:
Unexpected non-void return value in void function
so how can i solve this problem?
You are correct. It is being returned empty because it is being run on the background thread. When making network requests we therefor tend to use completionBlocks Swift 5 implemented the new typ of Result<Any, Error> which is really convenient.
Try implementing completion((Result<Any, Error>) -> ()) in your function params instead. When you get the response you unwrap it my writing:
switch result {
case .succeses(let data):
//Do something
break
case .failure(let error):
//Do something
break
}
As you are inside the actual block you can't execute a return statement. That's the error you are getting. The block itself doesn't ask for a return. Unlike map, filter, reduce etc.

How to handle the response of all types of requests in one handler, but also uniquely handle every request with Alamofire and Moya

In my app I use Moya and Alamofire (And also Moya/RxSwift and Moya-ObjectMapper) libraries for all network requests and responses.
I would like to handle the response of all types of requests in one handler, but also uniquely handle every request.
For example for any request I can get the response "Not valid Version", I would like to avoid to check in every response if this error arrived.
Is there an elegant way to handle this use case with Moya?
Apparently that is very simple, You just should create your own plugin. And add it to your Provider instance (You can add it in the init function)
For example:
struct NetworkErrorsPlugin: PluginType {
/// Called immediately before a request is sent over the network (or stubbed).
func willSendRequest(request: RequestType, target: TargetType) { }
/// Called after a response has been received, but before the MoyaProvider has invoked its completion handler.
func didReceiveResponse(result: Result<Moya.Response, Moya.Error>, target: TargetType) {
let responseJSON: AnyObject
if let response = result.value {
do {
responseJSON = try response.mapJSON()
if let response = Mapper<GeneralServerResponse>().map(responseJSON) {
switch response.status {
case .Failure(let cause):
if cause == "Not valid Version" {
print("Version Error")
}
default:
break
}
}
} catch {
print("Falure to prase json response")
}
} else {
print("Network Error = \(result.error)")
}
}
}
I suggest to use generic parametrized method.
class DefaultNetworkPerformer {
private var provider: RxMoyaProvider<GitHubApi> = RxMoyaProvider<GitHubApi>()
func performRequest<T:Mappable>(_ request: GitHubApi) -> Observable<T> {
return provider.request(request).mapObject(T.self)
}
}
DefaultNetworkPerformer will handle all requests from you Moya TargetType. In my case it was GitHubApi. Example usage of this implementation is:
var networkPerformer = DefaultNetworkPerformer()
let observable: Observable<User> = networkPerformer.performRequest(GitHubApi.user(username: "testUser"))
here you 'inform' network performer that response will contain User object.
observable.subscribe {
event in
switch event {
case .next(let user):
//if mapping will succeed here you'll get an Mapped Object. In my case it was User that conforms to Mappable protocol
break
case .error(let error):
//here you'll get MoyaError if something went wrong
break
case .completed:
break
}
}

Completion Handler - Parse + Swift

I'm trying to generate an array of PFObjects called 'areaList'. I've been researching this quite a bit and understand that I could benefit from using a completion handler to handle the asynchronous nature of the loaded results. My ask, specifically, is to get some guidance on what I'm doing wrong as well as potential tips on how to achieve the result "better".
Here is my query function with completion handler:
func loadAreasNew(completion: (result: Bool) -> ()) -> [Area] {
var areaList = self.areaList
let areaQuery = PFQuery(className: "Area")
areaQuery.findObjectsInBackgroundWithBlock {
(areas: [PFObject]?, error: NSError?) -> Void in
if error == nil {
for area in areas! {
let areaToAdd = area as! Area
areaList.append(areaToAdd)
// print(areaList) // this prints the list each time
// print(areaToAdd) // this prints the converted Area in the iteration
// print(area) // this prints the PFObject in the iteration
if areaList.count == areas!.count {
completion(result: true)
} else {
completion(result: false)
}
}
} else {
print("There was an error")
}
}
return areaList
}
Here is how I'm attempting to call it in viewDidLoad:
loadAreasNew { (result) -> () in
if (result == true) {
print(self.areaList)
} else {
print("Didn't Work")
}
}
I assigned this variable before viewDidLoad:
var areaList = [Area]()
In the console, I get the following:
Didn't Work
Didn't Work
Didn't Work
Didn't Work
[]
Representing the 5 items that I know are there in Parse...
This is an interesting question. First off, PFQuery basically has a built in completion handler, which is quiet nice! As you probably know, all of the code within the areaQuery.findObjectsInBackgroundWithBlock {...} triggers AFTER the server response. A completion most often serves the purpose of creating a block, with the ability of asynchronously returning data and errors.
Best practice would (IMO) to just call the code that you want to use with the results from your PFQuery right after your area appending loop (which I'm gonna take out because I'm picky like that), like so:
func loadAreasNew() {
var areaList = self.areaList
let areaQuery = PFQuery(className: "Area")
areaQuery.findObjectsInBackgroundWithBlock {
(areas: [PFObject]?, error: NSError?) -> Void in
if error == nil {
let areasFormatted = areas! As [Areas]
areasList += areasFormatted
//Something like this
self.codeINeedAreasFor(areasList)
}
} else {
print(error)
}
}
}
HOWEVER! If you really feel the need to use some completion handlers, check out this other answer for more info on how to use them. But keep in mind all tools have a time and a place...
There are a few issues here.
Your completion handler doesn't require you to define the name for your completion handler's parameters, so you could easily use completion: (Bool) -> ()
Further in your function, you're returning areaList. This should be put through the completion handler like this onComplete(areaList) and change your completion handler parameter to expect your area list.
Then, when you call your function, it could look more like this :
loadAreasNew { result in
if (result == true) {
print(self.areaList)
} else {
print("Didn't Work")
}
}
Here is my concern:
1) Don't pass in a local variable and make the function return it, it's meaningless and danger.
You may want to initiate an empty array and make your fetch, then "return" it.
2) The fetch request is processed in background, you will have no idea when it will have finished. If you return the array immediately it will always be an empty array.
Put the "return" in your completion too.
3) Parse already has a distance checking method, you don't have to do it manually. aPARSEQUERRY.where(key:,nearGeoPoint:,inKilometers:)
I will rewrite the function as:
func loadNewAreas(completion:([Area],err?)->()){
let areaQuery = PFQuery(className: "Area")
areaQuery.where("location",nearGeoPoint:MYCURRENTLOCATION,inKilometers:50)
areaQuery.findObjectInBackgroundWithBlock(){objects,err
if objects.count == 0
{
completion([],err)
}
let areas = Area.areasFromPFObjects(objects)
completion(areas,err)
}
}

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.)

iOS: pass data to another class

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

Resources