玩转iOS开发 - JSON 和 Xml 数据解析

前言

Json 和xml是网络开发中常用的数据格式,JSON轻量级,xml相对较复杂,所以现在用JSON的比例非常大,基本上从服务器获取的返回数据都是JSON格式的,作为iOS开发者,解析JSON, XML文件是网络开发最基本的一步,不扯蛋了,直接进入正题。

JSON解析

JSON介绍

JSON 本质上,就是一个”特殊格式”的字符串

  • JSON 是网络上用来传输数据使用最广泛的数据格式,没有之一
  • JSON 出身草根,是 Javascript 的子集,专门负责描述数据格式

参考网站:http://www.w3cschool.cc

JSON 语法规则

  • 数据以 key/value 值对表示
  • 数据由逗号分隔
  • 花括号保存对象 (字典)
  • 方括号保存数组

JSON 存储值

  • 数字(整数或浮点数)
  • 字符串(在双引号中)
  • 逻辑值(true 或 false)
  • 数组(在方括号中)
  • 对象(在花括号中)
  • null

下面看条结构清晰的JSON数据,和上面JSON的特点对应一下:

玩转iOS开发 - JSON 和 Xml 数据解析_第1张图片

序列化 & 反序列化

  • 序列化:在向服务器发送数据之前,将 NSArray / NSDictionary 转换成二进制的过程
  • 反序列化:在从服务器接收到数据之后,将二进制数据转换成 NSArray / NSDictionary 的过程

JSON 反序列化

  • 虎嗅新闻接口
NSURL *url = [NSURL URLWithString:@"http://m.api.huxiu.com/portal/1/1?client_ver=6&push_type=iOSRel"];
//反序列化
id result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
选项 说明
NSJSONReadingMutableContainers = (1UL << 0) 容器可变
NSJSONReadingMutableLeaves = (1UL << 1) 叶子可变
NSJSONReadingAllowFragments = (1UL << 2) 顶级节点可以不是 NSArray 或者 NSDictionary
  • 在实际开发中,获得网络的数组或者字典之后,通常会做字典转模型!反序列化的结果是否可变并不重要

选项选择 0,表示任何附加操作都不做,效率最高!

NSJSONSerialization 类

  • 专门负责在 JSON 和 Foundation 对象直接转换的类

可以转换成 JSON 的 Foundation 对象需要具备的条件:

* 顶级节点是 NSArray 或者 NSDictionary
* 所有的对象是 NSString, NSNumber, NSArray, NSDictionary 或者 NSNull
* 所有字典的 key 是 NSString
* NSNumber 不是空或者无穷大

JSON解析例子

下面是一个简单的虎嗅新闻的例子:

  • 请求的URL: http://m.api.huxiu.com/portal/1/1?client_ver=6&push_type=iOSRel
  • 返回JSON数据并对JOSN数据解析
  • 打印出第一条新闻

我们先来看下URL实际返回的JSON是什么样子的:
玩转iOS开发 - JSON 和 Xml 数据解析_第2张图片

在线 JSON解析的网站:http://json.cn

示例代码用swift 写的,亲们看下Swift 代码是不是比OC漂亮多了,而且今年swift2.0就要开源了喽,博主难以掩饰对swift的喜爱,哈哈

有木有发现swift的代码使用起来和OC非常像,使用函数的名字基本一致,只不过那个讨厌的 [] 变成了让人喜爱的 . 语法,大爱!
如果对swift感兴趣,可以后续看下blog中swift系列的文章

好了言归正传,亲们看下下面的JSON解析是不是很简单:

func readJson() 
{
    //这个是虎嗅新闻看点的API
    let url = NSURL(string: "http://m.api.huxiu.com/portal/1/1?client_ver=6&push_type=iOSRel")!
    let request = NSURLRequest(URL: url)

    //用NSURLConnection 做异步请求
    NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) { (_, data, error) -> Void in
        if (data == nil || error != nil) 
        {
            println("网络不给力")
            return
        }

        //根据数据格式可以知道返回的是一个字典(有的网站会返回数组,比较少见)    
        let resultDic = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.allZeros, error: nil) as! NSDictionary

        //获取内容部分,内容的key-> 'content', value:是个数组
        let resultArray = resultDic["content"] as! NSArray

        //这里我们只看第一条新闻,也可以遍历数组查看所有的内容        
        let result = resultArray[0] as! NSDictionary;

        //根据key 提取我们想要的内容   
        let cityName = result["img"] as! String
        let author = result["author"] as! String
        let title = result["title"] as! String
        let summary = result["summary"] as! String

        println("虎嗅新闻 \n作者:\(author)\n标题:\(title) \n摘要:\(summary) ")
   }
}

上面结果输出:

虎嗅新闻
作者:虎嗅
标题:综艺节目大爆炸,电视台和视频网站过得好吗
摘要:要赚钱,还要领导放心,观众满意,可真够难的。

来看下手机app上的结果,是不是一样滴:

玩转iOS开发 - JSON 和 Xml 数据解析_第3张图片

注意:

上面请求使用的swift 闭包{ (_, data, error) -> Void in} 来进行JSON数据解析,代码执行的顺序为:readJson函数-> 网络异步请求-> readJson 函数执行完毕-> 服务器返回Json数据-> 回调闭包中代码块进行JSon数据解析。看吧,闭包和OC的block很想吧,实际上swift闭包功能更强大。

JSON解析-第三方框架

常见的 JSON 解析第三方框架

  • JSONKit(最快)
  • SBJson
  • TouchJSON

上面三个框架的性能依次降低!所以我们只说一下JSONKit

JSONKit

使用步骤步骤

  1. 下载框架 https://github.com/johnezang/JSONKit

  2. 导入框架文件

    • JSONKit.h
    • JSONKit.m
  3. 设置 MRC 标记(JSONKit 基于MRC的)

    • 选择”项目”-”Build Phases”-”Compile Sources”
    • 找到 JSONKit.m 并且在 Compiler Flags 中添加 -fno-objc-arc, 可以告诉编译器,编译 JSONKit.m 时不使用 ARC
  4. 修改错误

    • 利用自动修复功能,修改两处 isa 的错误
  5. 反序列化
    使用起来非常简单,创建一个decoder对象,调用解析方法就可以

id result = [[JSONDecoder decoder] objectWithData:data];

注意

  • JSONKit 只支持iOS5.0之前的系统,是MRC下的框架,所以新项目不要使用

  • JSONKit 2012已经停止更新,当时apple官方说JSONKit 的效率要高于系统自带的JSON解析,不过要知道苹果对于自身系统的优化更新是非常快的,目前的apple原生态的JSON解析已经完爆JSONKit了,如果你现在还在用他,建议换成系统自身的JSON解析。

  • 苹果新系统强制推广的速度非常快,随着更新,很多以前的东西会被更好的替代,NSURLConnection 就是一个例子,所以程序猿要跟上时代的潮流啊

XML解析

XML 介绍

  • XML的特点,出身名门,W3C制定,微软和IBM曾经共同大力推荐过的数据格式
  • XML 指可扩展标记语言(eXtensible Markup Language)
  • 被设计用来传输和存储数据

让我来看下XML数据,瞬间有木有想感觉信息量好大:


<Books>
  <Book id="1">
      <title>Circumferencetitle>
      <author>Nicholas Nicastroauthor>
      <summary>Eratosthenes and the Ancientsummary>
  Book>
  <Book id="2">
      <title>Copernicus Secrettitle>
      <author>Jack Repcheckauthor>
      <summary>How the scientific revolution begansummary>
  Book>
  <Book id="3">
      <title>Angels and Demonstitle>
      <author>Dan Brownauthor>
      <summary>Robert Langdon is summoned to a Swisssummary>
  Book>
Books>

XML 解析的方式

DOM 解析

  • 是在 MAC上使用的解析方式
  • 内存消耗极大,不适用于手机
  • iPhone无法直接使用 DOM 方式解析 XML

SAX 解析

  • 是只读的方式,从上向下的方式解析
  • 是苹果提供的解析方式
  • 速度快
  • NSXMLParser 通过代理实现解析

SAX 解析步骤

  1. 开始文档 - 准备工作
  2. 开始”节点”
  3. 发现节点内部的内容,每一个节点,可能需要多次才能找完
  4. 结束”节点”
  5. 结束文档 - 解析结束

以上步骤,2,3,4,会不断循环,一直到所有解析完成!

其实解析本质上就是不断循环遍历节点,获取节点内容,因为XML的节点格式是成对出现的,不过节点的名字是自定义的,这也是解析难点所在。

1. loadData

- (void)loadData {

    NSURL *url = [NSURL URLWithString:@"http://localhost/books.xml"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    // 异步解析 XML
    [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {

        // 1. 使用网络返回的二进制数据实例化 NSXMLParser 对象
        NSXMLParser *ZHParser = [[NSXMLParser alloc] initWithData:data];

        // 2. 设置代理,通过代理方法实现 SAX 解析
        ZHParser.delegate = self;

        // 3. 开始解析
        [ZHParser parse];
    }];
}

使用代理方法获取数据

只要没有碰到文档的结束符,解析器就会循化执行2,3,4方法,直到所以后的数据解析完

// MARK: - NSXMLParserDelegate
// 1. 开始文档 - 准备工作
- (void)parserDidStartDocument:(NSXMLParser *)parser {
    NSLog(@"1. 开始文档");
}

// 2. 开始节点
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
    NSLog(@"2. 开始节点 %@ %@", elementName, attributeDict);
}

// 3. 发现文字
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    NSLog(@"==> %@", string);
}

// 4. 结束节点
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    NSLog(@"4. 结束节点 %@", elementName);
}
// 5. 结束文档 - 解析结束
- (void)parserDidEndDocument:(NSXMLParser *)parser {
    NSLog(@"5. 解析结束");
}

// 6. 错误处理
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
    NSLog(@"解析错误 %@", parseError);
}

SAX & DOM 解析对比

SAX 特点

  • 只读
  • 从上向下
  • 速度快
  • 解析的时候相对比较繁琐,有5个代理方法,每个代理方法都要写一定代码
  • 适合大的 XML 文件解析

总的来说,SAX只在xml文档中查找特定条件的内容,并且只提取需要的内容。这样做占用内存小,灵活。

DOM 特点

  • 背景

    • 主要用在 PC 端或者服务器端
    • 苹果提供了 NSXML 类支持 DOM 方式的解析
    • 不过 NSXML 类只能用在 MAC 开发,在 iOS 中无法直接使用
    • DOM 方式不仅能解析 XML 文档,还能够修改: 增加节点/删除节点
  • 实现

    • 一次性将 XML 文档以树形结构读入内存
    • 横向的节点越多,内存消耗越大
    • 使用 DOM 解析适合于小的 XML 解析,并且能够动态维护
    • 有些第三方框架就提供了 DOM 方式的解析,GDataKissXML(XMPP)
    • 在 iOS 开发中,如果要使用 DOM 方式解析,最好只处理小的 XML

GData解析XML

GDataXMLNode是Google提供的用于XML数据处理的类集。该类集对libxml2-DOM处理方式进行了封装,能对较小或中等的XML文档进行读写操作且支持XPath语法, 原理上和DOM相同。

GData解析步骤

  • 获得根节点,依次 Log,一定要确认能够拿到所有子节点的内容
  • 横向节点越多,for的层次就越深
  • 根据实际的 XML 的情况,确认解析

准备工作

  • 访问 http://code.google.com/p/gdata-objectivec-client/source/browse/trunk/Source/XMLSupport/, 获得GDataXMLNode.h和GDataXMLNode.m 文件
  • 将GDataXMLNode.h/m文件添加到工程中
  • 向工程中增加“libxml2.dylib”库;
  • 在工程的“Build Settings”页中找到“Header Search Path”项,添加“/usr/include/libxml2”到其路径

GData使用示例

XML数据


<xml_api_reply version="1">
<cities>
<city>
<name data="保定"/>
<latitude_e6> 38849998latitude_e6>
<longitude_e6> 115569999longitude_e6>
city>
<city default="true" >
<name data="北京"/>
<latitude_e6> 39930000latitude_e6>
<longitude_e6> 116279998longitude_e6>
city>
<city>
<name data="沈阳"/>
<latitude_e6> 41770000latitude_e6>
<longitude_e6> 123430000longitude_e6>
city>
<city>
<name data="成都"/>
<latitude_e6> 30670000latitude_e6>
<longitude_e6> 104019996longitude_e6>
city>
<city>
<name data="大连"/>
<latitude_e6> 38900001latitude_e6>
<longitude_e6> 121629997longitude_e6>
city>
<city>
<name data="福州"/>
<latitude_e6> 26079999latitude_e6>
<longitude_e6> 119279998longitude_e6>
city>
cities>
xml_api_reply>

解析方法

首先分析下数据层次结构:

 :  xml_api_reply>
<cities> : cities>
<city> : city>

根据上面的节点来解析数据,要有两个for循环

- (void)parseCitys
{
    NSData *xmlCitysData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"citys" ofType:@"xml"]];

    // 读取data在内存中形成完整的树形结构
    NSError * error = nil;
    GDataXMLDocument * documents = [[GDataXMLDocument alloc]initWithData:xmlCitysData options:0 error:&error];

    // 取得根节点
    GDataXMLElement *rootNode = [documents rootElement];

    //获取根节点xml_api_reply的数组,包含的是cities,如果根节点不知有一个数组,就又要遍历
    NSArray * citiesArray = [rootNode elementsForName:@"cities"];
    for (int i = 0; i < [citiesArray count]; i++) {

        // 取得单个cities节点
        GDataXMLElement * cities = [citiesArray objectAtIndex:i];

        //获取节点cities的数组,包含的是city
        //看出规律了吧,通过当前GDataXMLElement和子节点的name,可以获取子节点的数组
        NSArray * cityArray = [cities elementsForName:@"city"];
        for (int j = 0; j < [cityArray count]; j++) {

            // 取得单个city节点
            GDataXMLElement * city = [cityArray objectAtIndex:j];

            //节点下面是3个并行的节点,都是1个,直接通过lastObject来取得想要的值
            NSString * name = [[[[city elementsForName:@"name"]lastObject] attributeForName:@"data"] stringValue];
            NSLog(@"name = %@",name);

            NSString * latitude_e6 = [[[city elementsForName:@"latitude_e6"]lastObject] stringValue];
            NSLog(@"latitude_e6 = %@",latitude_e6);

            NSString * longitude_e6 = [[[city elementsForName:@"longitude_e6"]lastObject] stringValue];
            NSLog(@"longitude_e6 = %@",longitude_e6);   
        }       
    }   
}

PList解析

PList 主要在苹果开发中常用,比如那个新建项目都会自带一个info.plist,其实所有的数据格式都是相通的,无非存储数组,字典,对象,好的数据格式就是易于存储,易于读写。这里最后简单介绍下plist, 毕竟大部分服务器后台并不会返回 PList 的数据格式。

/**
 参数
 1. data:   要反序列化的二进制数据
 2. option: 选项,位移枚举类型
     NSPropertyListImmutable = 0,                   不可变
     NSPropertyListMutableContainers = 1,           容器可变
     NSPropertyListMutableContainersAndLeaves = 2   容器和叶子可变
 3. format: 如果不希望知道格式,传入 NULL 即可
 4. error:  错误
 */
id result = [NSPropertyListSerialization propertyListWithData:data options:0 format:NULL error:NULL];

后续阅读

  • JSON与XML的区别比较
    http://blog.csdn.net/hmt20130412/article/details/24048297

  • 2.XML解析类库对比和安装说明
    http://blog.csdn.net/hmt20130412/article/details/24048719

你可能感兴趣的:(IOS)