iOS系统中XML&JSON解析的代码实现
2015-12-01 14:01
751 查看
本文主要介绍了XML和JSON数据解析的基本知识,并展示了NSXMLParser方法、GDataXML第三方库以及NSJSONSerialization方法的实际运用案例。
XML和JSON是目前Web开发中经常用到的标记语言。这两者主要用于Web开发中标记数据结构。以微博为例,每一条微博都有_Logo_, 作者, 时间, 正文, 转发数, 回复, 点赞数 等项目。这些数据在网络中都是按一定的结构存储的。
在iOS开发中,往往需要将网络中的数据下载到本地,然后按一定的逻辑予以呈现。这个过程就是常说的__解析__。
一、语言简介
1.XML
XML是_可扩展标记语言(Extensible Markup Language)_的缩写,其中的_标记(markup)_是关键部分。XML语言将文件内容用限定标记进行标记,从而使每个单词、短语或段落成为可识别、可分类的信息。更多内容可参考XML 新手入门基础知识和XML 简介。举个栗子,这个栗子节选自这里。<?xml version="1.0" encoding="ISO-8859-1"?> <CATALOG> <CD> <TITLE>Empire Burlesque</TITLE> <ARTIST>Bob Dylan</ARTIST> <COUNTRY>USA</COUNTRY> <COMPANY>Columbia</COMPANY> <PRICE>10.90</PRICE> <YEAR>1985</YEAR> </CD> <CD> <TITLE>Hide your heart</TITLE> <ARTIST>Bonnie Tyler</ARTIST> <COUNTRY>UK</COUNTRY> <COMPANY>CBS Records</COMPANY> <PRICE>9.90</PRICE> <YEAR>1988</YEAR> </CD> 。。。 </CATALOG>
这是一个CD专辑列表,其中包含很多CD专辑,每张专辑给出了_标题_, 作者, 国家, 出版方, 价格, 年份 等信息。其中每一个信息块的前后两端都被标记。标题被
<TITLE>和
</TITLE>标记为_元素属性_,专辑被
<CD>和
</CD>标记为_子元素_,而整张列表被
<CATALOG>和
</CATALOG>标记为_根元素_。整个XML文件可以是TXT文本文件,可以跨系统读写。
2.JSON
JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式。它基于ECMAScript的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C、C++、C#、Java、JavaScript、Perl、Python等)。这些特性使JSON成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成(网络传输速率)。来自百度百科{ "book1": { "type": "textbook", "pages": "256", "title": "Programming Pearls 2nd Edition", "description": "The first edition of Programming Pearls was one of the most influential books I read early in my career...", "rating": "4.5", "coverType": "paperback", "genre": "Computer Science", "author": "Jon Bentley", "publisher": "Addison-Wesley Professional", "copyright": "1999" }, "book2": { ... }, ... }
如上例所示,JSON采用 数组 和 键值对 的形式标记数据结构。
3.区别在哪
(1)JSON的效率更高
在这篇文章中,作者对XML和JSON的解析效率进行了测试。结果表明__相对XML,JSON的解析速度提高了30%,占用空间少30%__。(2)XML有更多的解析方式
XML目前设计了两种解析方式:DOM(Document Object Model文档对象模型)方式。解析时需要将XML文件整体读入,并且将XML结构化成树状,使用时再通过树状结构读取相关数据,查找特定节点,然后对节点进行读或写。该方式把一个数据交换格式XML看成一个DOM对象,需要把XML文件整个读入内存,这一点上JSON和XML的原理是一样的,但是XML要考虑父节点和子节点,这一点上__JSON的解析难度要小__很多,因为JSON构建于两种结构:key/value,键值对的集合;值的有序集合,可理解为数组;
SAX(Simple API for XML)方式。基于事件驱动的解析方式,逐行解析数据。这一方式不需要整个读入文档就可以对解析出的内容进行处理,是一种逐步解析的方法。程序也可以随时终止解析。这样,一个大的文档就可以逐步的、一点一点的展现出来,所以SAX适合于__大规模的解析__。这一点,JSON目前是做不到得。
总体而言:__JSON__只提供整体解析方案,而这种方法只在解析较少的数据时才能起到良好的效果;__XML__提供了对大规模数据的逐步解析方案,这种方案很适合于对大量数据的处理。
二、在iOS中的解析
1.XML解析方式简介
iOS中苹果官方提供了NSXMLParser和libxml2两种XML解析方式,同时也有第三方库TBXML、TouchXML、KissXML、TinyXML、GDataXML可以执行XML解析。其中NSXMLParser采用SAX方式解析;libxml2为基于C语言API的开源库,可以提供DOM和SAX两种解析方式,但使用比NSXMLParser麻烦。TBXML、TouchXML、KissXML、TinyXML、GDataXML等第三方库均采用DOM方式。__GDataXML__由Google基于libxml2重新封装得到。大神_Ray Wenderlich_在文章XML Tutorial for iOS: How To Choose The Best XML Parser for Your iPhone Project中描述了对各个方法的测试。作者总结认为:对于__读取__小型XML文档,__TouchXML, KissXML, GDataXML__足矣;如果是__读写__小型XML文档,__KissXML和GDataXML__都不错;对于大型XML文档,__libxml2 SAX, TBXML, libxml2 DOM__更好。
尽管NSXMLParser表现逊于libxml2 SAX,但胜在方便,无需调用第三方库。根据原文的推荐,加上手边现有的资料,最后我决定学习NSXMLParser和GDataXML两种方式。
####(1)NSXMLParser解析的代码实现
任务目标:(下同)采用NSXMLParser方法解析前文XML样例,并将得到的CD信息在UITableView中列出来。
主控制器头文件,注意声明代理
#import <UIKit/UIKit.h> @interface CDListTableViewController : UITableViewController <NSXMLParserDelegate> @property (strong, nonatomic) NSMutableArray *dataSource;//存放解析得到的数据 @property (strong, nonatomic) NSString *startTag; @end
根据要获得的CD信息,自定义了CD类
#import <Foundation/Foundation.h> @interface CD : NSObject @property (strong, nonatomic) NSString *title; @property (strong, nonatomic) NSString *artist; @property (strong, nonatomic) NSString *country; @property (strong, nonatomic) NSString *company; @property (strong, nonatomic) NSString *price; @property (strong, nonatomic) NSString *year; @end @implementation CD @synthesize title,artist,country,company,price,year; @end
主控制器的实现文件
#import "CDListTableViewController.h" #import "CD.h" @implementation CDListTableViewController - (void)viewDidLoad { [super viewDidLoad]; UITableView *tableView = (UITableView *)[self.view viewWithTag:1]; UIEdgeInsets contentInset = tableView.contentInset; contentInset.top = 20; tableView.contentInset = contentInset; tableView.delegate = self; tableView.dataSource = self; self.dataSource = [[NSMutableArray array]init]; NSXMLParser *aParser = [[NSXMLParser alloc]initWithContentsOfURL:[NSURL URLWithString:@"http://www.w3school.com.cn/example/xmle/cd_catalog.xml"]]; aParser.delegate = self; [aParser parse];//开始解析 aParser = nil;//释放内存 } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } #pragma mark - XML Parse - (void)parserDidStartDocument:(NSXMLParser *)parser { //开始解析整个文档时调用 NSLog(@"-START-"); } - (void)parserDidEndDocument:(NSXMLParser *)parser { //结束解析整个文档时调用 NSLog(@"-END-"); } - (void)parser:(NSXMLParser *)parser didStartElement:(nonnull NSString *)elementName namespaceURI:(nullable NSString *)namespaceURI qualifiedName:(nullable NSString *)qName attributes:(nonnull NSDictionary<NSString *,NSString *> *)attributeDict { //当解析遇到开始标签时调用 NSLog(@"Did-START"); self.startTag = elementName; if ([elementName isEqual:@"CD"]) { CD *newCD = [[CD alloc]init]; [self.dataSource addObject:newCD]; NSLog(@"self.dataSource has %lx Objects",[self.dataSource count]); } } - (void)parser:(NSXMLParser *)parser didEndElement:(nonnull NSString *)elementName namespaceURI:(nullable NSString *)namespaceURI qualifiedName:(nullable NSString *)qName attributes:(nonnull NSDictionary<NSString *,NSString *> *)attributeDict { //当解析遇到结束标签时调用 NSLog(@"Did-END"); self.startTag = nil; } - (void)parser:(NSXMLParser *)parser foundCharacters:(nonnull NSString *)string { //为新建的CD类实例添加信息 NSLog(@"FOUND %@",string); CD *currentCD = [self.dataSource lastObject]; if ([self.startTag isEqualToString:@"TITLE"]) { currentCD.title = string; }else if ([self.startTag isEqualToString:@"ARTIST"]){ currentCD.artist = string; }else if ([self.startTag isEqualToString:@"COUNTRY"]){ currentCD.country = string; }else if ([self.startTag isEqualToString:@"COMPANY"]){ currentCD.company = string; }else if ([self.startTag isEqualToString:@"PRICE"]){ currentCD.price = string; }else if ([self.startTag isEqualToString:@"YEAR"]){ currentCD.year = string; } self.startTag = nil; } #pragma mark - Table view data source - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.dataSource count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CDCellID" forIndexPath:indexPath]; if (cell == nil) { cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"CDCellID"]; } CD *currentCD = [[CD alloc]init]; currentCD = [self.dataSource objectAtIndex:indexPath.row]; cell.textLabel.text = currentCD.title; cell.detailTextLabel.text = currentCD.artist; //这里只提取了两组数据,如要显示更多信息请自定义cell return cell; } @end
(2)GDataXML解析的代码实现
准备工作首先从这里得到GDataXMLNode.h和GDataXMLNode.m文件,并导入到当前工程中。
其次,因为GDataXML是基于libxml2封装得到,因此还要导入libxml2库文件:在Linked Frameworks and Libraries点击加号然后搜索libxml2,双击文件即可导入。
接下来,在Build Settings中搜索“Header Search Paths”,将其值设置为 ${SDK_DIR}/usr/include/libxml2。否则会收到报错:
libxml/tree.h not found。
最后一步,要将BuildSettings中的Objective-C Automatic Reference Counting设置为NO。否则会收到有关ARC的报错。
开始工作
刚才关闭了ARC,注意做好代码的内存管理工作。
#import "CDListTableViewController.h" #import "GDataXMLNode.h" //导入第三方库 #import "CD.h" @implementation CDListTableViewController - (void)viewDidLoad { [super viewDidLoad]; UITableView *tableView = (UITableView *)[self.view viewWithTag:1]; UIEdgeInsets contentInset = tableView.contentInset; contentInset.top = 20; tableView.contentInset = contentInset; tableView.delegate = self; tableView.dataSource = self; self.dataSource = [[NSMutableArray array]init]; //导入整个XML文件 GDataXMLDocument *aDocument = [[GDataXMLDocument alloc]initWithXMLString:[NSString stringWithContentsOfURL:[NSURL URLWithString:@"http://www.w3school.com.cn/example/xmle/cd_catalog.xml"] encoding:NSUTF8StringEncoding error:nil] options:0 error:nil]; //标记根元素和子元素 GDataXMLElement *rootElement = [aDocument rootElement]; NSArray *subElement = [rootElement elementsForName:@"CD"]; //读取子元素 for (GDataXMLElement *anElement in subElement) { CD *newCD = [[CD alloc]init]; newCD.title = [[[anElement elementsForName:@"TITLE"] firstObject] stringValue]; newCD.artist = [[[anElement elementsForName:@"ARTIST"] firstObject] stringValue]; newCD.country = [[[anElement elementsForName:@"COUNTRY"] firstObject] stringValue]; newCD.company = [[[anElement elementsForName:@"COMPANY"] firstObject] stringValue]; newCD.price = [[[anElement elementsForName:@"PRICE"] firstObject] stringValue]; newCD.year = [[[anElement elementsForName:@"YEAR"] firstObject] stringValue]; [self.dataSource addObject:newCD]; [newCD release]; } } ///////////////////////////////////////////// //其余代码与上例类似,只要注意做好内存管理工作即可/// ///////////////////////////////////////////// - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } #pragma mark - Table view data source - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.dataSource count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CDCellID" forIndexPath:indexPath]; if (cell == nil) { cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"CDCellID"]; } CD *currentCD = [[CD alloc]init]; currentCD = [self.dataSource objectAtIndex:indexPath.row]; cell.textLabel.text = currentCD.title; cell.detailTextLabel.text = currentCD.artist; [currentCD release]; return [cell autorelease]; } @end
更多有关GDataXML解析的代码实现,可以参考XML Tutorial for iOS: How To Read and Write XML Documents with GDataXML。
2.JSON解析方式简介
苹果原生提供NSJSONSerialization解析方式,也有第三方选择:JSONKit,SBJSON,TouchJSON。根据iOS中四种JSON解析效率比较一文,原生的NSJSONSerialization方法是最佳选择,JSONKit是次优选择。NSJSONSerialization解析的代码实现
任务目标: 利用娱乐花边API实现NSJSONSerialization解析。目标分析:
JSON文档有两种结构: __对象:__以“{“开始,以”}”结束,是“名称/值”对儿的集合。名称和值中间用“:”隔开。多个“名称/值”对之间用“,”隔开。类似Objective-C的NSDictionary。 __数组:__以“["开始,以“]”结束,中间是数据。数据以“,”分割。类似Objective-C的NSArray。不同的JSON结构有不同的转化方式。
JSON格式与Objective-C转化对照表
JSON | Objective-C |
---|---|
大括号{} | NSDictionary |
中括号[] | NSArray |
双引号 "" | NSString |
数字{} | NSNumber |
{ "0": { "time": "2015-07-21 19:51", "title": "太忙找不到好男人?你要学学Angelababy", "description": "太忙找不到好男人?你要学学Angelababy...", "picUrl": "http://img1.gtimg.com/ent/pics/hv1/33/0/1885/122572158_small.png", "url": "http://ent.qq.com/a/20150721/049132.htm" }, "1": { "time": "2015-07-21 19:13", "title": "刘昊然晒中戏录取通知书 意外暴露接地气本名", "description": "刘昊然晒中戏录取通知书 意外暴露接地气本名...", "picUrl": "http://img1.gtimg.com/ent/pics/hv1/187/252/1884/122571547_small.jpg", "url": "http://ent.qq.com/a/20150721/048494.htm" }, 。。。 "code": 200, "msg": "ok" }
考察本例,返回对象是一个对象的两级嵌套,转换为Objective-C应该是两级字典。
代码实现
针对返回数据,新建了item类。简单起见,就只提取返回数据的标题和URL两个属性。
#import <Foundation/Foundation.h> @interface item : NSObject @property (strong, nonatomic) NSString *title; @property (strong, nonatomic) NSString *url; @end @implementation item @synthesize title,url; @end
向主控制器代码中导入item.h文件。这代码关闭了ARC,因此有手动管理内存代码。
#import <UIKit/UIKit.h> #import "item.h" @interface ListTableViewController : UITableViewController @property (strong, nonatomic) NSMutableArray *dataSource; @end @implementation ListTableViewController - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)viewDidLoad { [super viewDidLoad]; UITableView *tableView = (UITableView *)[self.view viewWithTag:1]; UIEdgeInsets contentInset = tableView.contentInset; contentInset.top = 20; tableView.contentInset = contentInset; tableView.delegate = self; tableView.dataSource = self; self.dataSource = [[NSMutableArray array]init]; //API提供了URL和 request: withHttpArg: 方法 NSString *httpUrl = @"http://apis.baidu.com/txapi/huabian/newtop"; NSString *httpArg = @"num=10&page=1"; [self request: httpUrl withHttpArg: httpArg]; } - (void)request: (NSString*)httpUrl withHttpArg: (NSString*)HttpArg { NSString *urlStr = [[NSString alloc]initWithFormat: @"%@?%@", httpUrl, HttpArg]; NSURL *url = [NSURL URLWithString: urlStr]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL: url cachePolicy: NSURLRequestUseProtocolCachePolicy timeoutInterval: 10]; [request setHTTPMethod: @"GET"]; //下句中(MY-API-KEY)应为使用者自己的API Key [request addValue: @" (MY-API-KEY) " forHTTPHeaderField: @"apikey"]; [NSURLConnection sendAsynchronousRequest: request queue: [NSOperationQueue mainQueue] completionHandler: ^(NSURLResponse *response, NSData *data, NSError *error){ if (error) { NSLog(@"Httperror: %@%ld", error.localizedDescription, error.code); } else { NSInteger responseCode = [(NSHTTPURLResponse *)response statusCode]; NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"HttpResponseCode:%ld", responseCode); NSLog(@"HttpResponseBody %@",responseString); //这句是自己添加的,执行对返回数据的处理 [self reLoadTableViewWith:data]; } }]; } - (void)reLoadTableViewWith:(NSData *)data { //生成一级字典 NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil]; //遍历一级字典 for (int k = 0; k<= [dict count]; k++) { NSString *count =[NSString stringWithFormat:@"%i",k]; NSDictionary *subDict = dict[count];//生成二级字典 item *newItem = [[item alloc]init]; newItem.title = subDict[@"title"]; newItem.url = subDict[@"url"]; [self.dataSource addObject:newItem]; [newItem release]; } [self.tableView reloadData]; } #pragma mark - Table view data source - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.dataSource count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"itemCellID" forIndexPath:indexPath]; if (cell == nil) { cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"itemCellID"]; } item *currentItem = [[item alloc]init]; currentItem = [self.dataSource objectAtIndex:indexPath.row]; cell.textLabel.text = currentItem.title; cell.detailTextLabel.text = currentItem.url; [currentItem release]; return [cell autorelease]; } @end
相关文章推荐
- 【理解】iOS获取设备信息
- iOS 从开发到发布过程中需要注意的事项(持续补充)
- iOS CoreImage专题(二) —— 进阶
- ios 模糊效果
- 如何在xcode7上免开发者账号进行ios程序真机测试
- Github上的600多个iOS开源类库
- iOS中解析 XML / JSON
- iOS_ARC下需要release 的情况
- iOS Assigning to 'id<XXXDelegate>' from incompatible type 'BViewController *__strong'
- iOS开发--如何跳到系统设置里的WiFi界面
- IOS ARC中CTCallCenter无法监听电话的解决方案
- iOS CoreImage专题(一)—— 概述
- afn加密处理
- iOS 电话 短信 邮件 详解
- 获取本机ip
- iOS定位与地图
- ios view的frame和bounds之区别
- iOS-iOS8之后,push界面导航栏上方空出20像素
- iOS和tvOS游戏按需加载资源简介
- iOS 中类方法的多种调用方式