本文主要介紹了XML和JSON數(shù)據(jù)解析的基本知識,并展示了NSXMLParser方法、GDataXML第三方庫以及NSJSONSerialization方法的實際運用案例。
XML和JSON是目前Web開發(fā)中經(jīng)常用到的標記語言。這兩者主要用于Web開發(fā)中標記數(shù)據(jù)結(jié)構(gòu)。以微博為例,每一條微博都有Logo, 作者, 時間, 正文, 轉(zhuǎn)發(fā)數(shù), 回復, 點贊數(shù) 等項目。這些數(shù)據(jù)在網(wǎng)絡中都是按一定的結(jié)構(gòu)存儲的。

在iOS開發(fā)中,往往需要將網(wǎng)絡中的數(shù)據(jù)下載到本地,然后按一定的邏輯予以呈現(xiàn)。這個過程就是常說的解析。
一、語言簡介
1.XML
XML是可擴展標記語言(Extensible Markup Language)的縮寫,其中的標記(markup)是關鍵部分。XML語言將文件內(nèi)容用限定標記進行標記,從而使每個單詞、短語或段落成為可識別、可分類的信息。更多內(nèi)容可參考XML 新手入門基礎知識和XML 簡介。舉個栗子,這個栗子節(jié)選自這里。
<?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文本文件,可以跨系統(tǒng)讀寫。
2.JSON
JSON (JavaScript Object Notation) 是一種輕量級的數(shù)據(jù)交換格式。它基于ECMAScript的一個子集。 JSON采用完全獨立于語言的文本格式,但是也使用了類似于C語言家族的習慣(包括C、C++、C#、Java、JavaScript、Perl、Python等)。這些特性使JSON成為理想的數(shù)據(jù)交換語言。 易于人閱讀和編寫,同時也易于機器解析和生成(網(wǎng)絡傳輸速率)。來自百度百科
{
"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采用 數(shù)組 和 鍵值對 的形式標記數(shù)據(jù)結(jié)構(gòu)。
3.區(qū)別在哪
(1)JSON的效率更高
在這篇文章中,作者對XML和JSON的解析效率進行了測試。結(jié)果表明相對XML,JSON的解析速度提高了30%,占用空間少30%。
(2)XML有更多的解析方式
XML目前設計了兩種解析方式:
DOM(Document Object Model文檔對象模型)方式。解析時需要將XML文件整體讀入,并且將XML結(jié)構(gòu)化成樹狀,使用時再通過樹狀結(jié)構(gòu)讀取相關數(shù)據(jù),查找特定節(jié)點,然后對節(jié)點進行讀或?qū)?。該方式把一個數(shù)據(jù)交換格式XML看成一個DOM對象,需要把XML文件整個讀入內(nèi)存,這一點上JSON和XML的原理是一樣的,但是XML要考慮父節(jié)點和子節(jié)點,這一點上JSON的解析難度要小很多,因為JSON構(gòu)建于兩種結(jié)構(gòu):key/value,鍵值對的集合;值的有序集合,可理解為數(shù)組;
SAX(Simple API for XML)方式?;谑录?qū)動的解析方式,逐行解析數(shù)據(jù)。這一方式不需要整個讀入文檔就可以對解析出的內(nèi)容進行處理,是一種逐步解析的方法。程序也可以隨時終止解析。這樣,一個大的文檔就可以逐步的、一點一點的展現(xiàn)出來,所以SAX適合于大規(guī)模的解析。這一點,JSON目前是做不到得。
總體而言:JSON只提供整體解析方案,而這種方法只在解析較少的數(shù)據(jù)時才能起到良好的效果;XML提供了對大規(guī)模數(shù)據(jù)的逐步解析方案,這種方案很適合于對大量數(shù)據(jù)的處理。
二、在iOS中的解析
1.XML解析方式簡介
iOS中蘋果官方提供了NSXMLParser和libxml2兩種XML解析方式,同時也有第三方庫TBXML、TouchXML、KissXML、TinyXML、GDataXML可以執(zhí)行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中描述了對各個方法的測試。作者總結(jié)認為:對于讀取小型XML文檔,TouchXML, KissXML, GDataXML足矣;如果是讀寫小型XML文檔,KissXML和GDataXML都不錯;對于大型XML文檔,libxml2 SAX, TBXML, libxml2 DOM更好。
盡管NSXMLParser表現(xiàn)遜于libxml2 SAX,但勝在方便,無需調(diào)用第三方庫。根據(jù)原文的推薦,加上手邊現(xiàn)有的資料,最后我決定學習NSXMLParser和GDataXML兩種方式。
(1)NSXMLParser解析的代碼實現(xiàn)
任務目標:(下同)采用NSXMLParser方法解析前文XML樣例,并將得到的CD信息在UITableView中列出來。
主控制器頭文件,注意聲明代理<NSXMLParserDelegate>
#import <UIKit/UIKit.h>
@interface CDListTableViewController : UITableViewController <NSXMLParserDelegate>
@property (strong, nonatomic) NSMutableArray *dataSource;//存放解析得到的數(shù)據(jù)
@property (strong, nonatomic) NSString *startTag;
@end
根據(jù)要獲得的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
主控制器的實現(xiàn)文件
#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;//釋放內(nèi)存
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - XML Parse
- (void)parserDidStartDocument:(NSXMLParser *)parser {
//開始解析整個文檔時調(diào)用
NSLog(@"-START-");
}
- (void)parserDidEndDocument:(NSXMLParser *)parser {
//結(jié)束解析整個文檔時調(diào)用
NSLog(@"-END-");
}
- (void)parser:(NSXMLParser *)parser didStartElement:(nonnull NSString *)elementName namespaceURI:(nullable NSString *)namespaceURI qualifiedName:(nullable NSString *)qName attributes:(nonnull NSDictionary<NSString *,NSString *> *)attributeDict {
//當解析遇到開始標簽時調(diào)用
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 {
//當解析遇到結(jié)束標簽時調(diào)用
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;
//這里只提取了兩組數(shù)據(jù),如要顯示更多信息請自定義cell
return cell;
}
@end

(2)GDataXML解析的代碼實現(xiàn)
準備工作
- 首先從這里得到GDataXMLNode.h和GDataXMLNode.m文件,并導入到當前工程中。
-
其次,因為GDataXML是基于libxml2封裝得到,因此還要導入libxml2庫文件:在Linked Frameworks and Libraries點擊加號然后搜索libxml2,雙擊文件即可導入。
添加libxml2 - 接下來,在Build Settings中搜索“Header Search Paths”,將其值設置為 ${SDK_DIR}/usr/include/libxml2。否則會收到報錯:
libxml/tree.h not found。 -
最后一步,要將BuildSettings中的Objective-C Automatic Reference Counting設置為NO。否則會收到有關ARC的報錯。
設置Objective-C Automatic Reference Counting選項
開始工作
剛才關閉了ARC,注意做好代碼的內(nèi)存管理工作。
#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];
}
}
/////////////////////////////////////////////
//其余代碼與上例類似,只要注意做好內(nèi)存管理工作即可///
/////////////////////////////////////////////
- (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解析的代碼實現(xiàn),可以參考XML Tutorial for iOS: How To Read and Write XML Documents with GDataXML。
2.JSON解析方式簡介
蘋果原生提供NSJSONSerialization解析方式,也有第三方選擇:JSONKit,SBJSON,TouchJSON。根據(jù)iOS中四種JSON解析效率比較一文,原生的NSJSONSerialization方法是最佳選擇,JSONKit是次優(yōu)選擇。
NSJSONSerialization解析的代碼實現(xiàn)
任務目標:利用娛樂花邊API實現(xiàn)NSJSONSerialization解析。
目標分析:
JSON文檔有兩種結(jié)構(gòu): 對象:以“{“開始,以”}”結(jié)束,是“名稱/值”對兒的集合。名稱和值中間用“:”隔開。多個“名稱/值”對之間用“,”隔開。類似Objective-C的NSDictionary。 數(shù)組:以“["開始,以“]”結(jié)束,中間是數(shù)據(jù)。數(shù)據(jù)以“,”分割。類似Objective-C的NSArray。不同的JSON結(jié)構(gòu)有不同的轉(zhuǎn)化方式。
JSON格式與Objective-C轉(zhuǎn)化對照表
| JSON | Objective-C |
|---|---|
| 大括號{} | NSDictionary |
| 中括號[] | NSArray |
| 雙引號 "" | NSString |
| 數(shù)字{} | NSNumber |
該API返回的JSON格式數(shù)據(jù)如下所示。
{
"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"
}
考察本例,返回對象是一個對象的兩級嵌套,轉(zhuǎn)換為Objective-C應該是兩級字典。
代碼實現(xiàn)
針對返回數(shù)據(jù),新建了item類。簡單起見,就只提取返回數(shù)據(jù)的標題和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,因此有手動管理內(nèi)存代碼。
#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);
//這句是自己添加的,執(zhí)行對返回數(shù)據(jù)的處理
[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


