使用 Swift 解析 JSON

Posted

技术标签:

【中文标题】使用 Swift 解析 JSON【英文标题】:Parsing JSON using Swift 【发布时间】:2014-08-08 18:39:43 【问题描述】:

我在一个自定义类中有一个异步方法,它返回一个包含字典的 NSArray。我正在尝试在 UITableView 中显示这些数据。一个问题是 getGenres 方法调用是异步的,因此数据会在表格显示后加载。主要问题是弄清楚如何实现这 3 个数据源方法。在 Objective C 中,这非常简单......

var genreList:NSArray?

override func viewDidLoad() 
    super.viewDidLoad()
    Bookshop.getGenres 
        genres in
            self.genreList = genres // copies data to the public NSArray
            self.tableView.reloadData() // this returns the data async so need to reload the tableview
            println("records: \(self.genreList?.count)") // displays the array of dictionaries correctly
    


// #pragma mark - Table view data source

override func numberOfSectionsInTableView(tableView: UITableView!) -> Int 
    return 1


override func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int 
    if self.genreList != nil 
        println("Rows in section: \(self.genreList?.objectAtIndex(0))")
        return self.genreList?.count! // Value of optional 'Int?' not wrapped; did you mean to use '!' 
    
    return 3


override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell? 
    let cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier("GenreCell", forIndexPath: indexPath) as UITableViewCell
    cell.textLabel.text = "Hello World!"
    println("ROW: \(indexPath.row)")
    println("data: \(self.genreList?.objectAtIndex(indexPath.row))")
    var item = self.genreList?.objectAtIndex(indexPath.row) Variable 'item' inferred to have type 'AnyObject?', which may be unexpected.
    //cell.textLabel.text = item.title
    //var item:NSDictionary = self.genreList?.objectAtIndex(indexPath.row) as NSDictionary
    //println("item: \(item)")
    return cell

我做错了什么?完成一项非常简单的任务似乎太复杂了。

这是我的实用程序类:

import Foundation

class Bookshop 
    class func getGenres(completionHandler: (genres: NSArray) -> ()) 
        //println("Hello inside getGenres")
        let urlPath = "http://creative.coventry.ac.uk/~bookshop/v1.1/index.php/genre/list"
        //println(urlPath)
        let url: NSURL = NSURL(string: urlPath)
        let session = NSURLSession.sharedSession()
        var resultsArray:NSArray!
        let task = session.dataTaskWithURL(url) 
            data, response, error in
            //println("Task completed")
            if(error) 
                println(error.localizedDescription)
            
            //println("no error")
            var err: NSError?
            var options:NSJSONReadingOptions = NSJSONReadingOptions.MutableContainers
            var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: options, error: &err) as NSDictionary
            if(err != nil) 
                println("JSON Error \(err!.localizedDescription)")
            
            //NSLog("jsonResults %@", jsonResult)
            let results: NSArray = jsonResult["genres"] as NSArray
            //NSLog("jsonResults %@", results)
            //resultsArray = results
            //println("calling completion handler")
            completionHandler(genres: results)
        
        task.resume()
    

【问题讨论】:

您遇到了什么具体问题? (另外,我在您的问题中看不到任何关于 JSON 解析的内容 - 当您正在对其进行编辑以使其更易于回答时,您可能会考虑将标题更改为更具描述性的内容。) 我无法提取 NSArray self.genreList.count 中的索引数给了我可选“Int?”的错误值未包装;你的意思是用'!'并且行 var item = ... 给我一个警告变量“item”推断为“AnyObject?”类型,这可能是意外的。 【参考方案1】:

swift 所做的只是消除了一堆我们真的不应该依赖的容易出错的功能,特别是依赖设置为零的消息总是容易出错。现在它有点冗长,但明显更明确,因此更安全。

objective-C 中return self.genreList.count 的等价物是:

override func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int 
    if let genres = self.genreList 
        return genres.count
    
    else 
        // Note: this is the case when you haven't yet downloaded your genre
        //  list, do whatever is appropriate, this just shows an empty list
        return 0
    

由于cellForRowAtIndexPath 只有在您的单元格计数不为零时才会被调用,因此该方法更简单,但实际上为了一致性/安全性,您遵循相同的一般流程:

override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell? 
    if let genres = self.genreList as? [NSDictionary] 
        let cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier("GenreCell", forIndexPath: indexPath) as UITableViewCell
        var item = genres[indexPath.row]
        cell.textLabel.text = item["title"] as String
        return cell
    
    else 
        // Note: again, this is the case where you don't have your genre list
        //  list yet, so you can create whatever default cells needed.
        return nil
    

【讨论】:

这里的基本原理很明确:当您真正需要的是检查已有的选项时,引入更多隐式展开的选项会使您的代码可读性降低且更容易出错。此外,您可以使用 beta 5 中引入的可选合并运算符来缩短第一种情况,将 numberOfRowsInSection 正文减少为一行:return self.genreList?.count ?? 0 是的,合并运算符是一种更简洁的表达方式。在这种情况下,我更多的是试图建立一个如何有效和清晰地处理选项的一般模式。【参考方案2】:

将 self.genreList 的声明从 var genreList:NSArray? 更改为 var genreList:NSArray!。然后您可以像在 Obj-C 中那样访问成员(没有问号)。也许,如果 getGenres 方法中的处理程序在另一个线程中,您需要编写

Bookshop.getGenres 
        genres in
            self.genreList = genres // copies data to the public NSArray
dispath_asynch(dispatch_get_main_queue) 
            self.tableView.reloadData() // this returns the data async so need to reload the tableview
            println("records: \(self.genreList.count)") // displays the array of dictionaries correctly

    

在你的 cellForRowAtIndexPath 中,你需要像这样转换项目:

var item = self.genreList[indexPath.row] as NSDictionary
dump(item) // just to show the dictionary in this scenario
cell.textLabel.text = item["title"] as String

另外:在 cellForRowAtIndexPath 中访问它之前,您必须检查 self.genreList != nil:

if genreList != nil 
var item = self.genreList[indexPath.row] as NSDictionary
    dump(item) // just to show the dictionary in this scenario
    cell.textLabel.text = item["title"] as String

else 
cell.textLabel.text = "Loading..."

【讨论】:

Ben,您的第一次更正很完美,但最后一点不起作用,因为每个索引都包含一个如下所示的字典: id = 9;记录 = 2; selfLink = "creative.coventry.ac.uk/~bookshop/v1.1/index.php/genre/id/9"; title = "食物和饮料"; 而不是作为 myGenre 对象的一个​​实例。我试图在适当的索引处检索字典,然后提取每个字符串和 int 以显示在表格视图单元格中。 第一行 var 项给出错误 'AnyObject' is not convertible to '[String : AnyObject]' 这很奇怪,使用 NSDictionary 怎么样?我还更改了代码看起来更酷:) 语法没有错误,但在该行崩溃并出现错误:致命错误:在展开可选值时意外发现 nil 那么在你的 getGenres 方法中,如果流派的类型到底是什么? (只需按住 alt 键点击)

以上是关于使用 Swift 解析 JSON的主要内容,如果未能解决你的问题,请参考以下文章

使用 Swift (NSArray) 解析 JSON

需要帮助使用 Alamofire 使用 Swift 解析 JSON

JSON数据未在swift3中使用Alamofire进行解析

使用 swift json 解析 json 数据

使用 alamofire 在 Swift 上解析 Json

使用 Swift 解析 JSON