在工作中,不可避免的我们会频繁从网络获取XML或者Json格式的数据,这些数据有着特定的数据结构,必须对其进行解析,得到我们可以处理的数据。
总结几种iOS开发中常用来解析网络数据的几种方式:
Demo传送门:https://github.com/Elbertz/ZDXXML-JsonAnalyze
Json格式:
NSJSONSerialization,官方提供的Json数据格式解析类,iOS5以后支持
SBJson
XML格式:
NSXMLParse,官方自带
XMLDictionary,好用的开源XML解析方案
GDataXML,Google提供的开源XML解析库
一、XML解析
NSXMLParser
NSXMLParser是Foundation库中提供的解析XML的类,支持从XML文件路径URL和XML数据等多种方式进行解析,常用调用接口如下:
- (nullable instancetype)initWithContentsOfURL:(NSURL *)url;
- (instancetype)initWithData:(NSData *)data;
还有许多代理方法帮助我们获取XML数据的节点以及对应的数值。
基础使用方法:
self.par = [[NSXMLParser alloc]initWithData:data];
self.par.delegate = self;
//初始化数组,存放解析后的数据
self.list = [NSMutableArray array];
[self.par parse];
遵循NSXMLParserDelegate协议,我们可以在代理方法中获取并处理XML解析后的数据,其中最重要的回调是:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(nullable NSString *)namespaceURI qualifiedName:(nullable NSString *)qName attributes:(NSDictionary*)attributeDict;
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string;
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(nullable NSString *)namespaceURI qualifiedName:(nullable NSString *)qName;
基础流程:
首先通过第一个回调获得节点名字以及节点属性列表,即elementName和attributeDict;
其次在第二个回调中,通过上面获取到的节点名字获取节点内容,即string;
第三个回调是当一个节点解析完毕后触发。
XMLDictionary
XMLDictionary是基于NSXMLParser的封装,在NSXMLParser进行解析过程中,如果事先并不清楚XML节点的名字,会在数据处理过程中花费很多时间。XMLDictionary就是完成了这个处理,让我们可以立即拿到字典结构的XML解析数据,方便开发。
使用之前需要做的设置:
导入XMLDictionary.h XMLDictionary.m文件
+ (nullable NSDictionary*)dictionaryWithXMLParser:(NSXMLParser *)parser;
+ (nullable NSDictionary*)dictionaryWithXMLData:(NSData *)data;
+ (nullable NSDictionary*)dictionaryWithXMLString:(NSString *)string;
+ (nullable NSDictionary*)dictionaryWithXMLFile:(NSString *)path;
从参数可知,无论你网络接收到的XML数据是以data数据还是文件还是字符串,都可以满足你的需求。
源码解析:
除了基于NSXMLParserDelegate的三个主要回调,你还需要注意以下方法:
- (void)addText:(NSString *)text;
- (void)endText;
+ (NSString *)XMLStringForNode:(id)node withNodeName:(NSString *)nodeName;
上述几个方法源码获取到XML解析的数据生成NSDictionary的关键。
- (void)parser:(__unused NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(__unused NSString *)namespaceURI qualifiedName:(__unused NSString *)qName attributes:(NSDictionary *)attributeDict{
[self endText];
NSMutableDictionary*node = [NSMutableDictionary dictionary];
。。。
node[XMLDictionaryNodeNameKey] = elementName;
。。。
for (NSString *key in attributeDict)
{
node[[XMLDictionaryAttributePrefix stringByAppendingString:key]] = attributeDict[key];
}
。。。
if (!_root){
_root = node;
_stack = [NSMutableArray arrayWithObject:node];
if (_wrapRootNode) {
_root = [NSMutableDictionary dictionaryWithObject:_root forKey:elementName];
[_stack insertObject:_root atIndex:0];
}}else{
NSMutableDictionary*top = _stack.lastObject;
id existing = top[elementName];
if ([existing isKindOfClass:[NSArray class]])
{
[(NSMutableArray *)existing addObject:node];
}
else if (existing)
{
top[elementName] = [@[existing, node] mutableCopy];
}
else if (_alwaysUseArrays)
{
top[elementName] = [NSMutableArray arrayWithObject:node];
}
else
{
top[elementName] = node;
}
[_stack addObject:node];
}}
_root是解析完成后最终返回的字典对象;
_stack是包含_root和临时数据的一个可变数组(NSMutableArray),是_root能得到正确的XML数据的关键所在。
代码解析:根据获取到的节点名字(elementName)以及属性完成对_root的初始赋值,_stack把_root作为它本身的首项,使得后面获取到的子节点的属性以及内容,成为_root字典结构下的一部分。
- (void)parser:(__unused NSXMLParser *)parser foundCharacters:(NSString *)string
{
[self addText:string];
}
代码解析:获取节点的内容,存放在text,然后在 [self endText]中完成了对_root的写入。
GDataXML
GDataXML的解析原理是把整个xml文档一次性读出,放在一个树型结构里。在需要的时候,查找特定节点,然后对节点进行读或写。
使用之前需要做的设置:
1.导入GDataXMLNode.h和GDataXMLNode.m文件
2.引入libxml.2.2.tbd
3.选择项目的Build Settings下的
Search Paths/Header Search Paths ,加入 /usr/include/libxml2
Linking/Other Linker Flags,加入-lxml2 (Xcode8以下)
4.将GDataXMLNode.m 标记为不使用ARC: -fno-objc-arc
-(void)testGDataXMLAnalyze{
NSString *XMLPath = [[NSBundle mainBundle].bundleURL URLByAppendingPathComponent:@"config.xml" isDirectory:YES].path;
NSURL *url = [NSURL fileURLWithPath:XMLPath];
NSData *data = [NSData dataWithContentsOfURL:url];
NSLog(@"data = %@", data);
//1.首先根据XML数据生成XMLDocument对象,这个对象
GDataXMLDocument *document = [[GDataXMLDocument alloc] initWithData:data options:0 error:nil];
NSLog(@"document = %@", document);
//获取根节点
GDataXMLElement *rootElement = [document rootElement];
NSLog(@"rootElement = %@", rootElement);
//获取其他节点
NSArray *list = [rootElement elementsForName:@"List"];
NSLog(@"list = %@", list);
for (GDataXMLElement *obj in list) {
NSArray *items = [obj elementsForName:@"Item"];
NSLog(@"items = %@", items);
for (GDataXMLElement *obj in items) {
NSString *name = [[obj attributeForName:@"name"] stringValue];
NSString *value = [[obj attributeForName:@"value"] stringValue];
NSLog(@"name=%@--value=%@",name,value);
}}}
代码解析:
首先,根据XML的data生成GDataXMLDocument对象,这个对象是由libxml库解析出的标准XML数据结构构成(对于熟悉XML数据结构的童鞋很好理解)。
后面就很好理解了,根据节点结构逐一解析出所需要的节点内容。
PS:其他的还有比如像TBXML, TouchXML, KissXML, TinyXML等等,具体的使用方法可以去Github上找,后面有时间的话会分析上述XML解析方案并给出Demo。
二、Json解析
NSJSONSerialization
NSJSONSerialization是系统提供给我们的解析Json的类,用法简单:
NSString *jsonPath = [[NSBundle mainBundle].bundleURL URLByAppendingPathComponent:@"ZDXDirections.geojson" isDirectory:YES].path;
NSLog(@"jsonPath=%@",jsonPath);
。。。
NSData *data = [NSData dataWithContentsOfFile:jsonPath];
NSLog(@"data=%@",data);
[[SysJsonAnalyzeEngine shareInstance] startJsonAnalyzeWithData:data];
代码解析:我创建了一个ZDXDirections.geojson文件,存储Json 数据。在实际开发中大多是从HTTP请求中获取Json数据并进行解析。在iOS9之后,使用NSURLSession进行网络请求也很简单。
SBJson
这是一个比较久远使用比较广的一个解析Json数据的三方库。只是针对于数据解析使用方法非常简单。
NSString *jsonPath = [[NSBundle mainBundle].bundleURL URLByAppendingPathComponent:@"ZDXDirections.geojson" isDirectory:YES].path;
NSLog(@"jsonPath=%@",jsonPath);
NSData *data = [NSData dataWithContentsOfFile:jsonPath options:NSDataReadingMappedAlways error:nil];
NSLog(@"data=%@",data);
SBJsonParser *parser = [[SBJsonParser alloc]init];
NSDictionary *dic = [parser objectWithData:data];
NSLog(@"dic = %@",dic);
其他还有JSONKit、TouchJson等三方的解析库,都是使用方便应用广泛的解析Json数据的库,感兴趣的童鞋都可以根据自己的实际需求选择适合自己的解析方案。