先安利一波:编程
大数据时代,咱们须要从网络中获取海量的新鲜的各类信息,就难免要跟着两个家伙打交道,这是两种结构化的数据交换格式。通常来说,咱们会从网络获取XML或者Json格式的数据,这些数据有着特定的数据结构,必须对其进行解析,获得咱们能够处理的数据。所谓“解析”,就是从事先规定好的格式串中提取数据。解析的前提是数据的提供方与获取方提早约定好格式,数据提供方按照格式提供数据,数据获取方按照格式获取数据。api
iOS开发中,几乎只要是与网络相关的应用,都离不开对网络数据的解析与应用。现总结几种经常使用方式来解析网络数据:数组
按照目前的发展,Json正在逐步取代XML成为网络数据的通用格式,因此咱们重点来看Json格式的数据解析先。浏览器
在看如何使用Json和XML以前,咱们还有些事情要作,一是准备咱们要解析的数据,二是搭建一个界面来看实际效果,毕竟咱们解析了数据就是要在应用中展现出来的。网络
关于如何获取网络的数据在这里就很少赘述了,你只须要得到一个从网站为开发者提供的API接口中得到咱们想要的url就行了。我这里调用了豆瓣电影的API,随便选了在豆瓣电影首页的电影——《前任2:备胎反击战》,来看看豆瓣对这部电影的描述,因为标签太多,我这里只打算从中获取电影名称,体裁和剧情简介三部分打印出来。session
咱们能够先提早在浏览器中打开看一下这个待会咱们将要获得的东西:数据结构
是否是很乱。。。没错,网站返回的东西虽然看上去好像有点规律,可是仍是难以辨别,这里不用担忧,咱们可使用一个叫作Json校验格式化工具的东西来优化一下它的显示,这里有一个在线的。咱们把网站返回给咱们的数据copy到这里,点击校验,若是没有什么问题的话,为了方便展现,我把它copy到了Sublime中,咱们看一下结果你会发现它变成了下面这个样子,这样看起来就舒服多了,咱们也能够很是清楚地看到每一对“Key——Velue”对,以及每一个Velue的类型,弄清楚了,待会儿方便咱们查询和显示。多线程
找到了目标,下一步咱们先作个界面的模子出来,展现咱们解析过的数据。大概就是下面这个样子,点击不一样的按钮,能够以不一样的方式解析得到的数据并在TextView中打印。app
界面搭好以后不要忘了关联到代码。
咱们在项目中新建一个xml文件,编写其中的内容,待会儿解析内容并打印到TextView。
XML内容为Person,有几个学生的信息,包括学号,姓名,性别和年龄,一下子根据这些建立模型。
接下来就正式开始。苹果官方给出的解析方式是性能最优越的,虽然用起来稍显复杂。
首先咱们在上面已经有了我但愿获得的信息的网站的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按钮,就看到咱们要的内容啦!
事实上上面的解析过程仍是挺复杂的,主要是牵扯到了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 }
事实上,它虽然不支持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是数组类型的。
来看看最后一个:
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 }
呐,上述四种方式已经很清楚了,从代码量上来看,除了那些废话,原生的解析类库是实现起来最复杂的,其余三种却是挺简单,经过封装,只对外提供一个简单地接口调用就能实现解析功能,性能上都还能够接受。不过从我亲身提回来说,以为JsonKit是里面最快的,多是代码写的不够好,原生的解析方式若是好好优化一下的话应该是性能最好的。在实际的使用过程当中选择一种方式就好。
关于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,它是一种DOM方式的解析类库。DOM实现的原理是把整个xml文档一次性读出,放在一个树型结构里。在须要的时候,查找特定节点,而后对节点进行读或写。
在使用以前呢,咱们仍是先从网上下载GDataXML包,里面两个文件GDataXMLNode.h和GDataXMLNode.m导入到项目中来,编译,发现报错了,这是由于GDataXML是依赖libmxl2的,咱们要去项目的Target中作一些设置。
再次编译,就顺利经过了。
接下来看看咱们怎么用这个东西。贴代码以前我真的想说一句,比起苹果原生的类库,这些开源的第三方类库真的在用起来的时候不知道有多舒服,懒人必备啊。在实际的开发中能够为咱们节省不少的时间与精力,可是仍是要搞懂人家原生的东西,这样才叫学会了么。
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数据解析方式,SAX和DOM,各有优点,好比在应对比较大数据量的XML文件时,后者因为须要先读取整个文档,性能和速度上就必然不及前者了。
其实如今在实际应用中XML已经愈来愈少了,可是提及iOS中的网络编程,就免不了和XML格式的数据打交道。还有就是,咱们在这里仅仅介绍了两种经常使用的XML解析方式,如同解析Json数据同样,解析XML文件也有不少种方法,除了上述两种,还有好比像TBXML, TouchXML, KissXML, TinyXML等等,具体的使用方法能够去Github上找,都有使用方法的说明的。
参考文章:
http://www.jianshu.com/p/a54d367adb2a