引子
數據解析在iOS開發中是不可或缺的一環,從服務器獲取到的數據,就目前來說無非就是XML和json兩種。今天我就來總結一下iOS平臺下,常用的幾種解析數據的方法及步驟。
XML文件解析
關于XML文檔格式,這里不再贅述。XML百科介紹,XML菜鳥學習。 關于XML解析,大致有兩種方式:
- SAX(Simple API for XML):事件驅動機制。從根元素開始,按順序一個個元素往下解析,它只在XML文檔中查找特定條件的內容,并且只提取需要的東西,占用內存少,也比較靈活,所以適合解析大文件。
- DOM(Document Object Model):文檔對象模型。一次性將整個XML文檔加載進內存,放在一個樹型結構中,需要的時候查找特定節點。實現簡單,讀寫平衡,但是比較占內存,適合解析小文件。
- 這里我使用的XML例子如下:(中間很長的一坨只是為了說明自帶的解析器并不會一下子把節點之間的所有字符串給解析完)。
<?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系統自帶的解析類,屬于SAX解析方式,在解析到每個元素的時候會通知代理,所以使用NSXMLParser
必須遵守它的代理協議。
1. 使用步驟:
//1. 創建XML解析器
NSXMLParser *parser = [[NSXMLParser alloc]initWithData:self.xmlData];
//2. 設置解析器的代理
parser.delegate = self;
//3. 開始解析
[parser parse];
2. 代理方法
// 1. 打開文檔,準備解析,一般在這里邊用來將保存數據的數組暫時清空
- (void)parserDidStartDocument:(NSXMLParser *)parser;
//2. 發現節點。一般在這里主要進行數據模型的初始化和讀取節點的屬性值
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary<NSString *,NSString *> *)attributeDict
{
//節點的屬性值是以字典的形式傳遞進來的,所以取的時候也按字典的讀取方法取出來就行
self.book.bookID = [attributeDict[@"id"] integerValue];
//在準備開始解析下一個節點的數據時,先把數據清空
[self.elementString setString:@""];
}
//3. 解析節點之間的字符。 當解析器找到開始標記和結束標記之間的字符時,調用這個方法解析當前節點內的所有字符
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
//將一個節點中讀取到的數據進行拼接
[self.elementString appendString:string];
}
//4. 節點解析結束,在這個方法里通常進行數據的保存
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
//如果遇到了結束節點符號,則進行數據的保存
if ([elementName isEqualToString:@"Book"])
{
[self.books addObject:self.book];
}
//如果既不是數據的最后一個節點,也不是根節點,即為數據中間的節點,則將數據對應保存到模型中
//運用KVC保存,效率高,不過屬性的名字一定要和節點的名稱相對應
else if(![elementName isEqualToString:@"Books"])
{
[self.book setValue:self.elementString forKey:elementName];
}
//如果節點較少,也可以通過下邊這種手動賦值的方式
else if ([elementName isEqualToString:@"title"])
{
self.book.title = self.elementString;
}
}
//5. 文檔解析結束,可以把數據傳遞給主線程,進行相關的UI更新
- (void)parserDidEndDocument:(NSXMLParser *)parser
系統多次解析的情況:
這也是為什么要在第3步進行字符串的拼接和第2步的字符串的清空。
GDataXML解析
導入
GDataXML
并沒有和cocoaPod
進行關聯,所以無法使用Pod進行管理,只能從網上直接下載源文件,然后手動導入。好在GDataXML
很簡單,只有一個頭文件和一個實現文件,使用的時候導入其頭文件即可。
關于導入方法,首先需要添加libxml2.tbd
動態庫,然后添加兩個編譯參數,這在GDataXML.h
中描述的很明白,如下:
// libxml includes require that the target Header Search Paths contain
//
// /usr/include/libxml2
//
// and Other Linker Flags contain
//
// -lxml2
如果遇到:
那就這樣解決:
還有這樣:
解析數據
//初始化GDataXMLDocument,將整個文檔讀入
GDataXMLDocument *doc = [[GDataXMLDocument alloc]initWithData:self.xmlData options:0 error:nil];
//獲取根節點
GDataXMLElement *rootElement = [doc rootElement];
//獲取根節點下的數據節點,返回值是數組類型
NSArray *Books = [rootElement elementsForName:@"Book"];
for (GDataXMLElement *book in Books)
{
HXTXMLDataModel *bookModel = [[HXTXMLDataModel alloc]init];
//獲取Book節點的屬性值
bookModel.bookID = [[[book attributeForName:@"id"] stringValue]integerValue];
//獲取Book下的數據,并且賦值給數據模型
bookModel.title = [[[book elementsForName:@"title"]firstObject]stringValue];
bookModel.author = [[[book elementsForName:@"author"]firstObject]stringValue];
bookModel.summary = [[[book elementsForName:@"summary"]firstObject]stringValue];
//將數據模型添加至全局的模型數組中
[self.books addObject:bookModel];
}
HXTXMLDataModel *bookModel = self.books[0];
//取出數據,更新UI
self.Label1.text = [NSString stringWithFormat:@"%ld",bookModel.bookID];
GDataXML
將文件整個讀入內存,所以取值的時候一般都會返回包含所有數據的數組類型,如果要進行數據的查找,將會非常方便!不過不能像系統自帶的那樣通過KVC
賦值給數據模型,這一點也可以看出來GDataXML
比較適合小的文檔解析使用。
Json文檔解析
同樣,關于json文檔格式不會涉及太多,可以json百度百科。
本例用到的json文檔:
{
"Result": [
{
"id": "1",
"title": "月亮與六便士",
"author": "毛姆",
"summary": "上帝的磨盤磨得很慢,卻磨得很細上帝的磨盤磨得
很慢,卻磨得很細上帝的磨盤磨得很慢,卻磨得很細上帝的磨盤
磨得很慢,卻磨得很細上帝的磨盤磨得很慢,卻磨得很細上帝的
磨盤磨得很慢,卻磨得很細上帝的磨盤磨得很慢,卻磨得很細上
帝的磨盤磨得很慢,卻磨得很細上帝的磨盤磨得很慢,卻磨得很
細"
},
{
"id": "2",
"title": "島上書店",
"author": "加布瑞埃拉·澤文",
"summary": "小島上書店的老板和他的書店"
},
{
"id": "3",
"title": "白夜行",
"author": "東野圭吾",
"summary": "畸形但卻最深沉的愛情"
},
]
}
本地json文件解析
其實iOS更新到現在,json解析的第三方框架也有很多,不過iOS系統自帶的解析依然效率是最高的,所以這里著重講一下系統自帶的解析。對于json解析,最重要的是看清楚文檔結構,不用一著急上來就要解析,json文檔和OC之間的對應關系大致為:
json | OC |
---|---|
大括號 {} | NSDictionary |
中括號 [] | NSArray |
雙引號 "" | NSString |
數字 10、1.3 | NSNumber |
按照這個對應關系,我們來分析一下上邊的json文檔。
- 首先最外層是一個大括號,所以最一開始應該用
NSDictionary
來接收解析出來的數據; - 其次所有的數據都在
“Result”
這個鍵對應的值里邊,所以應該用keyForValue
這個方法獲取“Result”
下邊的數據,而這個數據最為外層是一對中括號[]
,所以應該用數組來進行接收; - 最后中括號
[]
里邊依然是三個大括號{}
括起來的數據,所以數組里邊的元素都是NSDictionary
類型,我們用NSDictionary
對數組進行遍歷,然后再次通過keyForValue
獲取最后我們需要的值。當然這一步,如果數據模型的屬性和key
值對應,則用KVC
更是方便。
//懶加載json數據,轉換成NSData以備解析
NSString *jsonPath = [[NSBundle mainBundle]pathForResource:@"書目" ofType:@"json"];
_jsonData = [NSData dataWithContentsOfFile:jsonPath];
//1. 用jsonDict字典來接收解析出來的初步數據
self.jsonDict = [NSJSONSerialization JSONObjectWithData:self.jsonData options:0 error:nil];
// NSLog(@"%@", self.jsonDict);
//2. 用鍵值取出Result對應的數據,保存在數組中
NSArray *resultArr = [self.jsonDict valueForKey:@"Result"];
//3. 獲取包含最終數據的字典
NSDictionary *bookDict = [resultArr firstObject];
//4. 賦值給數據模型,當然用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. 根據數據進行更新
self.Label1.text = [NSString stringWithFormat:@"%ld",bookModel.bookID];
self.Label2.text = bookModel.title;
self.Label3.text = bookModel.author;
self.Label4.text = bookModel.summary;
網絡json文件解析
其實網絡json文檔解析,跟前邊本地的步驟一樣,只不過是從網絡服務器進行數據獲取,寫在這里只是為了說明,從iOS9開始,網絡請求數據的方法有些變化:
//網址
NSString *path = @"http://www.weather.com.cn/data/sk/101010100.html";
//初始化url
NSURL *url = [NSURL URLWithString:path];
//獲取網絡單例
NSURLSession *session = [NSURLSession sharedSession];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//生成請求數據的任務
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:^(NSData * _Nullable data,
NSURLResponse * _Nullable response,
NSError * _Nullable error) {
//解析json數據,跟上一個本地解析的步驟一樣,不再贅述
}];
//調用任務
[task resume];
JSONKit解析
先說一個小坑:我是通過cocoaPod
導入的JSONKit
庫,不過引入頭文件之后,報出20個錯誤:
解決方法是:首先選中Xcod左邊欄Pods
,然后編譯設置中做如下設置:
完工開始。
JSONKit的使用也很簡單,常用的方法有:
- (id)objectFromJSONString;
- (id)objectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
- (id)objectFromJSONData;
- (id)objectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
如果數據是“單層”的,即value
都是字符串、數字,可以使用objectFromJSONString
或者objectFromJSONData
。
但是如果數據有嵌套,即value里有數組、字典、對象等,最好使用帶參數的objectFromJSONStringWithParseOptions
或者objectFromJSONDataWithParseOptions
。
這個例子中,我使用的是和data
相關的方法。
//1. 調用JSONKit的方法進行解析,用字典接收,這里邊的枚舉值我都嘗試了,都可以得到正確結果,暫不清楚區別是什么
self.jsonDict = [self.jsonData objectFromJSONDataWithParseOptions:JKSerializeOptionNone];
// NSLog(@"%@====", self.jsonDict);
//2. 用鍵值取出Result對應的數據,保存在數組中
NSArray *resultArr = [self.jsonDict valueForKey:@"Result"];
//3. 獲取包含最終數據的字典
NSDictionary *bookDict = [resultArr firstObject];
//4. 賦值給數據模型,當然用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. 根據數據進行更新
self.Label1.text = [NSString stringWithFormat:@"%ld",bookModel.bookID];
self.Label2.text = bookModel.title;
self.Label3.text = bookModel.author;
self.Label4.text = bookModel.summary;
大家也都看出來了,其實只是第一步解析的不同,后邊數據處理的方法和之前是一模一樣的。
另外再提一點,我在用JSONKit
解析的時候,發現它對json數據的格式比較挑剔。可以看到最上邊我那個json文檔例子,最后一個數據的大括號外邊多了一個逗號,用系統進行解析的時候沒什么問題,但是換成JSONKit
之后,嘗試了幾次都解析不出來,后來打印錯誤一看,提示那個逗號不對,刪除之后再解析就正確了。希望大家引以為戒!