Swift Xml Rss Reader... Make It Async

Can't Call Segue from Async Function like XMLParser

Perhaps perform segue from main thread will do the job:

func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?)
{
DispatchQueue.main.async {
performSegue(withIdentifier: "userDidRequestReset", sender: nil)
}
}

XML Parser blocking main thread and dispatch_async crashing app

Print your array. Check it twice. I think you are getting an empty array. Print every object in the console which you get from server and which you parse. So you will get idea.

Update:

Just reload the table data on the main thread and don't parse data on it. like:

dispatch_async(dispatch_get_main_queue(), ^{
[messageList reloadData];
});

Put the other code outside the main thread.

Trying to parse XML from URL to PickerView with swift

There are a whole bunch of issues:

  1. You have set your delegate after you parsed, whereas you must do this before you call parse().

  2. You have some of your delegate methods implemented inside didStartElement. These need to be top level methods of your parser delegate.

  3. Minor unrelated issue, but your class name of Categories isn't quite right, because it represents a single "category", not many. So I'd rename this to be Category. (I'd personally also make it a struct, but that's up to you.)

  4. Your id and name values are not attributes of the category element. They are their own elements, so you have to parse them separately (using foundCharacters and build the Category object on didEndElement of category.

  5. The id of the categories appears to be an integer, so I'd make the id of Category an Int.

  6. You're calling parse() of a XMLParser that is using a URL from the main thread. That's inadvisable because you're blocking the main thread while it does the request. Personally, I'd use URLSession to request the data asynchronously, process it on a background queue, and only dispatch the final update of the model object and UI update to the main queue.

    Personally, I'd adopt asynchronous patterns myself with @escaping completion handlers, to help isolate UI updates that I trigger after a parse from the parsing itself

  7. A stylistic matter, but I wouldn't put all of this XMLParserDelegate code in the view controller. At the very least, put it in an extension so it's nicely organized. Even better, define a stand-alone parser delegate object in the spirit of the single responsibility principle and ensuring that you're not accidentally updating view controller model objects while the UI might be referring to it. It more safely ensures thread-safety and keeps your code more nicely encapsulated.

Pulling this all together, you could do something like:

struct Category {
let id: Int
let name: String
}

class ViewController: UIViewController {
@IBOutlet weak var pickerView: UIPickerView!

var categories = [Category]()

override func viewDidLoad() {
super.viewDidLoad()

startRequestAndParse() { categories, error in
guard let categories = categories, error == nil else {
print(error?.localizedDescription ?? "Unknown error")
return
}

// if you got here, everything is OK, so update model and UI on main thread

DispatchQueue.main.async {
self.categories = categories
print(self.categories)

// trigger whatever UI update you want here, too;

self.pickerView.reloadAllComponents()
}
}
}

/// Initiate request from server and parse results
///
/// - Parameters:
/// - completion: This is called when the request/parsing is done. This may be called
/// on background thread. If parsing failed, the array of categories
/// will be `nil` and we should have `error`.
/// - categories: First parameter of the `completion` closure is the array of `Category` objects, or `nil` on error.
/// - error: Second parameter of the `completion` closure is the resulting `Error`, if any.

private func startRequestAndParse(completion: @escaping (_ categories: [Category]?, _ error: Error?) -> Void) {
let url = URL(string: "http://thecatapi.com/api/categories/list")!
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data, error == nil else {
completion(nil, error)
return
}

// ok, now parse

let parser = XMLParser(data: data)
let delegate = ParserDelegate()
parser.delegate = delegate
parser.parse()

completion(delegate.categories, parser.parserError)
}
task.resume()
}
}

// this assumes you set the picker's `delegate` to be the view controller (either in IB or programmatically in `viewDidLoad`

extension ViewController: UIPickerViewDelegate {
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return categories[row].name
}
}

// this assumes you set the picker's `dataSource` to be the view controller (either in IB or programmatically in `viewDidLoad`

extension ViewController: UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}

func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return categories.count
}
}

/// Parser delegate for categories

class ParserDelegate: NSObject, XMLParserDelegate {
private var id: Int?
private var name: String?
private var value: String?

var categories: [Category]?

// initialize `categories`

func parserDidStartDocument(_ parser: XMLParser) {
categories = []
}

// if `id` or `name`, initialize `value` so we'll capture the appropriate value

func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
if elementName == "id" || elementName == "name" {
value = ""
}
}

// if `value` is not `nil`, go ahead and concatenate the additional characters

func parser(_ parser: XMLParser, foundCharacters string: String) {
value? += string
}

// if `id` or `name`, update the appropriate property
// if `category`, build a `Category` object and add it to our array
// regardless, reset `value` to `nil`

func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
switch elementName {
case "id":
if let value = value {
id = Int(value)
}
case "name":
name = value
case "category":
if let id = self.id, let name = self.name {
categories!.append(Category(id: id, name: name))
}
id = nil
name = nil
default:
()
}
value = nil
}

// if any error, reset `categories` so caller knows there was an issue

func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
categories = nil
}

}


Related Topics



Leave a reply



Submit