引子
數(shù)據(jù)解析在iOS開發(fā)中是不可或缺的一環(huán),從服務(wù)器獲取到的數(shù)據(jù),就目前來說無非就是XML和json兩種。今天我就來總結(jié)一下iOS平臺下,常用的幾種解析數(shù)據(jù)的方法及步驟。
XML文件解析
關(guān)于XML文檔格式,這里不再贅述。XML百科介紹,XML菜鳥學習。 關(guān)于XML解析,大致有兩種方式:
- SAX(Simple API for XML):事件驅(qū)動機制。從根元素開始,按順序一個個元素往下解析,它只在XML文檔中查找特定條件的內(nèi)容,并且只提取需要的東西,占用內(nèi)存少,也比較靈活,所以適合解析大文件。
- DOM(Document Object Model):文檔對象模型。一次性將整個XML文檔加載進內(nèi)存,放在一個樹型結(jié)構(gòu)中,需要的時候查找特定節(jié)點。實現(xiàn)簡單,讀寫平衡,但是比較占內(nèi)存,適合解析小文件。
- 這里我使用的XML例子如下:(中間很長的一坨只是為了說明自帶的解析器并不會一下子把節(jié)點之間的所有字符串給解析完)。
<?xml version="1.0" encoding="UTF-8"?>
<Books>
<Book id="1">
<title>月亮與六便士</title>
<author>毛姆</author>
<summary>上帝的磨盤磨得很慢,卻磨得很細上帝的磨盤磨得
很慢,卻磨得很細上帝的磨盤磨得很慢,卻磨得很細上帝的磨盤
磨得很慢,卻磨得很細上帝的磨盤磨得很慢,卻磨得很細上帝的
磨盤磨得很慢,卻磨得很細上帝的磨盤磨得很慢,卻磨得很細上
帝的磨盤磨得很慢,卻磨得很細上帝的磨盤磨得很慢,卻磨得很
細</summary>
</Book>
<Book id="2">
<title>島上書店</title>
<author>加布瑞埃拉·澤文</author>
<summary>小島上書店的老板和他的書店</summary>
</Book>
<Book id="3">
<title>白夜行</title>
<author>東野圭吾</author>
<summary>畸形但卻最深沉的愛情</summary>
</Book>
</Books>
NSXMLParser解析
NSXMLParser是iOS系統(tǒng)自帶的解析類,屬于SAX解析方式,在解析到每個元素的時候會通知代理,所以使用NSXMLParser必須遵守它的代理協(xié)議。
1. 使用步驟:
//1. 創(chuàng)建XML解析器
NSXMLParser *parser = [[NSXMLParser alloc]initWithData:self.xmlData];
//2. 設(shè)置解析器的代理
parser.delegate = self;
//3. 開始解析
[parser parse];
2. 代理方法
// 1. 打開文檔,準備解析,一般在這里邊用來將保存數(shù)據(jù)的數(shù)組暫時清空
- (void)parserDidStartDocument:(NSXMLParser *)parser;
//2. 發(fā)現(xiàn)節(jié)點。一般在這里主要進行數(shù)據(jù)模型的初始化和讀取節(jié)點的屬性值
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary<NSString *,NSString *> *)attributeDict
{
//節(jié)點的屬性值是以字典的形式傳遞進來的,所以取的時候也按字典的讀取方法取出來就行
self.book.bookID = [attributeDict[@"id"] integerValue];
//在準備開始解析下一個節(jié)點的數(shù)據(jù)時,先把數(shù)據(jù)清空
[self.elementString setString:@""];
}
//3. 解析節(jié)點之間的字符。 當解析器找到開始標記和結(jié)束標記之間的字符時,調(diào)用這個方法解析當前節(jié)點內(nèi)的所有字符
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
//將一個節(jié)點中讀取到的數(shù)據(jù)進行拼接
[self.elementString appendString:string];
}
//4. 節(jié)點解析結(jié)束,在這個方法里通常進行數(shù)據(jù)的保存
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
//如果遇到了結(jié)束節(jié)點符號,則進行數(shù)據(jù)的保存
if ([elementName isEqualToString:@"Book"])
{
[self.books addObject:self.book];
}
//如果既不是數(shù)據(jù)的最后一個節(jié)點,也不是根節(jié)點,即為數(shù)據(jù)中間的節(jié)點,則將數(shù)據(jù)對應保存到模型中
//運用KVC保存,效率高,不過屬性的名字一定要和節(jié)點的名稱相對應
else if(![elementName isEqualToString:@"Books"])
{
[self.book setValue:self.elementString forKey:elementName];
}
//如果節(jié)點較少,也可以通過下邊這種手動賦值的方式
else if ([elementName isEqualToString:@"title"])
{
self.book.title = self.elementString;
}
}
//5. 文檔解析結(jié)束,可以把數(shù)據(jù)傳遞給主線程,進行相關(guān)的UI更新
- (void)parserDidEndDocument:(NSXMLParser *)parser
系統(tǒng)多次解析的情況:

這也是為什么要在第3步進行字符串的拼接和第2步的字符串的清空。
GDataXML解析
導入
GDataXML并沒有和cocoaPod進行關(guān)聯(lián),所以無法使用Pod進行管理,只能從網(wǎng)上直接下載源文件,然后手動導入。好在GDataXML很簡單,只有一個頭文件和一個實現(xiàn)文件,使用的時候?qū)肫漕^文件即可。
關(guān)于導入方法,首先需要添加libxml2.tbd動態(tài)庫,然后添加兩個編譯參數(shù),這在GDataXML.h中描述的很明白,如下:
// libxml includes require that the target Header Search Paths contain
//
// /usr/include/libxml2
//
// and Other Linker Flags contain
//
// -lxml2
如果遇到:

那就這樣解決:

還有這樣:

解析數(shù)據(jù)
//初始化GDataXMLDocument,將整個文檔讀入
GDataXMLDocument *doc = [[GDataXMLDocument alloc]initWithData:self.xmlData options:0 error:nil];
//獲取根節(jié)點
GDataXMLElement *rootElement = [doc rootElement];
//獲取根節(jié)點下的數(shù)據(jù)節(jié)點,返回值是數(shù)組類型
NSArray *Books = [rootElement elementsForName:@"Book"];
for (GDataXMLElement *book in Books)
{
HXTXMLDataModel *bookModel = [[HXTXMLDataModel alloc]init];
//獲取Book節(jié)點的屬性值
bookModel.bookID = [[[book attributeForName:@"id"] stringValue]integerValue];
//獲取Book下的數(shù)據(jù),并且賦值給數(shù)據(jù)模型
bookModel.title = [[[book elementsForName:@"title"]firstObject]stringValue];
bookModel.author = [[[book elementsForName:@"author"]firstObject]stringValue];
bookModel.summary = [[[book elementsForName:@"summary"]firstObject]stringValue];
//將數(shù)據(jù)模型添加至全局的模型數(shù)組中
[self.books addObject:bookModel];
}
HXTXMLDataModel *bookModel = self.books[0];
//取出數(shù)據(jù),更新UI
self.Label1.text = [NSString stringWithFormat:@"%ld",bookModel.bookID];
GDataXML將文件整個讀入內(nèi)存,所以取值的時候一般都會返回包含所有數(shù)據(jù)的數(shù)組類型,如果要進行數(shù)據(jù)的查找,將會非常方便!不過不能像系統(tǒng)自帶的那樣通過KVC賦值給數(shù)據(jù)模型,這一點也可以看出來GDataXML比較適合小的文檔解析使用。
Json文檔解析
同樣,關(guān)于json文檔格式不會涉及太多,可以json百度百科。
本例用到的json文檔:
{
"Result": [
{
"id": "1",
"title": "月亮與六便士",
"author": "毛姆",
"summary": "上帝的磨盤磨得很慢,卻磨得很細上帝的磨盤磨得
很慢,卻磨得很細上帝的磨盤磨得很慢,卻磨得很細上帝的磨盤
磨得很慢,卻磨得很細上帝的磨盤磨得很慢,卻磨得很細上帝的
磨盤磨得很慢,卻磨得很細上帝的磨盤磨得很慢,卻磨得很細上
帝的磨盤磨得很慢,卻磨得很細上帝的磨盤磨得很慢,卻磨得很
細"
},
{
"id": "2",
"title": "島上書店",
"author": "加布瑞埃拉·澤文",
"summary": "小島上書店的老板和他的書店"
},
{
"id": "3",
"title": "白夜行",
"author": "東野圭吾",
"summary": "畸形但卻最深沉的愛情"
},
]
}
本地json文件解析
其實iOS更新到現(xiàn)在,json解析的第三方框架也有很多,不過iOS系統(tǒng)自帶的解析依然效率是最高的,所以這里著重講一下系統(tǒng)自帶的解析。對于json解析,最重要的是看清楚文檔結(jié)構(gòu),不用一著急上來就要解析,json文檔和OC之間的對應關(guān)系大致為:
| json | OC |
|---|---|
| 大括號 {} | NSDictionary |
| 中括號 [] | NSArray |
| 雙引號 "" | NSString |
| 數(shù)字 10、1.3 | NSNumber |
按照這個對應關(guān)系,我們來分析一下上邊的json文檔。
- 首先最外層是一個大括號,所以最一開始應該用
NSDictionary來接收解析出來的數(shù)據(jù); - 其次所有的數(shù)據(jù)都在
“Result”這個鍵對應的值里邊,所以應該用keyForValue這個方法獲取“Result”下邊的數(shù)據(jù),而這個數(shù)據(jù)最為外層是一對中括號[],所以應該用數(shù)組來進行接收; - 最后中括號
[]里邊依然是三個大括號{}括起來的數(shù)據(jù),所以數(shù)組里邊的元素都是NSDictionary類型,我們用NSDictionary對數(shù)組進行遍歷,然后再次通過keyForValue獲取最后我們需要的值。當然這一步,如果數(shù)據(jù)模型的屬性和key值對應,則用KVC更是方便。
//懶加載json數(shù)據(jù),轉(zhuǎn)換成NSData以備解析
NSString *jsonPath = [[NSBundle mainBundle]pathForResource:@"書目" ofType:@"json"];
_jsonData = [NSData dataWithContentsOfFile:jsonPath];
//1. 用jsonDict字典來接收解析出來的初步數(shù)據(jù)
self.jsonDict = [NSJSONSerialization JSONObjectWithData:self.jsonData options:0 error:nil];
// NSLog(@"%@", self.jsonDict);
//2. 用鍵值取出Result對應的數(shù)據(jù),保存在數(shù)組中
NSArray *resultArr = [self.jsonDict valueForKey:@"Result"];
//3. 獲取包含最終數(shù)據(jù)的字典
NSDictionary *bookDict = [resultArr firstObject];
//4. 賦值給數(shù)據(jù)模型,當然用KVC更方便
HXTXMLDataModel *bookModel = [[HXTXMLDataModel alloc]init];
bookModel.bookID = [[[resultArr firstObject] valueForKey:@"id"]integerValue];
bookModel.title = [bookDict valueForKey:@"title"];
bookModel.author = [bookDict valueForKey:@"author"];
bookModel.summary = [bookDict valueForKey:@"summary"];
//5. 根據(jù)數(shù)據(jù)進行更新
self.Label1.text = [NSString stringWithFormat:@"%ld",bookModel.bookID];
self.Label2.text = bookModel.title;
self.Label3.text = bookModel.author;
self.Label4.text = bookModel.summary;
網(wǎng)絡(luò)json文件解析
其實網(wǎng)絡(luò)json文檔解析,跟前邊本地的步驟一樣,只不過是從網(wǎng)絡(luò)服務(wù)器進行數(shù)據(jù)獲取,寫在這里只是為了說明,從iOS9開始,網(wǎng)絡(luò)請求數(shù)據(jù)的方法有些變化:
//網(wǎng)址
NSString *path = @"http://www.weather.com.cn/data/sk/101010100.html";
//初始化url
NSURL *url = [NSURL URLWithString:path];
//獲取網(wǎng)絡(luò)單例
NSURLSession *session = [NSURLSession sharedSession];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//生成請求數(shù)據(jù)的任務(wù)
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:^(NSData * _Nullable data,
NSURLResponse * _Nullable response,
NSError * _Nullable error) {
//解析json數(shù)據(jù),跟上一個本地解析的步驟一樣,不再贅述
}];
//調(diào)用任務(wù)
[task resume];
JSONKit解析
先說一個小坑:我是通過cocoaPod導入的JSONKit庫,不過引入頭文件之后,報出20個錯誤:

解決方法是:首先選中Xcod左邊欄Pods,然后編譯設(shè)置中做如下設(shè)置:

完工開始。
JSONKit的使用也很簡單,常用的方法有:
- (id)objectFromJSONString;
- (id)objectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
- (id)objectFromJSONData;
- (id)objectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
如果數(shù)據(jù)是“單層”的,即value都是字符串、數(shù)字,可以使用objectFromJSONString或者objectFromJSONData。
但是如果數(shù)據(jù)有嵌套,即value里有數(shù)組、字典、對象等,最好使用帶參數(shù)的objectFromJSONStringWithParseOptions或者objectFromJSONDataWithParseOptions。
這個例子中,我使用的是和data相關(guān)的方法。
//1. 調(diào)用JSONKit的方法進行解析,用字典接收,這里邊的枚舉值我都嘗試了,都可以得到正確結(jié)果,暫不清楚區(qū)別是什么
self.jsonDict = [self.jsonData objectFromJSONDataWithParseOptions:JKSerializeOptionNone];
// NSLog(@"%@====", self.jsonDict);
//2. 用鍵值取出Result對應的數(shù)據(jù),保存在數(shù)組中
NSArray *resultArr = [self.jsonDict valueForKey:@"Result"];
//3. 獲取包含最終數(shù)據(jù)的字典
NSDictionary *bookDict = [resultArr firstObject];
//4. 賦值給數(shù)據(jù)模型,當然用KVC更方便
HXTXMLDataModel *bookModel = [[HXTXMLDataModel alloc]init];
bookModel.bookID = [[[resultArr firstObject] valueForKey:@"id"]integerValue];
bookModel.title = [bookDict valueForKey:@"title"];
bookModel.author = [bookDict valueForKey:@"author"];
bookModel.summary = [bookDict valueForKey:@"summary"];
//5. 根據(jù)數(shù)據(jù)進行更新
self.Label1.text = [NSString stringWithFormat:@"%ld",bookModel.bookID];
self.Label2.text = bookModel.title;
self.Label3.text = bookModel.author;
self.Label4.text = bookModel.summary;
大家也都看出來了,其實只是第一步解析的不同,后邊數(shù)據(jù)處理的方法和之前是一模一樣的。
另外再提一點,我在用JSONKit解析的時候,發(fā)現(xiàn)它對json數(shù)據(jù)的格式比較挑剔??梢钥吹阶钌线呂夷莻€json文檔例子,最后一個數(shù)據(jù)的大括號外邊多了一個逗號,用系統(tǒng)進行解析的時候沒什么問題,但是換成JSONKit之后,嘗試了幾次都解析不出來,后來打印錯誤一看,提示那個逗號不對,刪除之后再解析就正確了。希望大家引以為戒!