It takes at least 15-35 second for my iPhone to go through these loops. I learned about JSON mostly on StackOverflow and this is the way people do this. These arrays have 3 elements inside with some text and one small image per element
if let parseJSON = json{
let succes = parseJSON["data"]
let item = self.success["catalogue_products"] as! [[String: AnyObject]]
if item.isEmpty == false{
for i in item {
var categoryName = i["category_name"] as! String
if self.category == nil{
self.category = categoryName
self.categories.append(self.category)
self.categoryCount = 1
}
if self.category != categoryName{
self.categoryCount += 1
self.category = categoryName
self.categories.append(self.category)
}
var deep = i["products"] as! [[String: AnyObject]]
for i in deep{
var product = ProductCatalogue()
product.categoryName = categoryName
product.id = i["id"]
println(product.id)
product.name = i["name"]
product.imageUrl = i["image"]
product.value = i["value"]
var volumes = (i["volumes"] as? [AnyObject])!
var check = true
for i in volumes{
if check == true {
product.volumeMin = i
check = false
} else {
product.volumeMax = i
check = true
}
}
product.colors = i["colors"] as! [[String: AnyObject]]
for i in product.colors{
let temp: AnyObject? = i["code"]
product.colorCode.append(temp!)
let url2: AnyObject? = i["image"]
product.colorImageUrl.append(url2!)
}
let url = NSURL(string: String(stringInterpolationSegment: product.imageUrl))
let data = NSData(contentsOfURL: url!)
product.image = UIImage(data: data!)
if product.colorImageUrl.isEmpty == false {
for i in 0...(product.colorImageUrl.count - 1) {
let url1 = NSURL(string: String(stringInterpolationSegment: product.colorImageUrl[i]))
let data1 = NSData(contentsOfURL: url1!)
switch i {
case 0: product.color1 = UIImage(data: data1!)
case 1: product.color2 = UIImage(data: data1!)
case 2: product.color3 = UIImage(data: data1!)
case 3: product.color4 = UIImage(data: data1!)
case 4: product.color5 = UIImage(data: data1!)
case 5: product.color6 = UIImage(data: data1!)
case 6: product.color7 = UIImage(data: data1!)
default: println("")
}
}
}
self.array.append(product)
}
}
self.sortinOut()
self.loadScreen()
Its because you are making MANY HTTP requests. First in one for loop which is not a good idea at all and second time in the nested loop, which in my idea is a bad mistake. HTTP requests are not efficient and fast when dealing them like this.
If you are willing to get a bunch of data, make the JSON in your REST api and get it once.
If you don't have a REST api and you're simply getting images from the web, make it as efficient as possible by making the fewest requests possible. Also, try getting your images Asynchronously, by calling the GCD Async function.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// download image
});
Hope it helps
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
I am making a spelling bee game that gets words from Oxford Dictionary's API. The app delivers words expected, but its failure rate is higher than I want. I'm getting about a 25 to 36 percent success rate; most words deliver a nil result but that's obviously my fault, not the API's. I want a higher success rate because the API is not free. Or is this kind of hit-rate typical for major APIs?
I know that I need to change this over to Swift 4's Decodable for JSON but I haven't gotten around to figuring that one out yet.
Can you find anything in my code that would cause perfectly simple words to be skipped, such as one-syllable words that would be in any dictionary? At the end of the code, I've tacked on a sample console log of the words that succeed and fail.
func speakRandomWord() {
//Now that a valid random word is selected, this where I make my API call.
appId = "private"
appKey = "private"
language = "en"
word = randomWord //I get the random word from Lexicontext, Oxford API doesn't support random words yet. I filter out entriest that aren't one word.
regions = "gb"
word_id = word.lowercased()
url = URL(string:"https://odapi.oxforddictionaries.com:443/api/v1/entries/\(language)/\(word_id)/regions=\(regions)")!
var request = URLRequest(url: url)
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue(appId, forHTTPHeaderField: "app_id")
request.addValue(appKey, forHTTPHeaderField: "app_key")
let session = URLSession.shared
print ("URLSession began...")
dataAttempted += 1
_ = session.dataTask(with: request, completionHandler: { data, response, error in
if let httpResponse = response as? HTTPURLResponse {
print("statusCode: \(httpResponse.statusCode)")
if httpResponse.statusCode != 200 {
print ("URLSession failed, trying again...")
self.urlSessionFailed += 1
self.selectRandomWord()
self.speakRandomWord()
}
}
DispatchQueue.main.async { [unowned self] in
self.activitySpinner.stopAnimating()
self.activitySpinner.isHidden = true
self.theUserInput.becomeFirstResponder()
self.repeatButton.isEnabled = true
self.defineButton.isEnabled = true
}
if let response = response,
let data = data,
let jsonData = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:AnyObject]
{
if
let dictionary = jsonData as? [String: Any],
let results = dictionary["results"] as? [[String: Any]],
!results.isEmpty, case var result = results[0],
let lexicalEntries = result["lexicalEntries"] as? [[String: Any]],
!lexicalEntries.isEmpty, case var lexicalEntry = lexicalEntries[0],
let derivatives = lexicalEntry["derivatives"] as? [[String:Any]],
!derivatives.isEmpty, case var derivative = derivatives[0],
let pronunciations = lexicalEntry["pronunciations"] as? [[String: Any]],
!pronunciations.isEmpty, case var pronunciation = pronunciations[0],
let entries = lexicalEntry["entries"] as? [[String:Any]],
!entries.isEmpty, case var entry = entries[0],
let senses = entry["senses"] as? [[String:Any]],
!senses.isEmpty, case var sense = senses[0],
let examples = sense["examples"] as? [[String:Any]],
!examples.isEmpty, case var example = examples[0]
//Now I pick out details from the JSON like audio, sample sentence, etc.
{
if let theText = example.removeValue(forKey:"text") {
self.theText = String(describing:theText)
print ("The value is \(self.theText)")
}
if let meaning = sense.removeValue(forKey:"short_definitions") {
self.newMeaning = String(describing:meaning)
print ("The short meaning is \(self.meaning)")
}
if let origin = entry.removeValue(forKey:"etymologies") {
self.newEtymology = String(describing:origin)
self.decoded2 = String(self.newEtymology.characters.filter({!"U0123456789".contains(String($0))}))
// print ("The etymology is \(self.decoded2)")
}
if let audioPath = pronunciation.removeValue(forKey:"audioFile") {
print ("MP3 audio file for \(self.word) exists")
print (audioPath)
self.audioPaths.append(audioPath as! String)
self.dataIsValid += 1
self.successfulWords.append(self.word)
self.path = String(describing:audioPath)
self.repeatPath = self.path
print (self.path)
if let url = URL.init(string: self.path) {
self.player = AVPlayer.init(url: url)
self.player.play()
}
}
else {
print ("No audio data for \(self.word)!")
self.dataFailed += 1
self.audioFailed.append(self.word)
self.selectRandomWord()
}
}
else {
print ("\(self.randomWord) failed, reason unknown!")
self.dataFailed += 1
self.failedOtherReason.append(self.word)
self.selectRandomWord()
}
}
}).resume()
}
// Console results:
//URL Session failures: 5
//API attempts: 41.0
//API successes: 15.0
//API failures: 21.0
//Successful Oxford API hit percentage: 36.5853658536585
//These 15 words succeeded: ["denunciation", "strategic", "strategic", "detract", "opaline", "opaline", "jurist", "exit", "throaty", "mother", "mother", "regroup", "early", "managerial", "recalcitrant"]
//These 0 words had no audio: []
//These words 21 words failed for an unknown reason: ["overlook", "lignin", "mandate", "evident", "anagrammatize", "avoirdupois", "extraterrestrial", "opaline", "cauterization", "malted", "clear", "snuggle", "fresno", "jurist", "streamline", "nonconforming", "trochee", "adventitia", "rudiment", "sculpturesque", "bicycle"]
There is not enough information to debug this specific problem but it is likely that the JSON response you are receiving has optional fields. Fields that are expected to be empty for certain responses. This part of your code,
if
let dictionary = jsonData as? [String: Any],
let results = dictionary["results"] as? [[String: Any]],
!results.isEmpty,
case var result = results[0],
let lexicalEntries = result["lexicalEntries"] as? [[String: Any]],
!lexicalEntries.isEmpty,
case var lexicalEntry = lexicalEntries[0],
let derivatives = lexicalEntry["derivatives"] as? [[String:Any]],
!derivatives.isEmpty,
case var derivative = derivatives[0],
let pronunciations = lexicalEntry["pronunciations"] as? [[String: Any]],
!pronunciations.isEmpty, case var pronunciation = pronunciations[0],
let entries = lexicalEntry["entries"] as? [[String:Any]],
!entries.isEmpty,
case var entry = entries[0],
let senses = entry["senses"] as? [[String:Any]],
!senses.isEmpty, case var sense = senses[0],
let examples = sense["examples"] as? [[String:Any]],
!examples.isEmpty,
case var example = examples[0]
//Now I pick out details from the JSON like audio, sample sentence, etc.
{
needs to be split to handle (and report) missing fields. That is 20 boolean tests that all must pass before your 'good' path executes. What is likely happening is that your dictionary accesses are failing. If for instance dictionary does not contain "lexicalEntries", then that will take the failure path and not return an empty array as you are expecting.
Confirm that the same words fail every time. Look at the actual JSON for failed words and make sure that every field is being reported.
UPDATE:
if let response = response,
let data = data,
let jsonData = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:AnyObject]
{
guard let dictionary = jsonData as? [String: Any] else {
print("Error: dictionary was not returned by endpoint")
return
}
guard let results = dictionary["results"] as? [[String: Any]] else {
print("Error: no results array returned by endpoint")
return
}
result = results.first
...
I have json request which show a list of cars. I used timer to refresh the request as well as the list every 10 seconds. The problem is that the data keep adding up to the array and make my application crashes. How can I clear the data before appending new data? What should I do?
let list = listdevices[indexPath.row] // error
if list.statusxe == "run" {
cell?.devnameLabel?.text = list.devname
cell?.addressLabel?.text = list.address
cell?.statusxeLabel?.textColor = UIColor(red: 1/255, green: 117/255, blue: 0/255, alpha: 1)
cell?.statusxeLabel?.text = "Đang chạy"
cell?.speedLabel?.text = "\(list.speed) km/h"
}
else if list.statusxe == "stop"{
cell?.devnameLabel?.text = list.devname
cell?.addressLabel?.text = list.address
cell?.statusxeLabel?.textColor = UIColor(red: 230/255, green: 6/255, blue: 6/255, alpha: 1)
cell?.statusxeLabel?.text = "Đang dừng"
cell?.speedLabel?.text = ""
}
else if list.statusxe == "expired"{
cell?.devnameLabel?.text = list.devname
cell?.addressLabel?.text = list.address
cell?.statusxeLabel?.textColor = UIColor.black
cell?.speedLabel?.textColor = UIColor.black
cell?.statusxeLabel.text = " "
cell?.speedLabel?.text = "hết hạn dịch vụ"
}
else if list.statusxe == "lost_gprs"{
cell?.devnameLabel?.text = list.devname
cell?.addressLabel?.text = list.address
cell?.statusxeLabel?.textColor = UIColor.red
cell?.statusxeLabel?.text = "Mất GPRS"
cell?.speedLabel?.text = ""
}
cell?.accessoryType = UITableViewCellAccessoryType.disclosureIndicator
return cell!
}
I have json request which show a list of cars. I used timer to refresh the request as well as the list every 10 seconds. The problem is that the data keep adding up to the array and make my application crashes. How can I clear the data before appending new data? What should I do?
let url = "http://api.vnetgps.com:8000/tracking"
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "GET"
request.setValue(token , forHTTPHeaderField: "token")
request.setValue(username, forHTTPHeaderField: "username")
request.setValue("-1", forHTTPHeaderField: "devid")
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: nil, delegateQueue: OperationQueue.main)
let task = session.dataTask(with: request, completionHandler: {(data, response, error) in
if (error != nil ) {
print("Error")
}
else {
self.listdevices.removeAll()
if let json = try? JSONSerialization.jsonObject(with: data!, options: []) as? [String: Any],
let items = json?["units"] as? [[String: Any]] {
for item in items {
var lat = item["latitude"] as? String
UserDefaults.standard.set(lat, forKey: "latitude")
var long = item["longitude"] as? String
UserDefaults.standard.set(long, forKey: "longitude")
// print("long", long)
var devid = item["devid"] as? String
UserDefaults.standard.set(devid, forKey: "devid")
var devname = item["devname"] as? String
UserDefaults.standard.set(devname, forKey: "devname")
var speed = item["speed"] as? String
UserDefaults.standard.set(speed, forKey: "speed")
var statustt = item["status"] as? String
UserDefaults.standard.set(statustt, forKey: "statusxe")
var drivername = item["drivername"] as? String
UserDefaults.standard.set(drivername, forKey: "drivername")
var address = item["address"] as? String
UserDefaults.standard.set(address, forKey: "address")
var direction = item["direction"] as? String
self.listdevices.append(Listdevices(statusxe: statustt! , speed: speed!, devid: devid!, devname: devname!, address: address!, latitude: lat!, longitude: long!, drivername: drivername!, direction: direction!))
// print("list",self.listdevices)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
})
task.resume()
}
The problem is probably that you empty the array while it is being used by the tableview.
Instead use map to replace the old array contents with the new contents. That way you don't need to empty the array first.
Something like:
self.listDevices = items.map { Listdevices($0) }
And then implement an initialiser for ListDevices like this:
init(device: [String: Any]) { ... }
A couple of unsolicited code review comments:
All the writes to UserDefaults are pointless since each iteration just overwrites the previous one, so you should just remove the whole loop.
Make statusxe into an enum StatusXE
Replace the long conditional with a switch based on the new StatusXE enum
Be careful with naming. The variable list is not a list, so don't call it that, instead maybe call it device. The variable listdevices should just be called devices or if you insist on using the word list, it should be deviceList. Also remember proper camelCasing.
Avoid force unwrapping. Possibly use the nil coalescing operator to provide default values for device properties that are null.
Avoid unnecessarily duplicating code. In the long conditional, the first two lines of code are repeated in every case. Just move those two lines out of the conditional.
I think it could be due to the async nature of your code.
try avoid self.listdevices.append and use something like :
// declare listdevices with var instead of let
let temp = [Listdevices]()
for item in items {
...
temp.append(Listdevices(statusxe: statustt! , speed: speed!, devid: devid!, devname: devname!, address: address!, latitude: lat!, longitude: long!, drivername: drivername!, direction: direction!))
}
self.listdevices = temp
How would I be able to check whether or not the downloaded JSON content contains an error message rather than the expected content? I've tried to validate the URL but that cannot work due to how a false subdomain (location in this case) still returns an Error Message through the JSON content. I'd appreciate it if anybody could help me out. (Note: I want to check for an invalid location entered by the user and I'm using OpenWeatherMap API.)
func downloadData(completed: #escaping ()-> ()) {
print(url)
//UIApplication.shared.openURL(url as URL)
Alamofire.request(url).responseJSON(completionHandler: {
response in
let result = response.result
if let dict = result.value as? JSONStandard, let main = dict["main"] as? JSONStandard, let temp = main["temp"] as? Double, let weatherArray = dict["weather"] as? [JSONStandard], let weather = weatherArray[0]["main"] as? String, let name = dict["name"] as? String, let sys = dict["sys"] as? JSONStandard, let country = sys["country"] as? String, let dt = dict["dt"] as? Double {
self._temp = String(format: "%.0f °F", (1.8*(temp-273))+32)
self._weather = weather
self._location = "\(name), \(country)"
self._date = dt
}
completed()
})
}
Assuming the resulting JSON has different content when there is an error, check dict for the error content. Below is an example assuming there is a key named error. Adjust as needed based on what you really get when there is an error.
Alamofire.request(url).responseJSON(completionHandler: {
response in
let result = response.result
if let dict = result.value as? JSONStandard {
if let error = dict["error"] {
// parse the error details from the JSON and do what you want
} else if let main = dict["main"] as? JSONStandard, let temp = main["temp"] as? Double, let weatherArray = dict["weather"] as? [JSONStandard], let weather = weatherArray[0]["main"] as? String, let name = dict["name"] as? String, let sys = dict["sys"] as? JSONStandard, let country = sys["country"] as? String, let dt = dict["dt"] as? Double {
self._temp = String(format: "%.0f °F", (1.8*(temp-273))+32)
self._weather = weather
self._location = "\(name), \(country)"
self._date = dt
} else {
// Unexpected content, handle as needed
}
}
completed()
})
You should also provide a parameter to your downloadData completion handler so you can pass back an indication of success or failure so the caller can handle the result appropriately.
I am making an app that connects to the TMDB API.
I use Hyper Sync to persist JSON to Core Data with the following code:
let completion = { (error: NSError?) in
if error == nil {
self.backdropData = [[String: AnyObject]]()
for image in movie.backdrops!.allObjects as! [Image] {
var data = [String: AnyObject]()
data[ThumbnailTableViewCell.Keys.ID] = image.filePath as String!
data[ThumbnailTableViewCell.Keys.OID] = image.objectID
if let filePath = image.filePath {
let url = "\(Constants.TMDB.ImageURL)/\(Constants.TMDB.BackdropSizes[0])\(filePath)"
data[ThumbnailTableViewCell.Keys.URL] = url
}
self.backdropData!.append(data)
}
self.posterData = [[String: AnyObject]]()
for image in movie.posters!.allObjects as! [Image] {
var data = [String: AnyObject]()
data[ThumbnailTableViewCell.Keys.ID] = image.filePath as String!
data[ThumbnailTableViewCell.Keys.OID] = image.objectID
if let filePath = image.filePath {
let url = "\(Constants.TMDB.ImageURL)/\(Constants.TMDB.BackdropSizes[0])\(filePath)"
data[ThumbnailTableViewCell.Keys.URL] = url
}
self.posterData!.append(data)
}
self.tableView.reloadData()
}
}
var data = [[String: AnyObject]]()
data.append(["id": movie.movieID!.integerValue,
"backdrops": backdrops,
"posters": posters])
let predicate = NSPredicate(format: "movieID=%# ", movie.movieID!)
Sync.changes(data, inEntityNamed: "Movie", predicate: predicate, dataStack: appDelegate.dataStack, completion: completion)
When Sync finished saving the JSON data, the backdrops are deleted and only the posters are saved in Core Data. This is not the behavior I wanted. I also want to save the backdrops in Core Data. Any way to fix this?
Here is the screenshot of my Core Data model:
For full source code, here is the link to my Github project:
Cineko
I have a JSON fetch happening and then I do stuff with the data. My JSONObject is created and then I go about working with the data. A sample can be seen here: https://openlibrary.org/api/books?bibkeys=1593243987&f&jscmd=data&format=json
My first block to extract the author name is working perfectly, however the second to extract the cover url as a string isn't even running and I have no idea why.
If I set a breakpoint at if let thumbs = bookDictionary["cover"] as? NSArray {it stops, but then when I 'step through' the code, it jumps to the end and moves on, not even running anything inside the block.
I would appreciate any help anyone can offer. I'm using Swift 2.0 / Xcode 7b6.
let requestURL = ("https://openlibrary.org/api/books?bibkeys=" + lookUpID + "&f&jscmd=data&format=json")
let url = NSURL(string: requestURL)
let req = NSURLRequest(URL: url!)
let dataTask = session.dataTaskWithRequest(req) {
(data, response, error) in
do {
let jsonObject = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary
if let bookDictionary: AnyObject = jsonObject!["\(self.lookUpID)"] {
// Retrieve the author name
var names = [String]()
if let authors = bookDictionary["authors"] as? NSArray {
for author in authors {
if let author = author as? NSDictionary,
let name = author["name"] as? String {
names.append(name)
}
}
}
// Retrieve cover url
var coverThumbURL: String = ""
if let thumbs = bookDictionary["cover"] as? NSArray {
// This code isn't running at all.
for thumb in thumbs {
if let thumb = thumb as? NSDictionary,
let thumbnail = thumb["medium"] as? String {
coverThumbURL = thumbnail
}
}
}
}
Thanks for the help. I did some looking around & fixed the casting.
var coverThumbURL: String = ""
if let thumbs = bookDictionary["cover"] as? NSDictionary {
let thumbnail = thumbs.valueForKey("medium") as? String
coverThumbURL = thumbnail!
}