iOS開發——XML/JSON數據解析

先安利一波:編程

Json
XMLjson

  大數據時代,咱們須要從網絡中獲取海量的新鮮的各類信息,就難免要跟着兩個傢伙打交道,這是兩種結構化的數據交換格式。通常來說,咱們會從網絡獲取XML或者Json格式的數據,這些數據有着特定的數據結構,必須對其進行解析,獲得咱們能夠處理的數據。所謂「解析」,就是從事先規定好的格式串中提取數據。解析的前提是數據的提供方與獲取方提早約定好格式,數據提供方按照格式提供數據,數據獲取方按照格式獲取數據。api

iOS開發中,幾乎只要是與網絡相關的應用,都離不開對網絡數據的解析與應用。現總結幾種經常使用方式來解析網絡數據:數組

  • Json格式:
    • NSJSONSerialization,官方提供的Json數據格式解析類,iOS5之後支持
    • JSONKit(第三方類庫)
    • SBJson
    • TouchJson
  • XML格式:
    • NSXMLParse,官方自帶
    • GDataXML,Google提供的開元XML解析庫

按照目前的發展,Json正在逐步取代XML成爲網絡數據的通用格式,因此咱們重點來看Json格式的數據解析先。瀏覽器


準備工做

  在看如何使用Json和XML以前,咱們還有些事情要作,一是準備咱們要解析的數據,二是搭建一個界面來看實際效果,畢竟咱們解析了數據就是要在應用中展現出來的。網絡

Json數據準備

  關於如何獲取網絡的數據在這裏就很少贅述了,你只須要得到一個從網站爲開發者提供的API接口中得到咱們想要的url就行了。我這裏調用了豆瓣電影的API,隨便選了在豆瓣電影首頁的電影——《前任2:備胎反擊戰》,來看看豆瓣對這部電影的描述,因爲標籤太多,我這裏只打算從中獲取電影名稱,體裁和劇情簡介三部分打印出來。session

咱們能夠先提早在瀏覽器中打開看一下這個待會咱們將要獲得的東西:數據結構

  是否是很亂。。。沒錯,網站返回的東西雖然看上去好像有點規律,可是仍是難以辨別,這裏不用擔憂,咱們可使用一個叫作Json校驗格式化工具的東西來優化一下它的顯示,這裏有一個在線的。咱們把網站返回給咱們的數據copy到這裏,點擊校驗,若是沒有什麼問題的話,爲了方便展現,我把它copy到了Sublime中,咱們看一下結果你會發現它變成了下面這個樣子,這樣看起來就舒服多了,咱們也能夠很是清楚地看到每一對「Key——Velue」對,以及每一個Velue的類型,弄清楚了,待會兒方便咱們查詢和顯示。多線程

  找到了目標,下一步咱們先作個界面的模子出來,展現咱們解析過的數據。大概就是下面這個樣子,點擊不一樣的按鈕,能夠以不一樣的方式解析得到的數據並在TextView中打印。app

界面搭好以後不要忘了關聯到代碼。

XML數據準備

咱們在項目中新建一個xml文件,編寫其中的內容,待會兒解析內容並打印到TextView。

XML內容爲Person,有幾個學生的信息,包括學號,姓名,性別和年齡,一下子根據這些建立模型。

NSJSONSerialization

  接下來就正式開始。蘋果官方給出的解析方式是性能最優越的,雖然用起來稍顯複雜。

  首先咱們在上面已經有了我但願獲得的信息的網站的API給咱們的URL,在OC中,我要加載一個NSURL對象,來向網站提交一個Request。到這裏須要特別注意了,iOS9的時代已經來臨,咱們先前在舊版本中使用的某些類或者方法都已經被蘋果官方棄用了。剛剛咱們向網站提交了一個Request,在以往,咱們是經過NSURLConnection中的sendSynchronousRequest方法來接受網站返回的Response的,可是在iOS9中,它已經再也不使用了。從官方文檔中,咱們追根溯源,找到了它的替代品——NSURLSession類。這個類是iOS7中新的網絡接口,蘋果力推之,而且如今用它徹底替代了NSURLConnection。關於它的具體用法,仍是蠻簡單的,直接上代碼(ViewController.m文件):

 1 #import "ViewController.h"
 2 
 3 @interface ViewController ()
 4 @property (retain, nonatomic) IBOutlet UITextView *textView;
 5 @property (nonatomic, strong) NSMutableDictionary *dic;
 6 @property (nonatomic,strong) NSString *text;
 7 @end
 8 
 9 @implementation ViewController
10 - (IBAction)NSJson:(UIButton *)sender {
11     //GCD異步實現
12     dispatch_queue_t q1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
13     dispatch_async(q1, ^{
14 
15         //加載一個NSURL對象
16         NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://api.douban.com/v2/movie/subject/25881786"]];
17 
18         //使用NSURLSession獲取網絡返回的Json並處理
19         NSURLSession *session = [NSURLSession sharedSession];
20         NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error){
21 
22             //從網絡返回了Json數據,咱們調用NSJSONSerialization解析它,將JSON數據轉換爲Foundation對象(這裏是一個字典)
23             self.dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
24 
25             NSString *title = [self.dic objectForKey:@"original_title"];
26             NSMutableArray *genresArray = [self.dic objectForKey:@"genres"];
27             NSString *genres = [NSString stringWithFormat:@"%@/%@", [genresArray objectAtIndex:0], [genresArray objectAtIndex:1]];
28             NSString *summary = [self.dic objectForKey:@"summary"];
29 
30             self.text = [NSString stringWithFormat:@"電影名稱:\n%@\n體裁:\n%@\n劇情簡介:\n%@", title, genres, summary];
31 
32             //更新UI操做須要在主線程
33             dispatch_async(dispatch_get_main_queue(), ^{
34                 self.textView.text = self.text;
35             });
36         }];
37         //調用任務
38         [task resume];
39     });
40 }

  仍是要再提一下,由於涉及到了網絡請求,咱們在這裏用了一點關於使用GCD實現多線程的內容,之後再專門介紹吧。咱們運行程序,點擊NSJSONSerialization按鈕,就看到咱們要的內容啦!

SBJson

  事實上上面的解析過程仍是挺複雜的,主要是牽扯到了NSURLSession的使用。那接下來來看看一些第三方Json解析庫的使用。SBJson用起來就簡單多了。首先咱們去下載這個類庫,Github啊,CSDN啊,51啊哪裏的任何一個地方都有,很好找。下載下來後導入咱們的項目就能夠直接運行了。有些第三方類庫因爲年代久遠多是不支持ARC的,SBJson還好,下面那個JsonKit可就不這麼和諧了,這個待會再講。咱們此次點擊第二個按鈕來實現它。爲了以示區分,此次我換了一部電影,來看看《移動迷宮2 Maze Runner: The Scorch Trials》吧!

 1 //上面先導入包:
 2 #import "ViewController.h"
 3 #import "SBJson.h"
 4 
 5 //實現:
 6 - (IBAction)SBJson:(UIButton *)sender {
 7     //GCD異步實現
 8     dispatch_queue_t q1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 9     dispatch_async(q1, ^{
10 
11         //仍是先獲取url
12         NSURL *url = [NSURL URLWithString:@"https://api.douban.com/v2/movie/subject/25995508"];
13         //返回上面url的內容,格式爲Json放在了字符串裏
14         NSString *jsonString = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];
15         //實例化SBJson對象,將Json格式字符串解析,轉化爲字典。
16         SBJsonParser *parser = [[SBJsonParser alloc] init];
17         self.dic = [parser objectWithString:jsonString error:nil];
18 
19 
20         NSString *title = [self.dic objectForKey:@"original_title"];
21         NSMutableArray *genresArray = [self.dic objectForKey:@"genres"];
22         NSString *genres = [NSString stringWithFormat:@"%@/%@", [genresArray objectAtIndex:0], [genresArray objectAtIndex:1]];
23         NSString *summary = [self.dic objectForKey:@"summary"];
24         self.text = [NSString stringWithFormat:@"電影名稱:\n%@\n體裁:\n%@\n劇情簡介:\n%@", title, genres, summary];
25 
26         //更新UI操做須要在主線程
27         dispatch_async(dispatch_get_main_queue(), ^{
28             self.textView.text = self.text;
29         });
30 
31     });
32 }

JsonKit

  事實上,它雖然不支持ARC,但JsonKit是在性能上僅次於蘋果原生解析器的第三方類庫。咱們在導入它的包之後編譯會出現一大堆報錯,這時候不用慌,咱們會發現大部分是ARC的問題,解決方法也挺簡單,咱們進入項目的Target,找到Build Phases裏面的Compile Sources,接着找咱們的問題源頭JsonKit.m,雙擊更改它的Compiler Flags標籤爲「-fno-objc-arc」,再次編譯,就好啦~

 1 //上面先導入包:
 2 #import "ViewController.h"
 3 #import "JsonKit.h"
 4 
 5 //實現
 6 - (IBAction)JsonKit:(UIButton *)sender {
 7     //GCD異步實現
 8     dispatch_queue_t q1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 9     dispatch_async(q1, ^{
10 
11         //仍是先獲取url
12         NSURL *url = [NSURL URLWithString:@"https://api.douban.com/v2/movie/subject/26279433"];
13         NSString *jsonString = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];
14         //代碼愈來愈簡單了有木有!!就一個方法搞定~
15         self.dic = [jsonString objectFromJSONStringWithParseOptions:JKParseOptionLooseUnicode];
16 
17 
18         NSString *title = [self.dic objectForKey:@"original_title"];
19         NSMutableArray *genresArray = [self.dic objectForKey:@"genres"];
20         NSString *genres = [NSString stringWithFormat:@"%@/%@", [genresArray objectAtIndex:0], [genresArray objectAtIndex:1]];
21         NSString *summary = [self.dic objectForKey:@"summary"];
22         self.text = [NSString stringWithFormat:@"電影名稱:\n%@\n體裁:\n%@\n劇情簡介:\n%@", title, genres, summary];
23 
24         //更新UI操做須要在主線程
25         dispatch_async(dispatch_get_main_queue(), ^{
26             self.textView.text = self.text;
27         });
28     });
29 }

  雖然咱們只用了一個方法,可是這可不表明JsonKit類庫裏就只有這一個解析的方法,咱們能夠去看看它的源碼來找尋一番。通常來說,若是json是「單層」的,即value都是字符串、數字,可使用objectFromJSONString方法,這個也比較簡單。若是json有嵌套,即value裏有array、object,若是再使用objectFromJSONString,程序可能會報錯,這時咱們最好使用objectFromJSONStringWithParseOptions也就是我代碼裏使用的這個方法,由於電影體裁的Value是數組類型的。

TouchJson

來看看最後一個:

 1 //導入包:
 2 #import "ViewController.h"
 3 #import "CJSONSerializer.h"
 4 #import "CJSONDeserializer.h"
 5 
 6 
 7 //
 8 - (IBAction)TouchJson:(UIButton *)sender {
 9     //GCD異步實現
10     dispatch_queue_t q1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
11     dispatch_async(q1, ^{
12 
13         //仍是先獲取url
14         NSURL *url = [NSURL URLWithString:@"https://api.douban.com/v2/movie/subject/22265299"];
15         NSString *jsonString = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];
16         //仍是一句話的事兒
17         self.dic = [[CJSONDeserializer deserializer] deserialize:[jsonString dataUsingEncoding:NSUTF8StringEncoding] error:nil];
18 
19 
20         NSString *title = [self.dic objectForKey:@"original_title"];
21         NSMutableArray *genresArray = [self.dic objectForKey:@"genres"];
22         NSString *genres = [NSString stringWithFormat:@"%@/%@", [genresArray objectAtIndex:0], [genresArray objectAtIndex:1]];
23         NSString *summary = [self.dic objectForKey:@"summary"];
24         self.text = [NSString stringWithFormat:@"電影名稱:\n%@\n體裁:\n%@\n劇情簡介:\n%@", title, genres, summary];
25 
26         //更新UI操做須要在主線程
27         dispatch_async(dispatch_get_main_queue(), ^{
28             self.textView.text = self.text;
29         });
30     });
31 }

Json解析總結

  吶,上述四種方式已經很清楚了,從代碼量上來看,除了那些廢話,原生的解析類庫是實現起來最複雜的,其餘三種卻是挺簡單,經過封裝,只對外提供一個簡單地接口調用就能實現解析功能,性能上都還能夠接受。不過從我親身提回來說,以爲JsonKit是裏面最快的,多是代碼寫的不夠好,原生的解析方式若是好好優化一下的話應該是性能最好的。在實際的使用過程當中選擇一種方式就好。

NSXMLParse

  關於XML,有兩種解析方式,分別是SAX(Simple API for XML,基於事件驅動的解析方式,逐行解析數據,採用協議回調機制)和DOM(Document Object Model ,文檔對象模型。解析時須要將XML文件總體讀入,而且將XML結構化成樹狀,使用時再經過樹狀結構讀取相關數據,查找特定節點,而後對節點進行讀或寫)。蘋果官方原生的NSXMLParse類庫採用第一種方式,即SAX方式解析XML,它基於事件通知的模式,一邊讀取文檔一邊解析數據,不用等待文檔所有讀入之後再解析,因此若是你正打印解析的數據,而解析過程當中間出現了錯誤,那麼在錯誤節點之間的數據會正常打印,錯誤後面的數據不會被打印。解析過程由NSXMLParserDelegate協議方法回調。

  插句題外話先,我在寫這種方式解析XML數據的Demo時折騰了整整一天,提及來都有些很差意思了。程序運行的時候一直出現不能完成解析的狀況,各類查各類試,真的是整了整整一天的時間。就在崩潰的邊緣的時候,我居然發如今我本身寫XML文件時少寫了一個「/。。。瞬間感受整個世界都崩塌了。因此特意記下來警示本身也順便給你們提個醒,在這種低級失誤上浪費整整一天的時間,要多不值有多不值。謹記,謹記。

  咱們遵循MVC,首先咱們建立模型,新建一個person類,存放XML文件中描述的person屬性。再來一個解析XML文件的工具類XMLUtil,咱們在裏面實現文件的獲取,代理方法的實現。

先來看這兩個類的代碼:

 1 //person.h
 2 
 3 #import <Foundation/Foundation.h>
 4 
 5 @interface person : NSObject
 6 @property (nonatomic, copy) NSString *pid;
 7 @property (nonatomic, copy) NSString *name;
 8 @property (nonatomic, copy) NSString *sex;
 9 @property (nonatomic, copy) NSString *age;
10 @end
 1 //XMLUtil.h
 2 
 3 #import <Foundation/Foundation.h>
 4 #import "person.h"
 5 //聲明代理
 6 @interface XMLUtil : NSObject<NSXMLParserDelegate>
 7 //添加屬性
 8 @property (nonatomic, strong) NSXMLParser *par;
 9 @property (nonatomic, strong) person *person;
10 //存放每一個person
11 @property (nonatomic, strong) NSMutableArray *list;
12 //標記當前標籤,以索引找到XML文件內容
13 @property (nonatomic, copy) NSString *currentElement;
14 
15 //聲明parse方法,經過它實現解析
16 -(void)parse;
17 @end
18 
19 
20 
21 //XMLUtil.m
22 
23 #import "XMLUtil.h"
24 
25 @implementation XMLUtil
26 
27 - (instancetype)init{
28     self = [super init];
29     if (self) {
30         //獲取事先準備好的XML文件
31         NSBundle *b = [NSBundle mainBundle];
32         NSString *path = [b pathForResource:@"test" ofType:@".xml"];
33         NSData *data = [NSData dataWithContentsOfFile:path];
34         self.par = [[NSXMLParser alloc]initWithData:data];
35         //添加代理
36         self.par.delegate = self;
37         //初始化數組,存放解析後的數據
38         self.list = [NSMutableArray arrayWithCapacity:5];
39     }
40     return self;
41 }
42 
43 //幾個代理方法的實現,是按邏輯上的順序排列的,但實際調用過程當中中間三個可能由於循環等問題亂掉順序
44 //開始解析
45 - (void)parserDidStartDocument:(NSXMLParser *)parser{
46     NSLog(@"parserDidStartDocument...");
47 }
48 //準備節點
49 - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(nullable NSString *)namespaceURI qualifiedName:(nullable NSString *)qName attributes:(NSDictionary<NSString *, NSString *> *)attributeDict{
50 
51     self.currentElement = elementName;
52 
53     if ([self.currentElement isEqualToString:@"student"]){
54         self.person = [[person alloc]init];
55 
56     }
57 
58 }
59 //獲取節點內容
60 - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
61 
62     if ([self.currentElement isEqualToString:@"pid"]) {
63 
64         [self.person setPid:string];
65     }else if ([self.currentElement isEqualToString:@"name"]){
66         [self.person setName:string];
67     }else if ([self.currentElement isEqualToString:@"sex"]){
68         [self.person setSex:string];
69     }else if ([self.currentElement isEqualToString:@"age"]){
70 
71         [self.person setAge:string];
72     }
73 }
74 
75 //解析完一個節點
76 - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(nullable NSString *)namespaceURI qualifiedName:(nullable NSString *)qName{
77 
78     if ([elementName isEqualToString:@"student"]) {
79         [self.list addObject:self.person];
80     }
81     self.currentElement = nil;
82 }
83 
84 //解析結束
85 - (void)parserDidEndDocument:(NSXMLParser *)parser{
86     NSLog(@"parserDidEndDocument...");
87 }
88 
89 //外部調用接口
90 -(void)parse{
91     [self.par parse];
92 
93 }
94 
95 @end

  OK,總算是大功告成,若是對代理的使用比較熟悉的話,這部份內容其實還蠻簡單的。若是被代碼轉來轉去弄暈了的話能夠在每一個block的最後都加一個打印輸出,作好標記,你就能弄懂程序的執行順序了。

咱們點擊NSXMLParse,有了!

GDataXML

  來看GDataXML,它是一種DOM方式的解析類庫。DOM實現的原理是把整個xml文檔一次性讀出,放在一個樹型結構裏。在須要的時候,查找特定節點,而後對節點進行讀或寫。

  在使用以前呢,咱們仍是先從網上下載GDataXML包,裏面兩個文件GDataXMLNode.h和GDataXMLNode.m導入到項目中來,編譯,發現報錯了,這是由於GDataXML是依賴libmxl2的,咱們要去項目的Target中作一些設置。

  • 找到項目的Tarfet,進入Build Phases裏面的Link Binary With Libraries,點擊「加號」,搜索libxml,把出現的包添加進去,這裏最新版的XCode7和iOS9中,是libxml.2.2.tbd。
  • 再來到Build Settings,咱們能夠搜索一下,找到Header Search Paths,添加路徑「/usr/include/libxml2」。
  • 再找到Other Link Flags,添加「-libxml2「
  • 還有就是若是你下載的GDataXML是不支持ARC的,那麼你就要像上面那樣去添加「-fno-objc-arc」,這個視你下載的GDataXML包版本而定。

再次編譯,就順利經過了。

  接下來看看咱們怎麼用這個東西。貼代碼以前我真的想說一句,比起蘋果原生的類庫,這些開源的第三方類庫真的在用起來的時候不知道有多舒服,懶人必備啊。在實際的開發中能夠爲咱們節省不少的時間與精力,可是仍是要搞懂人家原生的東西,這樣才叫學會了麼。

 1 //ViewController.m
 2 
 3 - (IBAction)GDataXML:(id)sender {
 4 
 5     NSString *path = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"xml"];
 6     NSData *data = [[NSData alloc]initWithContentsOfFile:path];
 7     //對象初始化
 8     GDataXMLDocument *doc = [[GDataXMLDocument alloc]initWithData:data error:nil];
 9     //獲取根節點
10     GDataXMLElement *rootElement = [doc rootElement];
11     //獲取其餘節點
12     NSArray *students = [rootElement elementsForName:@"student"];
13     //初始化可變數組,用來顯示到textView
14     self.GDatatext = [[NSMutableString alloc]initWithString:@""];
15     for (GDataXMLElement *student in students) {
16         //獲取節點屬性
17         GDataXMLElement *pidElement = [[student elementsForName:@"pid"] objectAtIndex:0];
18         NSString *pid = [pidElement stringValue];
19 
20 
21         GDataXMLElement *nameElement = [[student elementsForName:@"name"] objectAtIndex:0];
22         NSString *name = [nameElement stringValue];
23 
24 
25         GDataXMLElement *sexElement = [[student elementsForName:@"sex"] objectAtIndex:0];
26         NSString *sex = [sexElement stringValue];
27 
28 
29         GDataXMLElement *ageElement = [[student elementsForName:@"age"] objectAtIndex:0];
30         NSString *age = [ageElement stringValue];
31 
32         //調整一下姿式,添加到可變長字符串~~
33         NSString *t = [NSString stringWithFormat:@"學號:%@ 姓名:%@ 性別:%@ 年齡:%@\n", pid, name, sex, age];
34         [self.GDatatext appendString:t];
35     }
36     self.textView.text = self.GDatatext;
37 }

就一段,是否是看起來很是的舒服呢!

 

XML解析總結

  上述兩種解析用到的類庫分別表明了兩種典型的XML數據解析方式,SAX和DOM,各有優點,好比在應對比較大數據量的XML文件時,後者因爲須要先讀取整個文檔,性能和速度上就必然不及前者了。

  其實如今在實際應用中XML已經愈來愈少了,可是提及iOS中的網絡編程,就免不了和XML格式的數據打交道。還有就是,咱們在這裏僅僅介紹了兩種經常使用的XML解析方式,如同解析Json數據同樣,解析XML文件也有不少種方法,除了上述兩種,還有好比像TBXML, TouchXML, KissXML, TinyXML等等,具體的使用方法能夠去Github上找,都有使用方法的說明的。

 

參考文章:

http://www.jianshu.com/p/a54d367adb2a

相關文章
相關標籤/搜索