I created a class as shown in the code below, and as u can see I am parsing a JSON file in the class outside the viewController.
When I create the AllCards object in the view controller obviously return 0 at the beginning but after a while it returns the correct number of cards.
here my questions:
1) How can I wait the object creation before the viewDidLoad so at the view did load the AllCard object will return the correct number of cards?
2) If I add a button in the viewController updating the number of cards it freezes until all the cards have been created. I think because in my code everything is in the main queue. How can I resolve that?
3) Is it a good practice parsing JSON in a separate class like I did?
AllCards class:
import Foundation
import Alamofire
import SwiftyJSON
class AllCards {
var allCard = [Card]()
let dispatchGroup = DispatchGroup()
//gzt the JSON with Alamofire request
let allCardsHTTP: String = "https://omgvamp-hearthstone-v1.p.mashape.com/cards?mashape"
init() {
dispatchGroup.enter()
Alamofire.request(allCardsHTTP, method: .get).responseJSON { (response) in
if response.result.isSuccess {
let jsonCards : JSON = JSON(response.value!)
print("success")
//create the cards
if jsonCards["messagge"].stringValue != "" {
print(jsonCards["message"].stringValue)
}
else {
for (set, value) in jsonCards {
if jsonCards[set].count != 0 {
for i in 0...jsonCards[set].count - 1 {
let card = Card(id: jsonCards[set][i]["cardId"].stringValue, name: jsonCards[set][i]["name"].stringValue, cardSet: set, type: jsonCards[set][i]["type"].stringValue, faction: jsonCards[set][i]["faction"].stringValue, rarity: jsonCards[set][i]["rarity"].stringValue, cost: jsonCards[set][i]["cost"].intValue, attack: jsonCards[set][i]["attack"].intValue, durability: jsonCards[set][i]["durability"].intValue, text: jsonCards[set][i]["text"].stringValue, flavor: jsonCards[set][i]["flavor"].stringValue, artist: jsonCards[set][i]["artist"].stringValue, health: jsonCards[set][i]["health"].intValue, collectible: jsonCards[set][i]["collectible"].boolValue, playerClass: jsonCards[set][i]["playerClass"].stringValue, howToGet: jsonCards[set][i]["howToGet"].stringValue, howToGetGold: jsonCards[set][i]["howToGetGold"].stringValue, mechanics: [""], img: jsonCards[set][i]["img"].stringValue, imgGold: jsonCards[set][i]["imgGold"].stringValue, race: jsonCards[set][i]["race"].stringValue, elite: jsonCards[set][i]["elite"].boolValue, locale: jsonCards[set][i]["locale"].stringValue)
if jsonCards[set][i]["mechanics"].count > 0 {
for n in 0...jsonCards[set][i]["mechanics"].count - 1 {
card.mechanics.append(jsonCards[set][i]["mechanics"][n]["name"].stringValue)
}
}
else {
card.mechanics.append("")
}
self.allCard.append(card)
}
}
else {
print("The set \(set) has no cards")
}
}
print(self.allCard.count)
}
}
else {
print("No network")
}
self.dispatchGroup.leave()
}
}
}
View Controller:
import UIKit
class ViewController: UIViewController {
let allcards = AllCards()
let mygroup = DispatchGroup()
@IBAction func updateBtn(_ sender: Any) {
print(allcards.allCard.count) //Button is frozen until all the cards have been created then it shows the correct number of cards
}
override func viewDidLoad() {
super.viewDidLoad()
print(allcards.allCard.count) / This returns 0
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Here is an example of completion handler. First you have to write a function in a single class ex: APICall
func getDataFromJson(allCardsHTTP: String, completion: @escaping (_ success: Any) -> Void) {
Alamofire.request(allCardsHTTP, method: .get).responseJSON { response in
if response.result.isSuccess {
completion(response)
}
}
}
and call this method from any class.
let callApi = APICall()
callApi.getDataFromJson(allCardsHTTP: "https://omgvamp-hearthstone-v1.p.mashape.com/cards?mashape",completion: { response in
print(response)
})
1) if you pass an object via a UIStoryboard segue, it's set before viewDidLoad()
is called. However if you want to wait for UI elements to be ready, I generally go for a didSet
on the UI element, you could add a guard
statement checking your object in there if you want.
2) first of all you'll probably want a closure so maybe read 3) first. you're using dispatchGroup.enter()
here, DispatchQueue.global.async { }
is the usual way to accomplish what you're doing. Add in a DispatchQueue.main.async { }
when done if you want, or dip into the main thread in the view controller, up to you really. Look into the differences between [unowned self]
and [weak self]
when you have the time.
3) Give your Card
object an init(from: JSON)
initialiser where it parses its properties from the JSON object you're passing it. Let the function responsible for the download (Alamofire in your case) live in a different class (like for example APIClient
) and give it a download function with a closure in the argument list like completion: ((JSON?) -> ())?
to return the JSON
object. Let that class download a JSON object and then initialise your Card
object with the init(from: JSON)
initializer you wrote earlier. Note that this isn't an approach fit for use with Core Data NSManagedObjects so if you'll need local storage maybe keep that in mind.
In the end you should be able to build an array of Cards something like this:
APIClient.shared.fetchCards(completion: { cardJSONs in
let cards = [Card]()
for cardJSON: JSON in cardJSONs {
let card = Card(from; JSON)
cards.append(card)
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.