Wait for multiple Alamofire request - ios

I'm trying to add data to my data model so to test it I'm printing the info fetched via Alamofire but my problem is since some data needs to call the api again it becomes null when I print it. Here's my code
Code for getting the person's data
func printAPI(){
swApiHandler.requestSWPApi("http://swapi.co/api/people", completionHandler: {(response, error) in
let json = JSON(response!)
let jsonResult = json["results"]
for (index,person):(String, JSON) in jsonResult{
let name = person["name"].stringValue
let height = person["height"].intValue
let mass = person["mass"].intValue
let hairColor = person["hair_color"].stringValue
let skinColor = person["skin_color"].stringValue
let eyeColor = person["eye_color"].stringValue
let birthYear = person["birth_year"].stringValue
let gender = person["gender"].stringValue
let homeWorldUrl = person["homeworld"].stringValue
let homeWorldNameKey = "name"
let homeWorld = self.getSWApiSpecificValue(homeWorldUrl, strKey: homeWorldNameKey)
print("Name: \(name)")
print("Height: \(height)")
print("Mass: \(mass)")
print("Hair Color: \(hairColor)")
print("Skin Color: \(skinColor)")
print("Eye Color: \(eyeColor)")
print("Birth Year: \(birthYear)")
print("Gender: \(gender)")
print("Home World: \(homeWorld)")
print("------------------------------")
}
})
}
Code for getting the specific value
func getSWApiSpecificValue(strUrl: String, strKey: String) -> String{
var name = ""
swApiHandler.requestSWPApi(strUrl, completionHandler: {(response,error) in
let json = JSON(response!)
print(json[strKey].stringValue)
name = json[strKey].stringValue
})
return name
}
If you want to know the JSON Model here it is
And for running the code here's the output

You should make your api call in background and after it's finished populate your data on main queue.
Just change your code to get specific value to this one:
func getSWApiSpecificValue(strUrl: String, strKey: String) -> String{
var name = ""
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) { () -> Void in
swApiHandler.requestSWPApi(strUrl, completionHandler: {(response,error) in
dispatch_async(dispatch_get_main_queue()) {
let json = JSON(response!)
print(json[strKey].stringValue)
name = json[strKey].stringValue
return name
}
})
}
}
In code above first you get make a request to the server in background and if you get response in main queue will populate you variable name.
Also it's better to change your api call function to something like that:
func getDataFromServer(ulr: String, success: (([AnyObject]) -> Void)?, failure: (error: ErrorType) -> Void){
}
In this way you can handle your errors and if success get your data.

Related

Cannot cast/decode data from server into Swift data model?

I need to cast the below response from my server as [UserResult] but I cannot get it to work??
What am I doing wrong?
func userSearch(keyword: String, completion: #escaping (Result<[UserResult], ResponseError>) -> Void ) {
socket.emit("userSearch", keyword)
socket.on("userFound") { ( data, ack) in
print(data) // prints below NSArray
if !data.isEmpty {
if let response = data as? [UserResult] {
print("USERS \(response)") // WILL NOT WORK?
completion(.success(response))
}
} else {
completion(.failure(.badRequest("No users found")))
}
}
}
Data from server
[<__NSArrayM 0x60000040e5b0>(
{
profileUrl = "www.address1.com";
username = chrissmith;
},
{
profileUrl = "www.address2.com";
username = johnsmith;
},
{
profileUrl = "www.address3.com";
username = alicesmith;
}
)
]
UserResult Model
struct UserResult: Decodable {
let username: String
let profileUrl: String
}
Well you are using Socket.IO library and specifically method
socket.on(clientEvent: .connect) {data, ack in
...
}
defined as
#discardableResult
open func on(clientEvent event: SocketClientEvent, callback: #escaping NormalCallback) -> UUID
using typealias:
public typealias NormalCallback = ([Any], SocketAckEmitter) -> ()
So basically at the and you are being returned data of type [Any] according to documentation.
Since you do not know what is inside your data it is better for you to unwrap objects in your array one by one (instead casting it directly to [UserResult]) and try to find out what Type there are by comparing to some set of known types as some of answers from this question suggest.
I would start with verifying the data structure with example code below , and only move on with casting to various type afterwards:
Lets assume example data1 is your data:
let dict1 = ["profileUrl":"www.address1.com","username":"chrissmith"]
let data1: NSArray = [dict1]
//printed data1:
// (
// {
// profileUrl = "www.address1.com";
// username = chrissmith;
// }
// )
if data1[0] as? [String:String] != nil {
print("We found out that first object is dictionary of [String:String]!")
}
else if data1[0] as? Dictionary<NSObject, AnyObject> != nil {
print("We found out that first object is dictionary of mixed values!")
} else {
print("We found out that first object has different data structure")
}
Hopefully this answer was at least a little bit helpfull, even though not providing direct easy solution for your problem.

Doing things in sequence using completion handlers

I have three functions that need to run in sequence as follows:
Gather integers from Firebase, place them into an array, and calculate the sum. Once complete...
Gather a different integer value from Firebase. Once complete...
The third function takes the value from function 2, and subtracts it from function 1.
I know I need to use completion handlers, but need some help with the syntax, etc.
//This first function grabs integers from Firebase and sums them up:
func LoadPointsCompleted(completion: #escaping(_ sumOfPointsCompleted:Int) -> Int){
self.challengeList.removeAll()
databaseReference = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let refChallenges = Database.database().reference(withPath: "Challenges").child(userID!).queryOrdered(byChild: "Status").queryEqual(toValue: "Complete")
refChallenges.observeSingleEvent(of: .value, with: { (snapshot) in
//if the reference have some values
if snapshot.childrenCount > 0 {
//clearing the list
self.challengeList.removeAll()
//iterating through all the values
for Challenges in snapshot.children.allObjects as! [DataSnapshot] {
//getting values
let challengeObject = Challenges.value as? [String: AnyObject]
let Points = challengeObject?["Points"] as! Int
//creating challenge object with model and fetched values
let challenge = pointsModel(Points: (Points as Int?)!)
//appending it to list
self.challengeList.append(challenge)
let sumOfPointsCompleted = self.challengeList.reduce(0) {$0 + $1.Points}
let sumOfPointsCompletedString = String(sumOfPointsCompleted)
self.Calc_Earned.text = sumOfPointsCompletedString
completion(sumOfPointsCompleted)
}
}
}
)}
// This second function just grabs an integer value from another location in Firebase
func loadPointsRedeemed(completion: #escaping (_ Points_Redeem: Int) -> Int) {
databaseReference = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
databaseReference.child("Users").child(userID!).observe(DataEventType.value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
// let Points_Earn = value?["Points_Earned"] as? String ?? ""
let Points_Redeem = value?["Points_Redeemed"] as! Int
// self.Points_Earned.text = Points_Earn
let points_redeemedString = String(Points_Redeem)
self.Points_Redeemed.text = points_redeemedString
// let pointsRedeemedAs = Points_Redeem
completion(Points_Redeem)
// Do any additional setup after loading the view.
}
)}
// This third function simply takes the values from the first two functions and subtracts them
func BalanceOfPoints(){
let a = LoadPointsCompleted()
let b = loadPointsRedeemed()
let balance = a - b
let balanceString = String(balance)
self.Points_Balance.text = balanceString
}
However, I get some errors in the third function "BalanceOfPoints" as follows:
Missing argument for parameter 'completion' in call - insert 'completion: <(Int) -> Int>'
I have no idea if my syntax is correct when I first start the functions as per here:
func LoadPointsCompleted(completion: #escaping(_ sumOfPointsCompleted:Int) -> Int){
and...
func loadPointsRedeemed(completion: #escaping (_ Points_Redeem: Int) -> Int) {
Could someone please help? Thanks in advance.
LoadPointsCompleted takes a closure as an argument (a completion handler)
you could either give it a nil completion handler
LoadPointsCompleted(completion: nil)
or give it a handler
LoadPointsCompleted() { sumOfPointsCompleted in
return <#some Int#>
}
but your logic in BalanceOfPoints seems a bit off because LoadPointsCompleted and loadPointsRedeemed are being called sequentially at the same time and also do not actually return an Int, only the completion handler of the function does. eg completion(sumOfPointsCompleted) could be changed to be let a = completion(sumOfPointsCompleted) but im sure thats not what you were intending.
I would change the signature of both those functions to
func LoadPointsCompleted(completion: #escaping(_ sumOfPointsCompleted:Int) -> Void){
and
func loadPointsRedeemed(completion: #escaping (_ Points_Redeem: Int) -> Void) {
then in BalanceOfPoints do this instead
func BalanceOfPoints(){
LoadPointsCompleted() { sumOfPointsCompleted in
loadPointsRedeemed() { Points_Redeem in
let balance = sumOfPointsCompleted - Points_Redeem
let balanceString = String(balance)
self.Points_Balance.text = balanceString
}
}
}
but just note that BalanceOfPoints in now an asynchronous function.
Not sure if step 1 and 2 in your question could be done in parallel, but seems like they could be, but as it stands in this answer they are in sequence

firebase duplicating observer on .value on to the terminal updating my branches multiple times

Im Using DataEventType.value observe received data duplicated Why ?
This is my database screenshot
this is the first update
this is the second update
this is the third update
func checkingPostToAddNotifications(userId: String, postId: String, hasUserIdCallback: #escaping (Bool)->Void) {
//this will get the post id
print("postId -> \(postId),userId -> \(userId)")
Api.Post.REF_POSTS.child(postId).child("likes").observe(DataEventType.value, with: { (postCheck) in
print("postCheck -> \(postCheck)")
if let notificationValue = postCheck.value as? NSDictionary{
print("notificationValue -> \(notificationValue.allKeys)")
let keys = notificationValue.flatMap(){$0.0 as? String}
print("keys => \(keys)")
let hasUserId = keys.contains(userId);
print("hasUserId -> \(hasUserId)")
//if your id is in we send notification to post.uid
//send notification if true
hasUserIdCallback(hasUserId)
} else {
//if not we remove from uid
hasUserIdCallback(false)
}
})
}

Extracting data from API (JSON format) doesn't save data outside of function call

I am trying to get an array of temperatures in a given time period from an API in JSON format. I was able to retrieve the array through a completion handler but I can't save it to another variable outside the function call (one that uses completion handler). Here is my code. Please see the commented area.
class WeatherGetter {
func getWeather(_ zip: String, startdate: String, enddate: String, completion: #escaping (([[Double]]) -> Void)) {
// This is a pretty simple networking task, so the shared session will do.
let session = URLSession.shared
let string = "api address"
let url = URL(string: string)
var weatherRequestURL = URLRequest(url:url! as URL)
weatherRequestURL.httpMethod = "GET"
// The data task retrieves the data.
let dataTask = session.dataTask(with: weatherRequestURL) {
(data, response, error) -> Void in
if let error = error {
// Case 1: Error
// We got some kind of error while trying to get data from the server.
print("Error:\n\(error)")
}
else {
// Case 2: Success
// We got a response from the server!
do {
var temps = [Double]()
var winds = [Double]()
let weather = try JSON(data: data!)
let conditions1 = weather["data"]
let conditions2 = conditions1["weather"]
let count = conditions2.count
for i in 0...count-1 {
let conditions3 = conditions2[i]
let conditions4 = conditions3["hourly"]
let count2 = conditions4.count
for j in 0...count2-1 {
let conditions5 = conditions4[j]
let tempF = conditions5["tempF"].doubleValue
let windspeed = conditions5["windspeedKmph"].doubleValue
temps.append(tempF)
winds.append(windspeed)
}
}
completion([temps, winds])
}
catch let jsonError as NSError {
// An error occurred while trying to convert the data into a Swift dictionary.
print("JSON error description: \(jsonError.description)")
}
}
}
// The data task is set up...launch it!
dataTask.resume()
}
}
I am calling this method from my view controller class. Here is the code.
override func viewDidLoad() {
super.viewDidLoad()
let weather = WeatherGetter()
weather.getWeather("13323", startdate: "2016-10-01", enddate: "2017-04-30") { (weatherhandler: [[Double]]) in
//It prints out the correct array here
print(weatherhandler[0])
weatherData = weatherhandler[0]
}
//Here it prints out an empty array
print(weatherData)
}
The issue is that API takes some time to return the data, when the data is return the "Completion Listener" is called and it goes inside the "getWeather" method implementation, where it prints the data of array. But when your outside print method is called the API hasn't returned the data yet. So it shows empty array. If you will try to print the data form "weatherData" object after sometime it will work.
The best way I can suggest you is to update your UI with the data inside the "getWeather" method implementation like this:
override func viewDidLoad() {
super.viewDidLoad()
let weather = WeatherGetter()
weather.getWeather("13323", startdate: "2016-10-01", enddate: "2017-04-30") { (weatherhandler: [[Double]]) in
//It prints out the correct array here
print(weatherhandler[0])
weatherData = weatherhandler[0]
// Update your UI here.
}
//Here it prints out an empty array
print(weatherData)
}
It isn't an error, when your controller get loaded the array is still empty because your getWeather is still doing its thing (meaning accessing the api, decode the json) when it finishes the callback will have data to return to your controller.
For example if you were using a tableView, you will have reloadData() to refresh the UI, after you assign data to weatherData
Or you could place a property Observer as you declaring your weatherData property.
var weatherData:[Double]? = nil {
didSet {
guard let data = weatherData else { return }
// now you could do soemthing with the data, to populate your UI
}
}
now after the data is assigned to wheaterData, didSet will be called.
Hope that helps, and also place your jsonParsing logic into a `struct :)

Make instance wait until called functions are complete - Swift

I apologise in advance if this has already been answered but as you can probably tell from the title I wasn't really sure how to describe the issue and a answer to a similar question I found wasn't helpful.
I'm attempting to make an instance of "Coupon" that has its properties loaded from an SQL database after passing an id to the database in the init method.
My issue is when I call then init method from a different viewController class it will return the instance with the default string values of "" as the data from the NSURLConnection hasn't been/decoded before returning to the viewContoller.
Im looking for a solution for to some how make the init method wait until the fields are loaded.
Coupon class relevant properties:
var webData: NSMutableData?
var id: Int
var name: String = ""
var provider: String = ""
var details: String = ""
Coupon class relevant methods:
convenience init(id: Int) {
self.init()
self.id = id
self.selectSQL(id) //passes id to server and returns all other varibles
}
func selectSQL(id: Int) {
let url = NSURL(string: "http://wwww.website.php?id=\(id)") // acess php page
let urlRequest = NSURLRequest(URL: url!)
let connection = NSURLConnection(request: urlRequest, delegate: self)
}
func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) {
webData = NSMutableData()
}
func connection(connection: NSURLConnection, didReceiveData data: NSData) {
webData?.appendData(data)
}
func connectionDidFinishLoading(connection: NSURLConnection) {
let result = NSJSONSerialization.JSONObjectWithData(webData!, options: .AllowFragments, error: nil) as? NSArray
let resultDict = result?[0] as? NSDictionary
if let dict = resultDict {
name = dict.objectForKey("name") as! String
provider = dict.objectForKey("provider") as! String
details = dict.objectForKey("details") as! String
}
It is not possible to "wait for your SQL finishing" and then return from init without blocking your thread (synchronize), which would not be what you want.
I suggest to use a factory method with a callback to get a workaround for it. Like this:
class Coupon {
private var handler: ((coupon: Coupon) -> ())?
class func createCoupon(id: Int, completionHandler: ((coupon: Coupon) -> ())?) {
let coupon = Coupon(id: id)
// Store the handler in coupon
coupon.handler = completionHandler
}
//...
func connectionDidFinishLoading(connection: NSURLConnection) {
//...Setup coupon properties
handler?(coupon: self)
handler = nil
}
}
Then you can create and use your coupon like this:
Coupon.createCoupon(1, completionHandler: { (coupon) -> () in
// Do your thing with fully "inited" coupon
})
Of course, you also need to consider the situation of connection failed to your server, and maybe call the handler with an error, which does not present in your current code.

Resources