iOS之数据解析 (JSON 、XML)

来源:互联网 发布:php 换行符分割字符串 编辑:程序博客网 时间:2024/05/19 13:09

Json格式:

NSJSONSerialization,官方提供的Json数据格式解析类,iOS5以后支持

JSONKit(第三方类库)

SBJson

TouchJson

XML格式:

NSXMLParse,官方自带

GDataXML,Google提供的开元XML解析库

JSON数据
NSJSONSerialization

苹果官方给出的解析方式是性能最优越的,虽然用起来稍显复杂。

首先我们在上面已经有了我希望得到的信息的网站的API给我们的URL,在OC中,我要加载一个NSURL对象,来向网站提交一个Request。到这里需要特别注意了,iOS9的时代已经来临,我们先前在旧版本中使用的某些类或者方法都已经被苹果官方弃用了。刚刚我们向网站提交了一个Request,在以往,我们是通过NSURLConnection中的sendSynchronousRequest方法来接受网站返回的Response的,但是在iOS9中,它已经不再使用了。从官方文档中,我们追根溯源,找到了它的替代品——NSURLSession类。这个类是iOS7中新的网络接口,苹果力推之,并且现在用它完全替代了NSURLConnection。关于它的具体用法,还是蛮简单的,直接上代码(ViewController.m文件):

 

    #import "ViewController.h"@interface ViewController ()@property (retain, nonatomic) IBOutlet UITextView *textView;
    @property (nonatomic, strong) NSMutableDictionary *dic;
    @property (nonatomic,strong) NSString *text;
    @end@implementation ViewController
    - (IBAction)NSJson:(UIButton *)sender {
       //GCD异步实现dispatch_queue_t q1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
       dispatch_async(q1, ^{
     
           //加载一个NSURL对象NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://api.douban.com/v2/movie/subject/25881786"]];
     
           //使用NSURLSession获取网络返回的Json并处理NSURLSession *session = [NSURLSession sharedSession];
           NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error){
     

               //从网络返回了Json数据,我们调用NSJSONSerialization解析它,将JSON数据转换为Foundation对象(这里是一个字典)            self.dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];

               NSString *title = [self.dic objectForKey:@"original_title"];

               NSMutableArray *genresArray = [self.dic objectForKey:@"genres"];
               NSString *genres = [NSString stringWithFormat:@"%@/%@", [genresArray objectAtIndex:0], [genresArray objectAtIndex:1]];
               NSString *summary = [self.dic objectForKey:@"summary"];
     
               self.text = [NSString stringWithFormat:@"电影名称:\n%@\n体裁:\n%@\n剧情简介:\n%@", title, genres, summary];
     
               //更新UI操作需要在主线程dispatch_async(dispatch_get_main_queue(), ^{
                   self.textView.text = self.text;
               });
           }];
           //调用任务
           [task resume];
       });
    }


SBJson

事实上上面的解析过程还是挺复杂的,主要是牵扯到了NSURLSession的使用。那接下来来看看一些第三方Json解析库的使用。SBJson用起来就简单多了。SBJson支持ARC的,直接引入工程就可以。

    //上面先导入包:#import "ViewController.h"#import "SBJson.h"//实现:
    - (IBAction)SBJson:(UIButton *)sender {
       //GCD异步实现dispatch_queue_t q1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
       dispatch_async(q1, ^{
     
           //还是先获取url        NSURL *url = [NSURL URLWithString:@"https://api.douban.com/v2/movie/subject/25995508"];

           //返回上面url的内容,格式为Json放在了字符串里        NSString *jsonString = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];

           //实例化SBJson对象,将Json格式字符串解析,转化为字典。
           SBJsonParser *parser = [[SBJsonParser alloc] init];
           self.dic = [parser objectWithString:jsonString error:nil];
     
     
           NSString *title = [self.dic objectForKey:@"original_title"];
           NSMutableArray *genresArray = [self.dic objectForKey:@"genres"];
           NSString *genres = [NSString stringWithFormat:@"%@/%@", [genresArray objectAtIndex:0], [genresArray objectAtIndex:1]];
           NSString *summary = [self.dic objectForKey:@"summary"];
           self.text = [NSString stringWithFormat:@"电影名称:\n%@\n体裁:\n%@\n剧情简介:\n%@", title, genres, summary];
     
           //更新UI操作需要在主线程dispatch_async(dispatch_get_main_queue(), ^{
               self.textView.text = self.text;
           });
     
       });
    }


JsonKit

事实上,它虽然不支持ARC,但JsonKit是在性能上仅次于苹果原生解析器的第三方类库。我们在导入它的包以后编译会出现一大堆报错,这时候不用慌,我们会发现大部分是ARC的问题,解决方法也挺简单,我们进入项目的Target,找到Build Phases里面的Compile Sources,接着找我们的问题源头JsonKit.m,双击更改它的Compiler Flags标签为“-fno-objc-arc”,再次编译,就好啦~


    //上面先导入包:#import "ViewController.h"#import "JsonKit.h"//实现
    - (IBAction)JsonKit:(UIButton *)sender {
       //GCD异步实现dispatch_queue_t q1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
       dispatch_async(q1, ^{
     

           //还是先获取url        NSURL *url = [NSURL URLWithString:@"https://api.douban.com/v2/movie/subject/26279433"];

           NSString *jsonString = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];

           //代码越来越简单了有木有!!就一个方法搞定~        self.dic = [jsonString objectFromJSONStringWithParseOptions:JKParseOptionLooseUnicode];

     
     
           NSString *title = [self.dic objectForKey:@"original_title"];
           NSMutableArray *genresArray = [self.dic objectForKey:@"genres"];
           NSString *genres = [NSString stringWithFormat:@"%@/%@", [genresArray objectAtIndex:0], [genresArray objectAtIndex:1]];
           NSString *summary = [self.dic objectForKey:@"summary"];
           self.text = [NSString stringWithFormat:@"电影名称:\n%@\n体裁:\n%@\n剧情简介:\n%@", title, genres, summary];
     
           //更新UI操作需要在主线程dispatch_async(dispatch_get_main_queue(), ^{
               self.textView.text = self.text;
           });
       });
    }


虽然我们只用了一个方法,但是这可不代表JsonKit类库里就只有这一个解析的方法,我们可以去看看它的源码来找寻一番。一般来讲,如果json是“单层”的,即value都是字符串、数字,可以使用objectFromJSONString方法,这个也比较简单。如果json有嵌套,即value里有array、object,如果再使用objectFromJSONString,程序可能会报错,这时我们最好使用objectFromJSONStringWithParseOptions也就是我代码里使用的这个方法,因为电影体裁的Value是数组类型的。

TouchJson

    //导入包:#import "ViewController.h"#import "CJSONSerializer.h"#import "CJSONDeserializer.h"//
    - (IBAction)TouchJson:(UIButton *)sender {
       //GCD异步实现dispatch_queue_t q1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
       dispatch_async(q1, ^{
     

           //还是先获取url        NSURL *url = [NSURL URLWithString:@"https://api.douban.com/v2/movie/subject/22265299"];

           NSString *jsonString = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];

           //还是一句话的事儿        self.dic = [[CJSONDeserializer deserializer] deserialize:[jsonString dataUsingEncoding:NSUTF8StringEncoding] error:nil];

             NSString *title = [self.dic objectForKey:@"original_title"];

           NSMutableArray *genresArray = [self.dic objectForKey:@"genres"];
           NSString *genres = [NSString stringWithFormat:@"%@/%@", [genresArray objectAtIndex:0], [genresArray objectAtIndex:1]];
           NSString *summary = [self.dic objectForKey:@"summary"];
           self.text = [NSString stringWithFormat:@"电影名称:\n%@\n体裁:\n%@\n剧情简介:\n%@", title, genres, summary];
     
           //更新UI操作需要在主线程dispatch_async(dispatch_get_main_queue(), ^{
               self.textView.text = self.text;
           });
       });
    }

Json解析总结

上述四种方式已经很清楚了,从代码量上来看,除了那些废话,原生的解析类库是实现起来最复杂的,其他三种倒是挺简单,通过封装,只对外提供一个简单地接口调用就能实现解析功能,性能上都还可以接受。不过从我亲身提回来讲,觉得JsonKit是里面最快的,可能是代码写的不够好,原生的解析方式如果好好优化一下的话应该是性能最好的。在实际的使用过程中选择一种方式就好。

XML解析

NSXMLParse

关于XML,有两种解析方式,分别是SAX(Simple API for XML,基于事件驱动的解析方式,逐行解析数据,采用协议回调机制)和DOM(Document Object Model ,文档对象模型。解析时需要将XML文件整体读入,并且将XML结构化成树状,使用时再通过树状结构读取相关数据,查找特定节点,然后对节点进行读或写)。苹果官方原生的NSXMLParse类库采用第一种方式,即SAX方式解析XML,它基于事件通知的模式,一边读取文档一边解析数据,不用等待文档全部读入以后再解析,所以如果你正打印解析的数据,而解析过程中间出现了错误,那么在错误节点之间的数据会正常打印,错误后面的数据不会被打印。解析过程由NSXMLParserDelegate协议方法回调。

先来看这两个类的代码:


    //person.h
     
    #import <Foundation/Foundation.h>
     
    @interface person : NSObject
    @property (nonatomic, copy) NSString *pid;
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, copy) NSString *sex;
    @property (nonatomic, copy) NSString *age;
    @end
    //XMLUtil.h
     
    #import <Foundation/Foundation.h>
    #import "person.h"
    //声明代理
    @interface XMLUtil : NSObject<NSXMLParserDelegate>
    //添加属性
    @property (nonatomic, strong) NSXMLParser *par;
    @property (nonatomic, strong) person *person;
    //存放每个person
    @property (nonatomic, strong) NSMutableArray *list;
    //标记当前标签,以索引找到XML文件内容
    @property (nonatomic, copy) NSString *currentElement;
     
    //声明parse方法,通过它实现解析
    -(void)parse;
    @end
     
     
     
    //XMLUtil.m
     
    #import "XMLUtil.h"
     
    @implementation XMLUtil
     
    - (instancetype)init{
       self = [super init];
       if (self) {
           //获取事先准备好的XML文件
           NSBundle *b = [NSBundle mainBundle];
           NSString *path = [b pathForResource:@"test" ofType:@".xml"];
           NSData *data = [NSData dataWithContentsOfFile:path];
           self.par = [[NSXMLParser alloc]initWithData:data];
           //添加代理
           self.par.delegate = self;
           //初始化数组,存放解析后的数据
           self.list = [NSMutableArray arrayWithCapacity:5];
       }
       return self;
    }
     
    //几个代理方法的实现,是按逻辑上的顺序排列的,但实际调用过程中中间三个可能因为循环等问题乱掉顺序
    //开始解析
    - (void)parserDidStartDocument:(NSXMLParser *)parser{
       NSLog(@"parserDidStartDocument...");
    }
    //准备节点
    - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(nullable NSString *)namespaceURI qualifiedName:(nullable NSString *)qName attributes:(NSDictionary<NSString *, NSString *> *)attributeDict{
     
       self.currentElement = elementName;
     
       if ([self.currentElement isEqualToString:@"student"]){
           self.person = [[person alloc]init];
     
       }
     
    }
    //获取节点内容
    - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
     
       if ([self.currentElement isEqualToString:@"pid"]) {
     
           [self.person setPid:string];
       }else if ([self.currentElement isEqualToString:@"name"]){
           [self.person setName:string];
       }else if ([self.currentElement isEqualToString:@"sex"]){
           [self.person setSex:string];
       }else if ([self.currentElement isEqualToString:@"age"]){
     
           [self.person setAge:string];
       }
    }
     
    //解析完一个节点
    - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(nullable NSString *)namespaceURI qualifiedName:(nullable NSString *)qName{
     
       if ([elementName isEqualToString:@"student"]) {
           [self.list addObject:self.person];
       }
       self.currentElement = nil;
    }
     
    //解析结束
    - (void)parserDidEndDocument:(NSXMLParser *)parser{
       NSLog(@"parserDidEndDocument...");
    }
     
    //外部调用接口
    -(void)parse{
       [self.par parse];
     
    }
     
    @end


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包版本而定。

再次编译,就顺利通过了。

接下来看看我们怎么用这个东西。贴代码之前我真的想说一句,比起苹果原生的类库,这些开源的第三方类库真的在用起来的时候不知道有多舒服,懒人必备啊。在实际的开发中可以为我们节省很多的时间与精力,但是还是要搞懂人家原生的东西,这样才叫学会了么。


    //ViewController.m
    - (IBAction)GDataXML:(id)sender {
       NSString *path = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"xml"];
       NSData *data = [[NSData alloc]initWithContentsOfFile:path];
       //对象初始化
       GDataXMLDocument *doc = [[GDataXMLDocument alloc]initWithData:data error:nil];
       //获取根节点
       GDataXMLElement *rootElement = [doc rootElement];
       //获取其他节点NSArray *students = [rootElement elementsForName:@"student"];
       //初始化可变数组,用来显示到textViewself.GDatatext = [[NSMutableString alloc]initWithString:@""];
       for (GDataXMLElement *student in students) {
           //获取节点属性
           GDataXMLElement *pidElement = [[student elementsForName:@"pid"] objectAtIndex:0];
           NSString *pid = [pidElement stringValue];
     
           GDataXMLElement *nameElement = [[student elementsForName:@"name"] objectAtIndex:0];
           NSString *name = [nameElement stringValue];
     
           GDataXMLElement *sexElement = [[student elementsForName:@"sex"] objectAtIndex:0];
           NSString *sex = [sexElement stringValue];
     
           GDataXMLElement *ageElement = [[student elementsForName:@"age"] objectAtIndex:0];
           NSString *age = [ageElement stringValue];
     
           //调整一下姿势,添加到可变长字符串~~NSString *t = [NSString stringWithFormat:@"学号:%@ 姓名:%@ 性别:%@ 年龄:%@\n", pid, name, sex, age];
           [self.GDatatext appendString:t];
       }
       self.textView.text = self.GDatatext;
    }


XML解析总结

上述两种解析用到的类库分别代表了两种典型的XML数据解析方式,SAX和DOM,各有优势,比如在应对比较大数据量的XML文件时,后者由于需要先读取整个文档,性能和速度上就必然不及前者了。

其实现在在实际应用中XML已经越来越少了,但是说起iOS中的网络编程,就免不了和XML格式的数据打交道。还有就是,我们在这里仅仅介绍了两种常用的XML解析方式,如同解析Json数据一样,解析XML文件也有很多种方法,除了上述两种,还有比如像TBXML, TouchXML, KissXML, TinyXML等等,具体的使用方法可以去Github上找,都有使用方法的说明的。

 
0 0
原创粉丝点击