iOS开发-XML解析基础笔记
当你想将某些网络媒体数据载入你的app时,就避免不了和商家提供的数据格式打交道,主要途径就是通过数据交换格式展示给想开发的用户。现在主要的两种数据交换格式为XML,JSON。在iOS开发中,提倡使用JSON格式,但是不乏一些商家并没有提供JSON格式的数据。本文简单讲解以下XML解析的基础,供新手参考,也是共同学习,因为在网上大部分写的都不够详细。之后会写一些关于JSON格式的,感觉JSON比XML的解析还是更加简单易懂易用的。
在iOS中,解析XML有许多框架可以选用,自带NSXMLParser, 一个开源框架libxml2,和其他第三方框架,这里主要说下自带的NSXMLParser的解析,其他第三方框架之后会更新。
NSXMLParser遵守的是SAX解析方式,相对于另外一种DOM的解析方式,SAX是以后逐渐的主流,因为其解析过程就好像是NSURLSession取代NSURLConnection一样,主要还是为了提高用户体验,SAX可以一边解析一边更新数据,而DOM则需要解析完整个文件才能进行处理。
NSXMLParser的实现思想也是完全按照一边解析一边提取数据的思想来做,这点如果是用JSON格式习惯的童鞋们,Google的GDataXML更适合你,其与JSON提取数据的过程有点类似。
这里只说一下如何得到XML里的数据,关于建立,更改XML文件的操作,之后也许会提到。
先贴代码,实现XML解析的model文件
1 #import "XMLProcess.h" 2 @interface XMLProcess()<NSXMLParserDelegate> 3 @property (nonatomic)NSString *webElementNode, *contentStr; 4 @property (nonatomic)BOOL flag; 5 6 @end 7 8 @implementation XMLProcess 9 10 #pragma mark - from web URL 11 12 //- (NSXMLParser *)parser 13 //{ 14 // NSURL *url = [NSURL URLWithString:@"https://itunes.apple.com/us/rss/topsongs/limit=10/xml"]; 15 // NSXMLParser *parser = [[NSXMLParser alloc]initWithContentsOfURL:url]; 16 // parser.delegate = self; 17 // return parser; 18 //} 19 20 #pragma mark - From Local files 21 22 - (NSXMLParser *)parser 23 { 24 NSString *filePath = [[NSBundle mainBundle]pathForResource:@"top10songs" ofType:@"xml"]; 25 NSData *data = [NSData dataWithContentsOfFile:filePath]; 26 NSXMLParser *parser = [[NSXMLParser alloc]initWithData:data]; 27 parser.delegate = self; 28 return parser; 29 } 30 31 - (void)parserDidStartDocument:(NSXMLParser *)parser 32 { 33 self.xmlResult = [[NSMutableArray alloc]init]; 34 } 35 36 - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary<NSString *,NSString *> *)attributeDict 37 { 38 if ([elementName isEqualToString:@"entry"]) { 39 self.flag = YES; 40 } 41 self.webElementNode = [NSString stringWithString:elementName]; 42 self.contentStr = [[NSString alloc]init]; 43 } 44 45 - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string 46 { 47 if (self.flag == YES) { 48 if ([self.webElementNode isEqualToString:@"title"]) { 49 self.contentStr = [NSString stringWithString:string]; 50 } 51 } 52 } 53 54 - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName 55 { 56 if ([elementName isEqualToString:@"title"]) { 57 [self.xmlResult addObject:self.contentStr]; 58 } 59 60 } 61 62 @end
清晰明了,我将NSXMLParser作了一个重写,得到XML文件有两种方式,一种是网络上的XML,一种是本地的XML文件。
网上几乎没有说网络XML文件的,其实也简单,注释掉的line 12-18就是网络XML文件的,line22-29是本地xml文件的,之前需要将XML文件引入你的project,选上copy item if needed。
解析的过程主要是delegate方法,这里以iTunes RSS Top 10 songs的XML为例 https://itunes.apple.com/us/rss/topsongs/limit=10/xml,有两个方法是选用的
1 - (void)parserDidStartDocument:(NSXMLParser *)parser 2 { 3 self.xmlResult = [[NSMutableArray alloc]init]; 4 } 5 6 - (void)parserDidEndDocument:(NSXMLParser *)parser 7 { 8 9 }
这两个方法类似于viewWillAppear和viewDidDisappear这两个方法,一定要用的时候比较少,一般用于锦上添花,显得代码更有逻辑性。比如在DidStart中初始化结果数组,以减少不必要的代码执行。
重点说下line 36 - 58这三个delegate方法,典型的SAX思维,一边解析一边操作数据
说明:iOS中model里自定义的方法需要在controller中能执行的方法中调用才能生效,但是delegate方法放在model中是完全没问题的
1 - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary<NSString *,NSString *> *)attributeDict 2 { 3 if ([elementName isEqualToString:@"entry"]) { 4 self.flag = YES; 5 } 6 self.webElementNode = [NSString stringWithString:elementName]; 7 self.contentStr = [[NSString alloc]init]; 8 }
这个方法是开始遍历节点,由于在iTunes源中,我们需要的数据在众多的<entry></entry>标签中,我选用的是title,而在开始出也有一个<title>iTunes Store: Top Songs</title>,而避免这个不必要的title,则需要加一个BOOL来判断执行,当开始遍历entry标签时,flag=YES。如果你的XML文件中没有你认为多余的标签时,请删除关于flag的代码,由于网上没有写过关于这种情况的判断,所以我写了一下,在你有这种多余某几个标签,而又没有attribute标示时,就像我做示例的这个XML,如果你用的NSXMLParser这种不像NSJSONSerializtion那种处理JSON简单明了的方法,则需要这样判断一下。
argument elementName是标签名称,由于需要将指定标签名称用于下一个delegate方法,所以赋给一个接口变量,self.contentStr代表标签内内容,argument attributeDict是属性的集合,当需要进行特定节点的处理or特定顺序排序时使用,进入下一个
1 - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string 2 { 3 if (self.flag == YES) { 4 if ([self.webElementNode isEqualToString:@"title"]) { 5 self.contentStr = [NSString stringWithString:string]; 6 } 7 } 8 }
这个方法用于找到了特定标签时的处理,注意是开始时,此处当flag==yes也就是属于entry之后的标签时,将title的内容赋予contentStr,NSXMLParser不会在没有属性标示时判断节点的从属级关系,只是从上到下一个一个走。
1 - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName 2 { 3 if ([elementName isEqualToString:@"title"]) { 4 [self.xmlResult addObject:self.contentStr]; 5 } 6 7 }
这个方法是全局的关键,对最后的数据进行操作要在这个方法里进行,意思是当走到结束标签时做的处理,为什么不在上一个delegate方法做最后处理呢?上一个方法的名字看起来更像是需要做最后处理的方法啊。如果做下调试以及想到标签的从属关系,不难想到。
上一个方法是开始的那个时刻做的方法,如果在其中做处理,由于没有关闭标签的限制(因为最后一个方法才是提到关闭标签的方法),会一直输出数据,由于不能进入其他同等级or上级的标签内,所以走到比其高等级标签时,就会输出空行,某些本身标签内的数据也会以一行一个character的形式输出。
所以最后一个方法意思是解析到关闭标签了,这时self.contentStr就表示整个标签内的数据,网上大部分说的将内容string做appendingString处理有时并不是必要的。至此将其加入结果数据,解析完成。以下是viewController的代码
1 #import "ViewController.h" 2 #import "XMLProcess.h" 3 4 @interface ViewController ()<UITableViewDataSource, UITableViewDelegate, NSXMLParserDelegate> 5 @property (nonatomic) UITableView *tableView; 6 7 @property (nonatomic)NSString *webElementNode; 8 @property (nonatomic)NSString *contentStr; 9 @property (nonatomic)NSMutableArray *xmlResult; 10 11 @property (nonatomic)XMLProcess *xmlProcess; 12 @end 13 14 @implementation ViewController 15 16 - (void)viewDidLoad { 17 [super viewDidLoad]; 18 self.tableView = [[UITableView alloc]initWithFrame:self.view.bounds]; 19 [self.tableView setDataSource:self]; 20 [self.tableView setDelegate:self]; 21 [self.view addSubview:self.tableView]; 22 23 self.xmlProcess = [[XMLProcess alloc]init]; 24 25 [self.xmlProcess.parser parse]; 26 } 27 28 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 29 { 30 return 1; 31 } 32 33 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 34 { 35 return self.xmlProcess.xmlResult.count; 36 } 37 38 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 39 { 40 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; 41 if (!cell) { 42 cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"]; 43 } 44 cell.textLabel.text = [self.xmlProcess.xmlResult objectAtIndex:indexPath.row]; 45 return cell; 46 } 47 48 - (void)didReceiveMemoryWarning { 49 [super didReceiveMemoryWarning]; 50 } 51 52 @end
注意在25行,当执行[self.parse parse];方法时才开始解析XML文件从而在制定时刻调用指定的delegate方法,调用顺序是123123123,不同于很多构造方法比如上边的构造tableView的方法调用顺序就是111222333.执行结果如图
希望对纯新手有启发
欢迎访问我的个人主页http://www.jiachengzhi.com
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步