Functions not running in order - ios

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.

Related

need to get the country name from open api

Needs to get country name from below api call :
https://restcountries.eu/rest/v1/all
My code :
var arrRes = []
func getCountry() {
let Url: String = "https://restcountries.eu/rest/v1/all"
Alamofire.request(Url).responseJSON { (responseData) -> Void in
do {
if let datas = responseData.result.value {
let data = (datas as AnyObject).data(using: .utf8)!
let parseData = try JSONSerialization.jsonObject(with: data, options: [])
for country in parseData {
if let name = country["name"] as? String {
print(name)
}
}
}
}
catch let error as NSError {
print(error)
}
}
}
getting error here : 'Any' is not convertible to 'AnyObject' on below line let data = (datas as AnyObject).data(using: .utf8)!..
I need to get only name and append to my array.Any other idea or solution to achieve that ?
Replace do catch block of statement with this.
do {
if let countries = responseData.result.value as? [[String: Any]] {
for country in countries {
if let name = country["name"] as? String {
print(name)
}
}
}
}
catch let error as NSError {
print(error)
}
Try this, its working fine for me.
let urlStr = "https://restcountries.eu/rest/v1/all"
let setFinalURl = urlStr.addingPercentEncoding (withAllowedCharacters: .urlQueryAllowed)!
var request = URLRequest(url: URL(string: setFinalURl)!)
request.httpMethod = HTTPMethod.get.rawValue
Alamofire.request(request).responseJSON
{ (responseObject) -> Void in
if responseObject.result.isSuccess
{
print(responseObject.result.value!)
if "\(String(describing: responseObject.response!.statusCode))" == "200"
{
let result = responseObject.result.value! as AnyObject
let countryNamesArr = result.value(forKey: "name") as! NSArray
print(countryNamesArr)
}
else
{
// handle error
}
}
if responseObject.result.isFailure
{
let error : Error = responseObject.result.error!
print(error.localizedDescription)
}
}
You can try
struct Root: Codable {
let name: String
}
func getCountry() {
let urlStr = "https://restcountries.eu/rest/v1/all"
Alamofire.request(urlStr).responseData { (data) in
do {
guard let data = data.data else { return }
let res = try JSONDecoder().decode([Root].self,from:data)
print(res)
}
catch {
print(error)
}
}
}
Just remove this line
let data = (datas as AnyObject).data(using: .utf8)!
and in optional binding just assign data, since value is of type Data?, from optional binding you get Data
if let data = responseData.result.value
then don't forget to downcast your json to array [String:Any]
...jsonObject(with: data, options: []) as? [[String:Any]]
... then don't forget to unwrap this array or you wouldn't be able to iterate through it in for each loop
Also note that since there is Codable, you should use it instead of JSONSerialization. Then you can decode your json using JSONDecoder to your own model which conforms to protocol Decodable.
As a simple approach, you could implement getCountry() like this:
func getCountry() {
let url: String = "https://restcountries.eu/rest/v1/all"
Alamofire.request(url).responseJSON { response in
if let resultValue = response.result.value, let countryObjects = resultValue as? [[String: Any]] {
let countryNames = countryObjects.compactMap { $0["name"] as? String }
print(countryNames)
}
}
}
At this point, there is no need to use JSONSerialization to get the country names; According to the API response, responseData.result.value is an array of countries (dictionaries), each dictionary has a "name" value, what you should do is to map the response to an array of string. countryNames should contains what are you looking for.
The benefit of using compactMap is to avoid any nil name, so countryNames should be [String] instead of [String?].
However, if you believe that you would need to transform the whole response objects into a custom objects (instead of dictionaries), I would highly recommend to follow the approach of using Decodable.
My code, its working well for me.
Swift 5
public func getCountry(completion: #escaping ([String]) -> ()) {
let url: String = "https://restcountries.eu/rest/v1/all"
AF.request(url).responseJSON { (responseData) -> Void in
do {
guard let data = responseData.data else { return }
let res = try JSONDecoder().decode([CountryName].self,from:data)
completion(self.getCountryName(countryName: res))
}
catch {
print(error)
}
}
}
struct CountryName: Codable {
let name: String
}
private func getCountryName(countryName:[CountryName]) -> [String]{
var country:[String] = []
for index in 0...countryName.count - 1{
country.append(countryName[index].name)
}
return country
}

Alamofire Web service manager returns nil in viewController

I want to create AlamofireWebService manager that contains all of my requests and I just use class functions of this class in my viewControllers and use responses in the viewController.
For example something like this in viewController:
let cardResponse : String?
cardResponse = WebServiceManager.shared.getCardTitle()
I searched and found I should use escaping completionHandler in my function, And I wrote this:
import Foundation
import Alamofire
import SwiftyJSON
class WebServiceManager {
static let shared : WebServiceManager = WebServiceManager()
let apiEndPoint = "My URL"
func getCardTitle(completionHandler: #escaping (NSDictionary?, Error?) -> ()) {
Alamofire.request("\(apiEndPoint)")
.responseJSON { response in
switch response.result {
case .success(let value):
print("from .success \(value)")
completionHandler(value as? NSDictionary, nil)
case .failure(let error):
completionHandler(nil, error)
}
}
}
}
In success case, print works fine but in viewController, it just prints nil.
My viewController:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
WebServiceManager.shared.getCardTitle() { responseObject, error in
// use responseObject and error here
print("responseObject = \(responseObject); error = \(error)")
return
}
}
What should I do to print in viewController print my response?
It means that your value can't be cast to NSDictionary it has another type.
completionHandler(value as? NSDictionary, nil)
Please try in the manager a "pseudo code"
if let dic = value as? [String: Any] {
debugPrint("Dic \(dic)")
} else if let str = value as? String {
debugPrint("String \(str)")
} else if let data = value as? Data, let str = String(data: data, encoding: .utf8) {
debugPrint("UTF8 String \(str)")
}
just check response nil
Alamofire.request("URL", method: .post).responseJSON{(responseData) -> Void in
if((responseData.result.value != nil)){
let jsonData = JSON(responseData.result.value)
if let arrJSON = jsonData["keyNodes"].arrayObject {
for index in 0...arrJSON.count-1 {
let aObject = arrJSON[index] as! [String : AnyObject]
var sl:String;
if let sl1 = aObject["sl"] as? String {
sl = sl1;
}
}
}
}
}

How to parse this JSON from swift 3 facebook graph request

I am using a graph request to get a json using this code:
nextrequest.start({ (response: HTTPURLResponse?, result: Any?) in
print(result)
})
this is the json result below and I have no idea how to access the the data inside such as gender, id and name...
Optional(FacebookCore.GraphRequestResult<FacebookCore.GraphRequest>.success(FacebookCore.GraphResponse(rawResponse: Optional({
gender = male;
id = 1128614937219535;
name = "Rayan Slim";
picture = {
data = {
height = 320;
"is_silhouette" = 0;
url = "https://scontent.xx.fbcdn.net/v/t1.0-1/p320x320/12541113_961418627272501_5451131278168499090_n.jpg?oh=47433bc236ce63ce1c07b92499087f29&oe=586A406A";
width = 320;
};
};
}))))
any help would be greatly appreciated!!!!
After permission has been granted here's the function that works.
if AccessToken.current != nil {
GraphRequest(graphPath: "me", parameters: ["fields": "id, name, first_name, last_name, picture.type(large), email"]).start({ (urlResponse, requestResult) in
switch requestResult {
case .Success(let response):
if let responseDictionary = response.dictionaryValue {
let email = responseDictionary["email"] as? String
print(email)
let first = responseDictionary["name"] as? String
print(first)
if let picture = responseDictionary["picture"] as? NSDictionary {
if let data = picture["data"] as? NSDictionary{
if let profilePicture = data["url"] as? String {
print(profilePicture)
}
}
}
}
case .Failed(let error):
print(error)
}
})
}
This is tested and working in Swift 3, using the Facebook SDK for Swift
let pictureRequest = GraphRequest(graphPath: "me/picture?type=large&redirect=false", parameters: [:])
pictureRequest.start{
(urlResponse, requestResult) in
switch requestResult {
case .failed(let error):
print("error in graph request:", error)
break
case .success(let graphResponse):
if let responseDictionary = graphResponse.dictionaryValue {
print(responseDictionary)
var dict: NSDictionary!
dict = responseDictionary["data"] as! NSDictionary
print(dict)
print(dict["url"])
}
}
}
if let userDataDict = result as? NSDictionary {
self.first_name = userDataDict["first_name"] as? String
self.id = userDataDict["id"] as? String
self.last_name = userDataDict["last_name"] as? String
let pictDict = userDataDict["picture"] as? NSDictionary
let pictureUrl = pictDict?["data"] as? NSDictionary
self.pictureUrl = pictureUrl?["url"] as? String
}
do like
nextrequest.start({ (response: HTTPURLResponse?, result: Any?) in
print(result)
if let userData = result as? [NSObject: Any]
{
if let name = userData["name"] as? String
{
print(name)
}
if let picture = userData["picture"] as? [NSObject: Any] {
if let data = picture["data"] as? [NSObject: Any] {
if let profilePictureURL = data["url"] as? String {
// Now add the data to the UI elements
print (profilePictureURL)
}
}
}
}
})
Make use of some JSON parser, maybe Unbox that makes it easier to handle JSON. This code is not tested, but it is an outline of how you could do it. It is always code to store data in a struct instead of using dictionaries.
typealias JSON = [String: Any]
protocol Model {
init?(json: JSON)
}
func getDataFromFacebook() {
...
nextrequest.start {
(response: HTTPURLResponse?, result: Any?) in
self.handleFacebookResult(result)
}
}
func handleFacebookResult(_ result: Any?) {
guard
let json = result as? JSON,
let person = Person(json: json)
else { return }
//do something with person! :)
}
struct Person: Model {
let name: String
let gender: String
let picture: Picture
init?(json: JSON) {
guard
let name = json["name"] as? String,
let gender = json["gender"] as? String,
let pictureJson = json["picture.data"] as? JSON, // "picture.data" possible when using 'Unbox'
let picture = Picture(json: pictureJson)
else { return nil }
self.name = name
self.gender = gender
self.picture = picture
}
}
struct Picture: Model {
let height: Int // Or maybe float...?
let width: Int // Or maybe float...?
let url: String
init?(json: JSON) {
guard
let height = json["height"] as? Int,
let width = json["width"] as? Int,
let url = json["url"] as? String
else { return nil }
self.height = height
self.width = width
self.url
}
}

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)

Return Bool in Alamofire closure

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
}
}

Resources