Return Bool in Alamofire closure - ios

I use Swift 2 and Xcode 7.1.
I have a function who connect my users, but it will connect at my database with HTTP. I use Alamofire for execute this request. I want to know, from a view controller if the user is connected.
I have my function connect in a class. And i test connection in a ViewController.
Like this :
class user {
// ...
func connectUser(username: String, password: String){
let urlHost = "http://localhost:8888/project350705/web/app_dev.php/API/connect/"
let parametersSymfonyG = [
username, password
]
let url = UrlConstruct(urlHost: urlHost).setSymfonyParam(parametersSymfonyG).getUrl()
//var userArray = [String:AnyObject]()
Alamofire.request(.GET, url)
.responseString { response in
if let JSON = response.result.value {
var result = self.convertStringToDictionary(JSON)!
if result["status"] as! String == "success"{
let userArray = result["user"] as! [String:AnyObject]
userConnect = self.saveUser(userArray)
} else{
print("ERROR-CONNECTION :\n Status :\(result["status"]!)\n Code :\(result["code"]!)")
}
return ""
}
}
}
// ...
}
class MyViewController: UIViewController {
// ...
#IBAction func connect(sender: AnyObject?) {
// CONNECTION
User.connectUser(self.username.text!, password: self.password.text!)
// CHECK
if userConnect != nil {
print("connected")
}else{
print("NotConnected")
}
}
// ...
}
First solution : Return
To do so would require that my function returns a Boolean.
Only I can not use return.
Alamofire.request(.GET, url)
.responseString { response in
if let JSON = response.result.value {
var result = self.convertStringToDictionary(JSON)!
if result["status"] as! String == "success"{
let userArray = result["user"] as! [String:AnyObject]
userConnect = self.saveUser(userArray)
} else{
print("ERROR-CONNECTION :\n Status :\(result["status"]!)\n Code :\(result["code"]!)")
}
return "" // Unexpected non-void return value in void function
}
}
Second solution :
I can also test if the user has been logged, but before testing, I must wait for the function have finished loading.
users.connectUser(self.username.text!, password: self.password.text!)
// after
if userConnect != nil {
print("connected")
}else{
print("NotConnected")
}
I would prefer return a boolean. It will facilitate the processing.
Do you have a solution ?

I would suggest employing a completion handler in your connectUser method:
func connectUser(username: String, password: String, completion: #escaping (Bool) -> Void) {
// build the URL
// now perform request
Alamofire.request(url)
.responseString { response in
if let json = response.result.value, let result = self.convertStringToDictionary(json) {
completion(result["status"] as? String == "success")
} else {
completion(false)
}
}
}
You can then call it using:
users.connectUser(username.text!, password: password.text!) { success in
if success {
print("successful")
} else {
print("not successful")
}
}
// But don't use `success` here yet, because the above runs asynchronously
BTW, if your server is really generating JSON, you might use responseJSON rather than responseString, further streamlining the code and eliminating the need for convertStringToDictionary:
func connectUser(username: String, password: String, completion: #escaping (Bool) -> Void) {
// build the URL
// now perform request
Alamofire.request(url)
.responseJSON { response in
if let dictionary = response.result.value as? [String: Any], let status = dictionary["status"] as? String {
completion(status == "success")
} else {
completion(false)
}
}
}
If you've written your own server code to authenticate the user, just make sure you set the right header (because responseJSON not only does the JSON parsing for you, but as part of its validation process, it makes sure that the header specifies JSON body; it's good practice to set the header, regardless). For example in PHP, before you echo the JSON, set the header like so:
header("Content-Type: application/json");

The completion handler of your Alamofire.request method is asynchronous and it doesn't have a return type specified in its signature. Thats why you see an error when you provide a return statement in your completion handler closure.
You will have to split your request and response processing to separate methods and call the response processing method instead of using return statement.
Alamofire.request(.GET, url).responseString { response in
if let JSON = response.result.value {
var result = self.convertStringToDictionary(JSON)!
if result["status"] as! String == "success"{
let userArray = result["user"] as! [String:AnyObject]
userConnect = self.saveUser(userArray)
processSuccessResponse() //Pass any parameter if needed
} else{
print("ERROR-CONNECTION :\n Status :\(result["status"]!)\n Code :\(result["code"]!)")
processFailureResponse() //Pass any parameter if needed
}
}
}
func processSuccessResponse() {
//Process code for success
}
func processFailureResponse() {
//Process code for failure
}

My preferred way of doing this is to call a function in the completion handler. You can also set a boolean flag in order to check if the user is connected at any given time.
func connectUser(username: String, password: String, ref: MyClass) {
Alamofire.request(.GET, url)
.responseString { response in
var userIsConnected = false
if let JSON = response.result.value {
var result = self.convertStringToDictionary(JSON)!
if result["status"] as! String == "success"{
let userArray = result["user"] as! [String:AnyObject]
userConnect = self.saveUser(userArray)
userIsConnected = true
} else {
print("ERROR-CONNECTION :\n Status :\(result["status"]!)\n Code :\(result["code"]!)")
}
} else {
print("Response result nil")
}
ref.finishedConnecting(userIsConnected)
}
}
}
class MyClass {
var userIsConnected = false
func startConnecting() {
connectUser(username, password: password, ref: self)
}
func finishedConnecting(success: Bool) {
userIsConnected = success
... post-connection code here
}
}

Related

How to reflect data from the Get Request of Alamofire going to the UI View Controller

How can I reflect the data from the JSON Dictionary using Alamofire to perform validation to check whether the passcode entered is valid or not to my UI View Controller. I already got a Successful result in getting data from the API but I don't know how can my view controller read the values from the API to validate the passcode entered. Please help me.......
APIService
class APIService
{
let eventAPIKey: String
let eventBaseURL: URL?
//static let kEventID = "id"
init(APIKey: String)
{
self.eventAPIKey = APIKey
eventBaseURL = URL(string: BASE_URL)
}
func validatePasscode(passcode: String, completion: #escaping (Event?) -> Void)
{
if let passcodeURL = URL (string: "\(PASSCODE_CHECKER_URL)/\(passcode)") {
Alamofire.request(passcodeURL, method: .get).responseJSON { (response) in
switch response.result{
case .success:
if let passcodeJSON = response.result.value {
print(passcodeJSON)
completion(passcodeJSON as? Event)
}
case .failure(let error):
print("\(error)")
}
}
}
}
}
ViewController
func validateEventPasscode(){
let api = APIService(APIKey: passcode)
api.validatePasscode(passcode: passcode) { (event) in
if let eventDetails = self.event {
self.event = eventDetails
self.view.squareLoading.stop(0.0)
self.performSegue(withIdentifier: "showEventDashboard", sender: self)
self.enteredEventCode.text = ""
}

ios - Cannot get data from Alamofire return

I want to get the data from server api using Alamofire call. But after the api function executed, the data return is empty because Swift is asynchronous...
This code is call the server api:
func getAllModels(completion: #escaping (_ result: [String]?) -> ()) {
var _modelList:[String] = []
let url = BASE_URL + "getAllProductAndModelv2"
Alamofire.request(url, method:.get, parameters: [:], encoding: JSONEncoding.default).responseJSON { response in
switch response.result {
case .success:
if((response.result.value) != nil) {
let data = NSData(contentsOf: URL(string: url)!)
do {
if let data = data, let json = try JSONSerialization.jsonObject(with: data as Data) as? [String: Any], let models = json["models"] as? [[String:Any]] {
for model in models {
if let name = model["name"] as? String {
_modelList.append(name)
}
}
}
}catch {
print("error")
}
completion(_modelList)
}
case .failure(let error):
print(error)
completion(nil)
}
}
}
And this code is get data from getAllModels function:
var models:[VirtualObject] = []
RestApiManager().getAllModels(){ (result) -> () in
print("************************************************")
if let result = result {
var array = result as Array
for item in array {
print(item)
models.append(VirtualObject(url: URL(string: item)!)!)
}
}
print("************************************************")
}
print(models)
return models
I don't know how to use the callback function exactly to bind the data to return model.. Please help!
Use didSet observer of variables. And call api in viewDidload.
class ViewController: UIViewController {
var arrModals = [Any]() {
didSet {
print("this call when get all modals from server")
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
RestApiManager().getAllModels(){ (result) -> () in
var arrTempModals = [Any]()
if let result = result {
var array = result as Array
for item in array {
print(item)
arrTempModals.append(item)
}
}
self.arrModals = arrTempModals
}
}
}

Returning a Promise from PromiseKit/Alamofire

I'm trying to do this seemingly trivial thing:
static func list() -> Promise<[Activity]> {
let endpoint = "\(self.baseUrl)/v1/activities"
return Promise { fulfill, reject in
self.fetchHeaders { (headers) in
return Alamofire.request(
endpoint,
method: .get,
parameters: nil,
encoding: JSONEncoding.default,
headers: headers
).validate().responseJSON().then() { response in
guard let json = response as? JSON else {
reject(ActivityError.parse("Malformed JSON"))
}
guard let jsonActivities = json["activities"] as? [JSON] else {
reject(ActivityError.parse("Missing field"))
}
var activities: [Activity] = []
for jsonActivity in jsonActivities {
guard let activity = Activity(json: jsonActivity) else {
reject(ActivityError.parse("Unable to parse an Activity object"))
}
activities.append(activity)
}
fulfill(activities)
}.catch { error in
reject(ActivityError.network("HTTP response failure"))
}
}
}
}
However, the compiler (rightfully) complains that:
'guard' body may not fall through, consider using 'return' or 'break'
to exit the scope
I understand I need to return a Promise here. I just can't figure out what exactly to put below the reject() and fulfill() calls.
There's nothing wrong with reject or fulfill calls. The issue is that after you reject in your guard statement, you also have to return to exit the closure:
guard let json = response as? JSON else {
reject(ActivityError.parse("Malformed JSON"))
return
}
guard let jsonActivities = json["activities"] as? [JSON] else {
reject(ActivityError.parse("Missing field"))
return
}
The key point is that you do not want to conflate the promise that is returned by this method (which is later satisfied by fulfill or reject), with the fact that within this closure, you have to immediately exit the closure with a return in the guard clause.
I cannot reproduce this issue with your code (because you haven't provided a MCVE and there are references here that I cannot resolve). But here is a simplified rendition of your code illustrating the use of guard:
So, if not using PromiseKit/Alamofire, you can do:
func list() -> Promise<[String: Any]> {
return Promise { fulfill, reject in
Alamofire.request(url)
.validate()
.responseJSON { response in
switch response.result {
case .success(let json):
guard let dictionary = json as? [String: Any] else {
reject(ActivityError.malformed("not a dictionary"))
return
}
fulfill(dictionary)
case .failure(let error):
reject(error)
}
}
}
}
As you can see, you are returning a Promise, but inside the Alamofire closure, you simply are exiting your guard statement.
If you're using PromiseKit/Alamofire and call then, you presumably want to create a promise that it can return, such as:
func list() -> Promise<String> {
return Alamofire.request(endPoint)
.validate()
.responseJSON()
.then { value in
return Promise { fulfill, reject in
guard let dictionary = value as? [String: Any], let name = dictionary["name"] as? String else {
reject(ActivityError.malformed("not dictionary"))
return
}
fulfill(name)
}
}
}
Or if that's too hairy, you can pull out that parsing of the value:
func list() -> Promise<String> {
return Alamofire.request(endPoint)
.validate()
.responseJSON()
.then { value in
self.parse(value)
}
}
func parse(_ value: Any) -> Promise<String> {
return Promise { fulfill, reject in
guard let dictionary = value as? [String: Any], let name = dictionary["name"] as? String else {
reject(ActivityError.malformed("not dictionary"))
return
}
fulfill(name)
}
}
But, either way, even when using PromiseKit/Alamofire, you still just return within the guard clause.

Deserialize a JSON array to a Swift array of objects

I am new to Swift, and am not able to figure out how to deserialize a JSON array to an array of Swift objects. I'm able to deserialize a single JSON user to a Swift user object fine, but just not sure how to do it with a JSON array of users.
Here is my User.swift class:
class User {
var id: Int
var firstName: String?
var lastName: String?
var email: String
var password: String?
init (){
id = 0
email = ""
}
init(user: NSDictionary) {
id = (user["id"] as? Int)!
email = (user["email"] as? String)!
if let firstName = user["first_name"] {
self.firstName = firstName as? String
}
if let lastName = user["last_name"] {
self.lastName = lastName as? String
}
if let password = user["password"] {
self.password = password as? String
}
}
}
Here's the class where I'm trying to deserialize the JSON:
//single user works.
Alamofire.request(.GET, muURL/user)
.responseJSON { response in
if let user = response.result.value {
var swiftUser = User(user: user as! NSDictionary)
}
}
//array of users -- not sure how to do it. Do I need to loop?
Alamofire.request(.GET, muURL/users)
.responseJSON { response in
if let users = response.result.value {
var swiftUsers = //how to get [swiftUsers]?
}
}
The best approach is the use Generic Response Object Serialization provided by Alamofire here is an example :
1) Add the extension in your API Manager or on a separate file
public protocol ResponseObjectSerializable {
init?(response: NSHTTPURLResponse, representation: AnyObject)
}
extension Request {
public func responseObject<T: ResponseObjectSerializable>(completionHandler: Response<T, NSError> -> Void) -> Self {
let responseSerializer = ResponseSerializer<T, NSError> { request, response, data, error in
guard error == nil else { return .Failure(error!) }
let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let result = JSONResponseSerializer.serializeResponse(request, response, data, error)
switch result {
case .Success(let value):
if let
response = response,
responseObject = T(response: response, representation: value)
{
return .Success(responseObject)
} else {
let failureReason = "JSON could not be serialized into response object: \(value)"
let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
case .Failure(let error):
return .Failure(error)
}
}
return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
public protocol ResponseCollectionSerializable {
static func collection(response response: NSHTTPURLResponse, representation: AnyObject) -> [Self]
}
extension Alamofire.Request {
public func responseCollection<T: ResponseCollectionSerializable>(completionHandler: Response<[T], NSError> -> Void) -> Self {
let responseSerializer = ResponseSerializer<[T], NSError> { request, response, data, error in
guard error == nil else { return .Failure(error!) }
let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let result = JSONSerializer.serializeResponse(request, response, data, error)
switch result {
case .Success(let value):
if let response = response {
return .Success(T.collection(response: response, representation: value))
} else {
let failureReason = "Response collection could not be serialized due to nil response"
let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
case .Failure(let error):
return .Failure(error)
}
}
return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
2) update your model object like this:
final class User: ResponseObjectSerializable, ResponseCollectionSerializable {
let username: String
let name: String
init?(response: NSHTTPURLResponse, representation: AnyObject) {
self.username = response.URL!.lastPathComponent!
self.name = representation.valueForKeyPath("name") as! String
}
static func collection(response response: NSHTTPURLResponse, representation: AnyObject) -> [User] {
var users: [User] = []
if let representation = representation as? [[String: AnyObject]] {
for userRepresentation in representation {
if let user = User(response: response, representation: userRepresentation) {
users.append(user)
}
}
}
return users
}
}
3) then you can use it like that :
Alamofire.request(.GET, "http://example.com/users")
.responseCollection { (response: Response<[User], NSError>) in
debugPrint(response)
}
Source: Generic Response Object Serialization
Useful Link: Alamofire JSON Serialization of Objects and Collections
Since you are using Alamofire to make your requests why don't you give a chance to Hearst-DD ObjectMapper it has an Alamofire extension AlamofireObjectMapper. I think it'll save you time!
I would loop through them then add each user to an array (preferably a property of the VC and not an instance variable) but here is an example.
Alamofire.request(.GET, "YourURL/users")
.responseJSON { response in
if let users = response.result.value {
for user in users {
var swiftUser = User(user: user as! NSDictionary)
//should ideally be a property of the VC
var userArray : [User]
userArray.append(swiftUser)
}
}
}
You could also try EVReflection https://github.com/evermeer/EVReflection
It's even more simple, i.e. to parse JSON (code snippet taken from EVReflection link):
let json:String = "{
\"id\": 24,
\"name\": \"Bob Jefferson\",
\"friends\": [{
\"id\": 29,
\"name\":
\"Jen Jackson\"}]}"
you can use this class:
class User: EVObject {
var id: Int = 0
var name: String = ""
var friends: [User]? = []
}
in this way:
let user = User(json: json)

Functions not running in order

In viewdidload i have below methods:
var savedSummonerID : Int?
savedSummonerID = LeagueMethodHelper.retrieveSummonerIDFromSummonerName(serverNameAbbreviation, summonerName : summonerName)
print("haha \(self.savedSummonerID)")
I expect to run methods in order but print statement is actually getting called first.
retrieveSummonerIDFromSummonerName is described below:
static func retrieveSummonerIDFromSummonerName(serverName : String, summonerName : String) -> Int {
var savedSummonerID = 0
Alamofire.request(.GET, "https://\(serverName).api.pvp.net/api/lol/\(serverName)/v1.4/summoner/by-name/\(summonerName)?api_key=(key)")
.responseJSON { response in
print(response.response?.statusCode) // URL response
if let JSON = response.result.value {
if let summonerJSONInfo = JSON[summonerName.lowercaseString] as? [String:AnyObject] {
if let summonerID = summonerJSONInfo["id"] as? Int {
savedSummonerID = summonerID
print(summonerID)
}
if let SummonerName = summonerJSONInfo["name"] as? String {
print(SummonerName)
}
}
}
}
return savedSummonerID
}
I think the solution to run functions in order would be making above function into a closure but I'm not sure how I can do it.
You can not return from an asynchronous task.
Your Alamofire task is executed in the background, and you are returning a default value, that is why it looks like it is skipped - but it's just launched in the background and the result is ignored.
The solution is to use a "completion handler" (a callback) instead of a return.
Example:
// (id: Int)->() is the completion handler signature that we add to your method parameters
static func retrieveSummonerIDFromSummonerName(serverName : String, summonerName : String, completion:(id: Int)->()) {
Alamofire.request(.GET, "https://\(serverName).api.pvp.net/api/lol/\(serverName)/v1.4/summoner/by-name/\(summonerName)?api_key=xxx")
.responseJSON { response in
print(response.response?.statusCode) // URL response
if let JSON = response.result.value {
if let summonerJSONInfo = JSON[summonerName.lowercaseString] as? [String:AnyObject] {
if let summonerID = summonerJSONInfo["id"] as? Int {
// use the completion where the result becomes available
completion(id: summonerID)
}
if let SummonerName = summonerJSONInfo["name"] as? String {
print(SummonerName)
}
}
}
}
}
You call it like that, with a "trailing closure":
LeagueMethodHelper.retrieveSummonerIDFromSummonerName(serverNameAbbreviation, summonerName: summonerName) { (id) in
savedSummonerID = id
print(savedSummonerID)
}
You are starting an asynchronous task in retrieveSummonerIDFromSummonerName. When it completes this block is executed:
.responseJSON { response in
print(response.response?.statusCode) // URL response
if let JSON = response.result.value {
if let summonerJSONInfo = JSON[summonerName.lowercaseString] as? [String:AnyObject] {
if let summonerID = summonerJSONInfo["id"] as? Int {
savedSummonerID = summonerID
print(summonerID)
}
if let SummonerName = summonerJSONInfo["name"] as? String {
print(SummonerName)
}
}
}
}
If you want the print statement to execute after, you can include it at the end of this block like this:
.responseJSON { response in
print(response.response?.statusCode) // URL response
if let JSON = response.result.value {
if let summonerJSONInfo = JSON[summonerName.lowercaseString] as? [String:AnyObject] {
if let summonerID = summonerJSONInfo["id"] as? Int {
savedSummonerID = summonerID
print(summonerID)
}
if let SummonerName = summonerJSONInfo["name"] as? String {
print(SummonerName)
}
}
}
print("haha \(self.savedSummonerID)")
}
Alternatively, you could implement some sort of callback which you would call at the end of your completion block in the same way.

Resources