I know how to make a synchronous request with completions. But I don't know how to make synchronous requests in a loop.
Here is my code:
var marks = [JSON]()
let vnCount = studentVnCodes.count
var i: Int = 0
marks = [JSON](repeating: JSON.null, count: vnCount)
for vn in studentVnCodes {
let url = "https://example.com/Student/Grade/GetFinalGrades?&vn=\(vn)&academic_year=All"
Alamofire.request(url).responseString { response in
var dataString: String = (response.result.value)!
dataString = cleanMarksJSON(string: dataString)
if let dict = convertToDictionary(text: dataString) {
marks[i] = (JSON(dict as Any))
i += 1
if (vnCount == marks.count) {
completionHandler(marks)
}
}
}
}
Here I'm trying to make x requests with the number of vn codes (vnCount).
The issue is that I get all the JSON in a wrong order in my array of JSON marks. Certainly because it appends responses in the array when it's finished and don't wait the previous request to be ended.
So I tried to create a variable i to force the function to append responses in the right order. That's not working. Any idea? Thanks!
You can run your requests sequentially in a serial queue, in which case they will be executed in the order you call them, which ensures they will be added to the array in order. However, this seems like a suboptimal solution, since you lose execution time by running your requests sequentially instead of concurrently.
If you still want to implement it like this, see the code below:
var marks = [JSON]()
let vnCount = studentVnCodes.count
marks = [JSON](repeating: JSON.null, count: vnCount)
let serialQueue = DispatchQueue(label: "serialQueue")
for vn in studentVnCodes {
serialQueue.async{
let url = "https://example.com/Student/Grade/GetFinalGrades?&vn=\(vn)&academic_year=All"
Alamofire.request(url).responseString { response in
var dataString: String = (response.result.value)!
dataString = cleanMarksJSON(string: dataString)
if let dict = convertToDictionary(text: dataString) {
marks.append(JSON(dict as Any))
if (vnCount == marks.count) {
completionHandler(marks)
}
}
}
}
}
A better solution would be to store the response in a data structure, where ordering doesn't matter, for example in a dictionary, where your keys are the indexes (which you would use for an array) and your values are the JSON response values. This way you can run the requests concurrently and access the responses in order.
Related
I'm struggling with some code I'm playing with at the minute to sort the response alphabetically. Everything I try, it keeps flagging various errors and build issues.
My code currently runs:
class Category {
var name = "Category"
var offences = [Offence]()
required init(name: String) {
self.name = name
}
func addOffence(data: JSON) {
let offence = Offence(data: data)
offences.append(offence)
}
var imageName: String {
return name
}
}
Any ideas where I can slot it in?
I forgot to update this previously...
So in order to sort the API response, I simply amended the API request to sort by Active desc and that achieves the result I was looking for.
Here I'm trying to get currency values of INR for last 30 dates.
I'm fetching last 30 dates values of INR currency using Alamofire.
//strDates contains all 30 days dates
for i in 0..<strDates.count {
Alamofire.request("http://api.fixer.io/\(strDates[i])?base=USD").responseJSON { response in
if let arr = response.result.value as? [String:AnyObject]
{
let inrc = (arr["rates"]?["INR"] as? Double)!
print(inrc)
self.sValues.append(inc)
print(sValues)
//It prints values here.
}
}
}
print(sValues) //Print nil
setChart(dataPoints: strDates, values: sValues)
How do I use this sValues array outside the Alamofire block.
Here, Actually I'm sending dates & INR values as a parameter to below method.
func setChart(dataPoints: [String], values: [Double]) {
barChartView.noDataText = "You need to provide data for the chart."
for i in 0..<dataPoints.count {
let dataEntry = BarChartDataEntry(x: Double(i), yValues: [values[i]])
dataEntries.append(dataEntry)
}
let chartDataSet = BarChartDataSet(values: dataEntries, label: "INR Rates(₹)/$")
let chartData = BarChartData(dataSet: chartDataSet)
barChartView.data = chartData
barChartView.xAxis.labelPosition = .bottom
barChartView.rightAxis.enabled = false
barChartView.leftAxis.enabled = true
barChartView.data?.setDrawValues(false)
barChartView.leftAxis.granularityEnabled = true
barChartView.leftAxis.granularity = 1.0
barChartView.xAxis.granularityEnabled = true
barChartView.xAxis.granularity = 1.0
barChartView.leftAxis.axisMinimum = 70//65
barChartView.leftAxis.axisMaximum = 60//70
//chartDataSet.colors = [UIColor.cyan, UIColor.green]
}
Are you looking for the chart to refresh when only when ALL of the calls are done? If so you use a pattern like this:
var completedCalls = 0
for i in 0..<strDates.count {
Alamofire.request("http://api.fixer.io/\(strDates[i])?base=USD").responseJSON { response in
if let arr = response.result.value as? [String:AnyObject]
{
completedCalls += 1
let inrc = (arr["rates"]?["INR"] as? Double)!
print(inrc)
self.sValues.append(inc)
print(sValues)
//It prints values here.
if completedCalls = strDates.count {
DispatchQueue.main {
setChart(dataPoints: strDates, values: sValues)
}
}
}
}
}
The idea is to count how many API requests come back and only take action when all of the requests are done (you should check that all of them actually succeeded as well and show an error if any fail).
Your codes cause sValues to print nil because your print(sValues) ran before your request response. This block
if let arr = response.result.value as? [String:AnyObject]
{
let inrc = (arr["rates"]?["INR"] as? Double)!
print(inrc)
self.sValues.append(inc)
print(sValues)
//It prints values here.
}
runs only when your request returns with a value or an error. So any usage of values from your request should also be done here. If you want to use the value to update certain UI element or database, you should also call the method from within this block to ensure that you already retrieved the value from its source.
I do suggest you adopt this mentality when programming any asynchronous codes as you can never expect when your codes running in another thread or the server to return. It is always safer to run you methods that requires the return value in a completion block that runs only when the asynchronous codes have completed.
If you're displaying them in a table, create a strong variable of type Array, or some other data provider (e.g. CoreData, SQLite) and store the results there. In the completion block for AF, set the value of the Array variable (or update your local data provider) to the API result data, then call reloadData() on your table. You'll also need to configure the UITableView delegate and datasource methods.
I've been busy creating a Schedule app for a school project but can't really seem to figure it out. Someone on this site already helped me to filter out data from a JSON link I had, but I can't really make it work. I want to put the Data that I get in my console into a Table view. I get roster info for 5 days, so every cell should have different information. I've watched several youtube videos and looked around SO but its either not really what I need, or I don't understand the way it works.
The code I currently have :
typealias JSONDictionary = [String:Any]
let savedValue = UserDefaults.standard.string(forKey: "LoginUserID")
let url = URL(string: "(url)")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil
{
print ("ERROR")
}
else
{
if let content = data
{
do {
if let json = try JSONSerialization.jsonObject(with:data!, options: []) as? JSONDictionary {
if let days = json["Days"] as? [JSONDictionary] {
for day in days {
print(day["DayName"] as! String)
if let lessons = day["Lessons"] as? [JSONDictionary] {
for lesson in lessons {
let classRoom = lesson["Classroom"] as! String
let name = lesson["Name"] as! String
let teacher = lesson["Teacher"] as! String
print(teacher)
}
}
}
}
}
} catch {
print(error)
}
}
}
}
task.resume()
So I need to first of all be able to get info for example just Monday. Then i need to divide a table cell into 7 parts, and then put all the info i get into those parts.
I hope someone can help out because I'm really struggling
You are parsing the data. That's great, but you need a reference to it, i.e. an object. (UPDATE: looking at your data and your parsing, you are going to have some issues. DayName & Lesson are dictionaries inside an array of the dictionary Day. Your parsing isn't quite complete...)
TableViews work best when access an array of objects. To create an array of objects, you'll need to create a class ("Lessons" I'm assuming?). In the class, you'll set up the properties that a Lesson object should have -> Classroom, Name, Teacher. Here is a Class file I made for a simple project.
class DailyWeather: NSObject {
var time : Double = 0.0
var summaryDescription : String = ""
var temperatureMin : Double = 0.0
var temperatureMax : Double = 0.0
var precipProbability : Double = 0.0
override init() {
super.init()
self.time = 0.0
self.summaryDescription = ""
self.temperatureMin = 0.0
self.temperatureMax = 0.0
self.precipProbability = 0.0
}
}
As you can see, I declared all my properties that I'll need in the class. I took an easy route and gave them all values to start, whether they were empty strings or values of 0 if the data type was an Int or Double. Then I initialized them. After that, I was able to create an object, and set it's values when I parsed the JSON data.
if let dailyDict = jsonDictionary["daily"] as? JSONDictionary {
print(dailyDict)
if let dataArray = dailyDict["data"] as? JSONArray {
print(dataArray)
for dataDict in dataArray {
let dailyWeatherData = DailyWeather()
if let summary = dataDict["summary"] as? String {
dailyWeatherData.summaryDescription = summary
} else {
print("I could not parse summary")
} ... <continue to parse and then append the object at the end before the loop begins again>
As you can see, just after I began looping through my Data array, I created an instance of the Class I created above
let dailyWeatherData = DailyWeather()
From there, I could set the properties of that dailyWeatherData object while I parsed it. By placing the object inside the loop, it will create a new object for you every time.
From there, you'll need to append the object at the end of the loop before it starts over again to an array of objects, in my case, an array of DailyWeather objects.
I'm going to assume you're doing everything in the ViewController (which is where most newbies start) so put your object array at the top before ViewDidLoad()
var DailyWeatherArray = [DailyWeather]()
This creates an array that will hold all those objects you're create in the loop while you parse the data. You'll put those objects by appending the object to the array just before the end of the loop.
After that, you can just access the array from the TableViewDelegates and DataSource functions you will be using to populate the tableview. I'll let you figure that part out as I've given you quite a bit to be going on with.
** My code was in an older version of Swift, so there may be a couple of discrepancies...
I'm currently struggling with trying to cache data and retrieve data which is being parsed using SwiftyJSON.
For the first method I'm trying to cache the data using the following function
Cache function:
func cacheStory(data: SwiftyJSON.JSON){
let cache = Shared.JSONCache
if let dataToCache: Haneke.JSON = data as? Haneke.JSON {
// print(dataToCache)
cache.set(value: dataToCache, key: "data")
}
}
This function will then be used like so
Using cache function:
var storyDataJson = SwiftyJSON.JSON(myresponse)
self.stories = storyDataJson["stories"]
self.cacheStory(storyDataJson["stories"])
This doesn't actually do anything and I'm currently getting a warning
Cast from 'JSON' to unrelated type 'JSON' always fails
So when trying to get this data using the function below the application doesn't even build.
Retrieval function:
func getCachedStory(key: String) {
let cache = Shared.JSONCache
cache.fetch(key: key).onSuccess { data in
if let d = data {
print(d)
} else {
print("Nowt")
}
}
}
Not too sure if it's even possible to use these two libraries together.
The foursquare api endpoint I'm hitting requires me to combine a photo prefix, size, and suffix together to create a usable image URL. I am attempting to do this in the "photoURL" constant which currently works.
How can I check to see if the data for all of the pieces of this photoURL is there (using if let) while combining the variables together to set venueImageView from URL using Haneke?
Here is my code:
func bindData() {
let ratingSignals = self.dog?["venue"]["ratingSignals"].stringValue
let photoURLPrefix = self.dog?["venue"]["featuredPhotos"]["items"][0]["prefix"].stringValue
let photoURLSuffix = self.dog?["venue"]["featuredPhotos"]["items"][0]["suffix"].stringValue
let photoURL = photoURLPrefix! + "original" + photoURLSuffix!
let venuePhotoURL = NSURL(string: photoURL)
println("photo url prefix is \(photoURLPrefix)")
println("photo url suffix is \(photoURLSuffix)")
println(photoURL)
self.openUntilLabel.text = self.dog?["venue"]["hours"]["status"].stringValue
self.addressLabel.text = self.dog?["venue"]["location"]["address"].stringValue
self.titleLabel.text = self.dog?["venue"]["name"].stringValue
self.ratingsLabel.text = "Based on \(ratingSignals) ratings"
self.ratingImageView.image = UIImage(named:"Score8-5")!
if let photoURL = photoURLPrefix! + "original" + photoURLSuffix!{
let url = NSURL(string: photoURL)
venueImageView.hnk_setImageFromURL(url!)
}
I commented out self.venueImageView.hnk_setImageFromURL(venuePhotoURL!) which currently works, but Im worried that if a request doesnt return an image it will crash the app. So I am trying to use an if let to check that the data exists, and then set the imageView inside of this statement.
The error I am getting:
"Bound value in a conditional binding must be of optional type"
Here is an image of the error:
I did not run your code but I think your code will crash even if you remove that if condition and if block completely in case of no data is returned.
You should validate your response like this;
let photoURLPrefix? = self.dog?["venue"]["featuredPhotos"]["items"][0]["prefix"] as String?
let photoURLSuffix? = self.dog?["venue"]["featuredPhotos"]["items"][0]["suffix"] as String?
if (photoURLPrefix == nil || photoURLSuffix == nil)
{
//Do not proceed
return;
}
I don't know if the way you are retrieving data from dog object, is working but the important thing is that optional sign to use.
Given that a GET request will always return a suffix if a prefix is present, I just tested against the prefix and didn't need to "combine" them.
Here is my solution: dog(above) is now "pin"
if let photoURLPrefix = self.pin?["venue"]["featuredPhotos"]["items"][0]["prefix"].stringValue {
let photoURLSuffix = self.pin?["venue"]["featuredPhotos"]["items"][0]["suffix"].stringValue
let photoURL = photoURLPrefix + "original" + photoURLSuffix!
let venuePhotoURL = NSURL(string: photoURL)
self.venueImageView.hnk_setImageFromURL(venuePhotoURL!)
println("photo url prefix is \(photoURLPrefix)")
println("photo url suffix is \(photoURLSuffix)")
println(photoURL)
}