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.
Related
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.
How to return what you got?Excuse me, novice, I understand only.enter image description here
enter image description here
Alamofire.request is an asynchronous task, so any code written after this is most likely going to be executed before the task is complete. There are a few ways of doing what you want, but since you haven't provided much information, I have just moved the JSON inside the closure. So you should get something to print out.
Alamofire.request("https://...").responseJSON { (response) in
// Inside this closure is where you receive your JSON
if let value = response.value {
let json = JSON(value)
let title = json["posts", 3, "title"].stringValue
print("Title:", title)
}
}
// Any code after this request will most likely be executed before
// the request has completed because it is done asynchronously.
This is another way that might work better for you.
I understand that you are a beginner and these kinds of operations can be quite complex. You need to understand the order at which code get executed, and how variables work. You are getting that error because swiftyJsonVar is declared in a block that is not accessible to code in the viewDidLoad. I suggest you learn about multi-threading and other asynchronous tasks and probably learn how to declare and use variables properly.
override func viewDidLoad() {
super.viewDidLoad()
load { (json) in
if let json = json {
let title = json["posts", 3, "title"].stringValue
print("Title:", title)
}
}
}
func load(completion: #escaping (JSON?) -> Void){
Alamofire.request("https://httpbin.org/get").responseJSON { (response) in //https://...
var json: JSON?
if let value = response.value {
json = JSON(value)
}
completion(json)
}
}
I am trying to read a locally declared JSON, since I still do not have the network ready to send me JSON examples for the app we are developing.
For some reason I get a null when trying to print the json created by SwiftyJSON framework.
This is my class:
import Foundation
import SwiftyJSON
let mockShifts = "{\"numOfShiftsInDay\": 3,\"shifts\": [{\"StartTime\": \"06:30\",\"EndTime\": \"14:00\"},{\"StartTime\": \"14:00\",\"EndTime\": \"19:00},{\"StartTime\": \"19:00\",\"EndTime\":\"01:00\"}]}"
class WeeklySchedule {
var shifts: [Shift] = []
var shiftsAmount: Int = 3
var relative: Int = 0
func setShiftsAmount(amount: Int){
self.shiftsAmount = amount
for _ in 1...amount{
self.shifts.append(Shift())
}
getShifts()
}
func getRelative() -> Int{
return relative
}
func getShifts(){
let data = mockShifts.data(using: .utf8)!
let json = JSON(data: data)
print(mockShifts) //This prints out a JSON that looks OK to me
print(json) //This prints NULL
if let numOfShifts = json["numOfShiftsInDay"].string {
print(numOfShifts) //This code is unreachable
}
}
}
And this is my console output, when calling setShiftsAmount() which calls getShifts():
{"numOfShiftsInDay": 3,"shifts": [{"StartTime": "06:30","EndTime": "14:00"},{"StartTime": "14:00","EndTime": "19:00},{"StartTime": "19:00","EndTime":"01:00"}]}
null
Why is my JSON null?
The reason you are getting null for your JSON because your JSON string mockShifts is not contain's valid JSON, there is missing double quote(\") for the EndTime key after 19:00 in the second object of array shifts. Add that double quote and you all set to go.
let mockShifts = "{\"numOfShiftsInDay\": 3,\"shifts\": [{\"StartTime\": \"06:30\",\"EndTime\": \"14:00\"},{\"StartTime\": \"14:00\",\"EndTime\": \"19:00\"},{\"StartTime\": \"19:00\",\"EndTime\":\"01:00\"}]}"
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.
[
{
"ID": 0,
"Name": "PHI"
},
{
"ID": 0,
"Name": "ATL"
}
]
I'm using SwiftyJSON and Alamofire. This is what is being returned. I want to loop through each of these objects now in my code. I'm having trouble getting this information though.
json[0]["Name"].string seems to return nil, and I'm not sure why. The JSON object is definitely getting the JSON, when I print it to the console it looks exactly like above.
I also tried:
var name = json[0].dictionary?["Name"]
Still nil though.
Any ideas?
EDIT
Here's the code I'm using to retrieve the JSON.
Alamofire.request(.GET, "url", parameters: nil, encoding: .JSON, headers: nil).responseJSON
{
(request, response, data, error) in
var json = JSON(data!)
//var name = json[0].dictionary?["Name"]
}
Your JSON is valid (at least this snippet is), and your first method to retrieve the data from SwiftyJSON is correct.
On the other hand, the Alamofire snippet you showed didn't compile for me, I had to change the signature.
Given that in the comments you say that not only json[0]["Name"] is nil but json[0] is also nil, I think you have a problem with how your data is fetched.
I tested this version of the Alamofire method in a sample app and it worked well:
Alamofire.request(.GET, yourURL).responseJSON(options: nil) { (request, response, data, error) in
let json = JSON(data!)
let name = json[0]["Name"].string
println(name)
}
In the screenshot, the URL is my local server, with your JSON copy pasted in "test.json".
This "answer" is an extension of my comment, I needed room to show more info...
I think you might try this.
first convert JSON(data) using swifyJSON
let json = JSON(data!)
then count the array element in data.
let count: Int? = json.array?.count
after that use for loop to reach array element one.
for index in 0...count-1 {
self.idArray.append(json[index]["ID"].stringValue)
self.nameArray.append(json[index]["Name"].stringValue)
}
you will get sorted data in "idArray" and "nameArray". try to print both the array.
You do not have to use index to parse array. You can get single json object from array and use it to access the data:
for (index: String, subJson: JSON) in json {
println("Current Index = \(index)")
if let _id = subJson["ID"].int {
println("ID = \(_id)")
} else {
println("Error ID")
}
if let _name = subJson["Name"].string {
println("Name = \(_name)")
} else {
println("Error Name")
}
}