// MainViewController.m // JSON & XML // Created by apple on 13-10-10. /* 异步加载网络图像的内存缓存解决方法 1. 在对象中定义一个UIImage 2. 在控制器中,填充表格内容时,判断UIImage是否存在内容 1> 如果cacheImage不存在,显示占位图像,同时开启异步网络连接加载网络图像 网络图像加载完成后,先设置对象的cacheImage 设置完成后,再刷新表格对应的行 2> 如果cacheImage存在,直接显示cacheImage // UITableView数据源方法 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 1. 使用可重用标示符查询可重用单元格 VideoCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; 注册可重用单元格后,不再需要使用以下实例化方法 if (cell == nil) { cell = [[VideoCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID]; } 设置单元格独一无二的内容 */ #import "MainViewController.h" #import "Video.h" #import "VideoCell.h" // 注册可重用单元格步骤一: static NSString *ID = @"MyCell"; #define kBaseURL @"http://192.168.3.252/~apple" @interface MainViewController () <uitableviewdatasource uitableviewdelegate="" nsxmlparserdelegate=""> // 苹果官方推荐控件用weak @property (weak, nonatomic) UITableView *tableView; // 全局的数据数组 数组中的元素是video对象实例 @property (strong, nonatomic) NSMutableArray *dataList; // 1) 全局的字符串,记录每一个元素的完整内容,主要用于拼接 @property (strong, nonatomic) NSMutableString *tempStr; // 2) 全局的video对象,记录当前正在解析的元素 @property (strong, nonatomic) Video *currentVideo; @end @implementation MainViewController /* 在开发网络应用中 1. 数据是同步加载的,可以保证用户有的看 2. 图像、音频、视频是异步加载的,保证在不阻塞主线程使用的前提下,用户能够渐渐地看到多媒体信息 XML文件解析步骤 1). 解析文档 在整个XML文件解析完成之前,2、3、4方法会不断被循环调用 2). 开始解析一个元素 3). 接收元素的数据(因为元素内容过大,此方法可能会被重复调用,需要拼接数据) 4). 结束解析一个元素 5). 解析文档结束 6). 解析出错 */ - (void)viewDidLoad { [super viewDidLoad]; // 调用自定义方法,加载UI界面 [self loadUI] // 注册可重用单元格 [self.tableView registerClass:[VideoCell class] forCellReuseIdentifier:ID]; } // 自定义方法,加载UI界面 - (void)loadUI { self.view = [[UIView alloc]initWithFrame: [UIScreen mainScreen].applicationFrame]; // 1. tableView CGRect frame = self.view.bounds; UITableView *tableView = [[UITableView alloc]initWithFrame: CGRectMake(0, 0, frame.size.width, frame.size.height - 44) style:UITableViewStylePlain]; // 1) tableView的数据源为 当前控制器 [tableView setDataSource:self]; // 2) tableView的代理为 当前控制器 [tableView setDelegate:self]; // 3) tableView的每一行的高度 [tableView setRowHeight:80]; // 4) 设置分隔线样式 [tableView setSeparatorStyle:UITableViewCellSeparatorStyleSingleLine]; [self.view addSubview:tableView]; // 用成员变量,记住实例化的 tableView self.tableView = tableView; // 2. toolBar UIToolbar *toolBar = [[UIToolbar alloc]initWithFrame: CGRectMake(0, tableView.bounds.size.height, 320, 44)]; [self.view addSubview:toolBar]; // 添加toolBar按钮 // BarButtonItem之 加载json UIBarButtonItem *btn_item_json = [[UIBarButtonItem alloc]initWithTitle: @"load json" style:UIBarButtonItemStyleDone target:self action:@selector(loadJson)]; // BarButtonItem之 加载xml UIBarButtonItem *btn_item_xml = [[UIBarButtonItem alloc]initWithTitle: @"load xml" style:UIBarButtonItemStyleDone target:self action:@selector(loadXML)]; // BarButtonItem之 弹簧 FlexibleSpace UIBarButtonItem *item3 = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; // 将所有的BarButtonItem 添加到 UIToolbar [toolBar setItems:@[item3, btn_item_json, item3, btn_item_xml, item3]]; } // UITableView数据源方法 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.dataList.count; } // UITableView数据源方法 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // 1. 使用可重用标示符查询可重用单元格 VideoCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; // 注册可重用单元格后,不再需要使用以下实例化方法 // if (cell == nil) { // cell = [[VideoCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID]; // } // 设置单元格独一无二的内容 Video *v = self.dataList[indexPath.row]; cell.textLabel.text = v.name; cell.detailTextLabel.text = v.teacher; cell.lengthLabel.text = v.lengthStr; // 加载图片 // 1) 同步加载网络图片 // 注意:在开发网络应用时,不要使用同步方法加载图片,因为严重影响用户体验 // 同步方法,意味着,这一指令执行完成之前,后续的指令都无法执行 // NSString *imagePath = [NSString stringWithFormat:@"%@%@", kBaseURL, v.imageURL]; // NSURL *imageUrl = [NSURL URLWithString:imagePath]; // NSData *imageData = [NSData dataWithContentsOfURL:imageUrl]; // UIImage *image = [UIImage imageWithData:imageData]; // 2) 异步加载网络图片 // gcd、nsoperation、nsthread // 网络连接本身就有异步命令 sendAsync // 如果缓存图像不存在 if (v.cacheImage == nil) { // 使用默认图像占位,既能够保证有图像,又能够保证有地方! UIImage *image = [UIImage imageNamed:@"user_default.png"]; [cell.imageView setImage:image]; // 自定义方法,开启异步加载图像,加载完图片,然后才刷新对应的表格行 [self loadImageAsyncWithIndexPath:indexPath]; } else { // 如果有缓存,就从缓存中取出图片 [cell.imageView setImage:v.cacheImage]; } return cell; } #pragma mark 自定义方法,异步加载网络图片 // 由于UITableViewCell是可重用的,为了避免用户频繁快速刷新表格,造成数据冲突, // 不能直接将UIImageView传入异步方法 // 正确地解决方法是:将表格行的indexPath传入异步方法,加载完成图像后,直接刷新指定的行 - (void)loadImageAsyncWithIndexPath:(NSIndexPath *)indexPath { // 取出数据中对应的model Video *v = self.dataList[indexPath.row]; // 1. url NSString *imagePath = [NSString stringWithFormat:@"%@%@", kBaseURL, v.imageURL]; NSURL *imageUrl = [NSURL URLWithString:imagePath]; // 2. request NSURLRequest *request = [NSURLRequest requestWithURL:imageUrl]; // 3. connection类方法, 发送异步请求, [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { // 将网络数据保存至Video的缓存图像 v.cacheImage = [UIImage imageWithData:data]; // 更改模型之后,才能刷新表格 [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft]; }]; } // 响应点击,加载JSON - (void)loadJson { // 从web服务器直接加载数据 NSString *str = @"http://ip/~apple/itcast/videos.php?format=json"; // 提示:NSData本身具有同步方法,但是在实际开发中,不要使用此方法 // 在使用NSData的同步方法时,无法指定超时时间,如果服务器连接不正常,会影响用户体验 // NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:str]]; // 1. 建立NSURL NSURL *url = [NSURL URLWithString:str]; // 2. 建立NSURLRequest NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:2.0f]; // 3. 利用NSURLConnection的同步方法加载数据 NSURLResponse *response = nil; NSError *error = nil; NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; // 不要忘记错误处理 if (data != nil) { // 仅用于跟踪调试使用 NSString *result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; // 做JSON数据的处理 // 提示:在处理网络数据时,不需要将NSData转换成NSString // 调用自定义方法,解析返回的json数据data [self handlerJSONData:data]; } else if (data == nil && error == nil) { NSLog(@"空数据"); } else { NSLog(@"%@", error.localizedDescription); } } // 自定义方法,解析返回的json数据data - (void)handlerJSONData:(NSData *)data { // JSON文件中的[]表示是一个数组 // 反序列化JSON数据 /* 序列化: 将NSObject转换成序列数据,以便可以通过互联网进行传输 反序列化: 将网络上获取的数据,反向生成我们需要的对象 */ // JSONObjectWithData通过data生成json对象 NSArray *array = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil]; // 提示:如果开发网络应用,可以将反序列化出来的对象,保存至沙箱,以便后续开发使用 NSArray *docs = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *path = [docs[0]stringByAppendingPathComponent:@"json.plist"]; [array writeToFile:path atomically:YES]; // 给model 即数据列表赋值 NSMutableArray *arrayM = [NSMutableArray array]; for (NSDictionary *dict in array) { Video *video = [[Video alloc]init]; // 给video赋值 [video setValuesForKeysWithDictionary:dict]; [arrayM addObject:video]; } // 用成员数组记住,数组中的元素是video对象实例 self.dataList = arrayM; // 数据模型有东东,之后,就可以刷新表格了 [self.tableView reloadData]; } // 响应点击,加载XML - (void)loadXML { // 0. 获取网络数据 // 从web服务器直接加载数据 NSString *str = @"http://ip/~apple/itcast/videos.php?format=xml"; // 1) 建立NSURL NSURL *url = [NSURL URLWithString:str]; // 2) 建立NSURLRequest NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:2.0f]; // 3) 利用NSURLConnection的同步方法加载数据 NSURLResponse *response = nil; NSError *error = nil; NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; // 1. 实例化解析器,传入要解析的数据 NSXMLParser *parser = [[NSXMLParser alloc]initWithData:data]; // 2. 设置解析器的代理 [parser setDelegate:self]; // 3. 解析器开始解析,其余交给代理去处理即可 [parser parse]; } /* XML解析的思路 目前的资源:dataList记录表格中显示的数组,保存video对象。 0. 数据初始化的工作,实例化dataList和发现文本方法中要使用的临时字符串 1. 如果在开始节点方法中,elementName == video,会在attributeDict中包含videoId属性 如果在开始节点方法中,elementName == video,需要实例化一个video实例, 发现文本的方法会被多次调用 2. 在发现文本的方法中,需要拼接字符串——需要定义一个临时字符串用于拼接 3. 在节点结束的方法中,可以将拼接的字符串,对video实例进行赋值 在节点结束的方法中,如果elementName == video,则将该对象加入对象数组dataList 需要的准备工作 1) 临时字符串,用于拼接 每次开始元素节点的时候,都要先清空 每次结束元素节点的时候,都要为对象成员赋值 2) 全局的video对象,代表当前正在解析的元素节点对应的数据模型对象 */ // XML解析器代理方法 1. 开始文档解析 - (void)parserDidStartDocument:(NSXMLParser *)parser { // 懒加载实例化数组,未来成员将是一个个vedio对象 if (self.dataList == nil) { self.dataList = [NSMutableArray array]; } else { // 清空数组,以防万一 [self.dataList removeAllObjects]; } // 中间字符串 if (self.tempStr == nil) { self.tempStr = [NSMutableString string]; } } // 在整个XML文件解析完成之前,2、3、4方法会不断被循环调用 // XML解析器代理方法 2. 开始元素节点解析 - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { NSLog(@"开始解析元素节点 %@ %@", elementName, attributeDict); if ([elementName isEqualToString:@"video"]) { // 1. 实例化currentVideo self.currentVideo = [[Video alloc]init]; // 2. 设置videoId,是video节点的一个属性 self.currentVideo.videoId = [attributeDict[@"videoId"]integerValue]; } // 清空临时字符串,为文本节点做准备 [self.tempStr setString:@""]; } // XML解析器代理方法 3. 发现文本,主要是拼接 // 3. 发现字符,因为文本内容过大,此方法可能会被重复调用,需要拼接数据) - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { // 只需拼接字符串 [self.tempStr appendString:string]; } // XML解析器代理方法 4. 元素节点结束,工作:一个完整的对象添加到数组中 - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { // 取出临时的字符串 NSString *result = [NSString stringWithString:self.tempStr]; if ([elementName isEqualToString:@"name"]) { self.currentVideo.name = result; } else if ([elementName isEqualToString:@"length"]) { self.currentVideo.length = [result integerValue]; } else if ([elementName isEqualToString:@"videoURL"]) { self.currentVideo.videoURL = result; } else if ([elementName isEqualToString:@"imageURL"]) { self.currentVideo.imageURL = result; } else if ([elementName isEqualToString:@"desc"]) { self.currentVideo.desc = result; } else if ([elementName isEqualToString:@"teacher"]) { self.currentVideo.teacher = result; } else if ([elementName isEqualToString:@"video"]) { // 如果是代表一个对象解析完毕,添加到数组 [self.dataList addObject:self.currentVideo]; } } // XML解析器代理方法 5. 文档解析完毕,数据已经准备好了,刷新表格 - (void)parserDidEndDocument:(NSXMLParser *)parser { // 清空临时变量 self.currentVideo = nil; [self.tempStr setString:@""]; // 数据已经准备好了,刷新表格 [self.tableView reloadData]; } // XML解析器代理方法 6. 解析出错 - (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError { NSLog(@"解析出现错误!"); // 清空临时数据 self.currentVideo = nil; [self.tempStr setString:@""]; // 清空数组 [self.dataList removeAllObjects]; } @end </uitableviewdatasource>
H:/1010/00_NSArray+Log.m
// NSArray+Log.m // JSON & XML // Created by apple on 13-10-10. // 重写数组的输入显示方式 #import "NSArray+Log.h" // ()里面为空,是extension类扩展 // ()里面有东西,是categroy分类 @implementation NSArray (Log) - (NSString *)descriptionWithLocale:(id)locale { NSMutableString *str = [NSMutableString string]; [str appendFormat:@"%d (", self.count]; // 遍历数组里面的所有内容 in self for (NSObject *obj in self) { [str appendFormat:@"\t%@\n,", obj]; } [str appendString:@")"]; return str; } @end
H:/1010/00_Video.m
// Video.h // JSON & XML // Created by apple on 13-10-10. /* 异步加载网络图像的内存缓存解决方法 1. 在对象中定义一个UIImage 2. 在控制器中,填充表格内容时,判断UIImage是否存在内容 1> 如果cacheImage不存在,显示占位图像,同时开启异步网络连接加载网络图像 网络图像加载完成后,先设置对象的cacheImage 设置完成后,再刷新表格对应的行 2> 如果cacheImage存在,直接显示cacheImage */ #import <Foundation/Foundation.h> @interface Video : NSObject // 成员依次是:视频id,名称,长度(秒数),视频url,图片url,描述,授课老师 @property (assign, nonatomic) NSInteger videoId; @property (strong, nonatomic) NSString *name; @property (assign, nonatomic) NSInteger length; @property (strong, nonatomic) NSString *videoURL; @property (strong, nonatomic) NSString *imageURL; @property (strong, nonatomic) NSString *desc; @property (strong, nonatomic) NSString *teacher; // 缓存图片 @property (strong, nonatomic) UIImage *cacheImage; // 视频时长的字符串 @property (strong, nonatomic, readonly) NSString *lengthStr; @end //====================================================================== //====================================================================== // Video.m // JSON & XML // Created by apple on 13-10-10. #import "Video.h" @implementation Video // 返回格式化后的时长 - (NSString *)lengthStr { return [NSString stringWithFormat:@"%02d:%02d", self.length / 60, self.length % 60]; } // 重写 toString方法 - (NSString *)description { return [NSString stringWithFormat:@"<Video: %p, video id: %d, name: %@" "length: %d videoURL: %@ imageURL: %@ desc: %@" "teacher: %@ >", self, self.videoId, self.name, self.length, self.videoURL, self.imageURL, self.desc, self.teacher]; } @end
H:/1010/00_VideoCell.m
// VideoCell.h // JSON & XML // Created by apple on 13-10-10. #import <UIKit/UIKit.h> // 自定义cell 继承自 UITableViewCell @interface VideoCell : UITableViewCell // 成员 时长 标签 @property (weak, nonatomic) UILabel *lengthLabel; @end //============================================================= //============================================================= //============================================================= // VideoCell.m // JSON & XML // Created by apple on 13-10-10. #import "VideoCell.h" @implementation VideoCell /* 如果在自定义单元格中,要修改默认单元格内对象的位置 则必须重写 layoutSubviews 方法,对视图中的所有控件的位置进行调整 且,要先调用父类的 layoutSubviews方法 */ #pragma mark - 重新调整UITalbleViewCell中的控件布局,必须用此方法 - (void)layoutSubviews { // 千万不要忘记super layoutSubViews [super layoutSubviews]; // 将imageView的宽高设置为60 [self.imageView setFrame:CGRectMake(10, 10, 60, 60)]; [self.textLabel setFrame:CGRectMake(80, 10, 220, 30)]; [self.textLabel setTextColor:[UIColor redColor]]; [self.detailTextLabel setFrame:CGRectMake(80, 50, 150, 20)]; [self.detailTextLabel setTextColor:[UIColor darkGrayColor]]; } // 实例化带自定义样式的单元格 - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { // 先调用父类的init方法 self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier]; if (self) { // 取消选中cell时,显示的默认的蓝色 [self setSelectionStyle:UITableViewCellSelectionStyleNone]; // 时长标签 UILabel *lenLable = [[UILabel alloc]initWithFrame: CGRectMake(240, 50, 60, 20)]; [self.contentView addSubview:lenLable]; [lenLable setTextColor:[UIColor darkGrayColor]]; // 清除时长标签lable的背景颜色 [lenLable setBackgroundColor:[UIColor clearColor]]; // 成员变量记住时长标签 self.lengthLabel = lenLable; // 设置cell的最右边 > [self setAccessoryType:UITableViewCellAccessoryDisclosureIndicator]; } return self; } // 单元格的方法 选中或者撤销选中 的颜色 - (void)setSelected:(BOOL)selected animated:(BOOL)animated { // 先调用父类的方法 [super setSelected:selected animated:animated]; // 选中表格行,yellow if (selected) { [self setBackgroundColor:[UIColor yellowColor]]; } else { // 撤销选中表格行,white [self setBackgroundColor:[UIColor whiteColor]]; } } @end
H:/1010/00_图片内存缓存.m
/* 异步加载网络图像的内存缓存解决方法 1. 在对象中定义一个成员,类型是UIImage,名字叫cacheImage 2. 在控制器中,填充表格内容时,判断UIImage是否存在内容 1> 如果cacheImage不存在,显示占位图像,同时开启异步网络连接加载网络图像 网络图像加载完成后,设置对象的cacheImage 设置完成后,刷新表格对应的行 2> 如果cacheImage存在,直接显示cacheImage */ // UITableView数据源方法 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // 模板代码 static NSString *ID = @"MyCell"; VideoCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; if (cell == nil) { cell = [[VideoCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID]; } // 设置单元格独一无二的内容 Video *v = self.dataList[indexPath.row]; cell.textLabel.text = v.name; cell.detailTextLabel.text = v.teacher; cell.lengthLabel.text = v.lengthStr; // 2) 异步加载网络图片 // 网络连接本身就有异步命令 sendAsync // 如果缓存图像不存在,使用默认图片占位,然后开启异步线程加载网络图片 if (v.cacheImage == nil) { // 使用默认图像占位,既能够保证有图像,又能够保证有地方! UIImage *image = [UIImage imageNamed:@"default.png"]; [cell.imageView setImage:image]; // 自定义方法,开启异步加载图像,加载完图片,然后才刷新对应的表格行 [self loadImageAsyncWithIndexPath:indexPath]; } else { // 如果video的成员cacheImage有缓存图片,就从缓存中取出图片 [cell.imageView setImage:v.cacheImage]; } return cell; } #pragma mark 自定义方法,异步加载网络图片 // 由于UITableViewCell是可重用的,为了避免用户频繁快速刷新表格,造成数据冲突 // 不能直接将UIImageView传入异步方法 // 正确做法:将表格行的indexPath传入异步方法, // 加载完成图像后,直接刷新指定的行 - (void)loadImageAsyncWithIndexPath:(NSIndexPath *)indexPath { // 取出数据中对应的model Video *v = self.dataList[indexPath.row]; // 1. 生成网络图片的url地址 NSString *imagePath = [NSString stringWithFormat:@"%@%@", kBaseURL, v.imageURL]; NSURL *imageUrl = [NSURL URLWithString:imagePath]; // 2. 根据url创建request请求 NSURLRequest *request = [NSURLRequest requestWithURL:imageUrl]; // 3. connection类方法, 发送异步请求,完成后的代码块中 data [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { // 将网络数据保存至video的成员cacheImage属性.即缓存图像 v.cacheImage = [UIImage imageWithData:data]; // 更改模型之后,才能刷新表格 [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft]; }]; }
H:/1010/00_注册可重用单元格.m
注册可重用单元格之前: - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *ID = @"MyCell"; VideoCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; if (cell == nil) { cell = [[VideoCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID]; } // 这时才可以,设置单元格独一无二的内容 Video *v = self.dataList[indexPath.row]; } //------------------------------------------------------------------ 注册可重用单元格之后: 1,注册可重用单元格,静态ID static NSString *ID = @"MyCell"; 2,在viewDidLoad方法里面,注册可重用单元格 [self.tableView registerClass:[VideoCell class] forCellReuseIdentifier:ID]; 3,UITableView数据源方法 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { VideoCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; // 直接可以设置单元格独一无二的内容 Video *v = self.dataList[indexPath.row]; }H:/1010/01_JSON_XML块代码解析_MainViewController.m
// MainViewController.m // JSON & XML // Created by apple on 13-10-10. // Copyright (c) 2013年 itcast. All rights reserved. #import "MainViewController.h" #import "Video.h" #import "VideoCell.h" #import "MyXMLParser.h" static NSString *ID = @"MyCell"; #define kBaseURL @"http://192.168.3.252/~apple" @interface MainViewController () <uitableviewdatasource uitableviewdelegate=""> @property (weak, nonatomic) UITableView *tableView; // 全局的数据数组 @property (strong, nonatomic) NSMutableArray *dataList; // 2) 全局的video对象,记录当前正在解析的元素 @property (strong, nonatomic) Video *currentVideo; @end @implementation MainViewController /* 在开发网络应用中 1. 文本是同步加载的,可以保证用户有的看 2. 图像、音频、视频是异步加载的,保证在不阻塞主线程使用的前提下, 用户能够渐渐地看到多媒体信息 零. 关于图像内存缓存的异步加载 1. 在对象中定义一个UIImage 2. 在控制器中,填充表格内容时,判断UIImage是否存在内容 1> 如果cacheImage不存在,显示占位图像,同时开启异步网络连接加载网络图像 网络图像加载完成后,设置对象的cacheImage 设置完成后,刷新表格对应的行 2> 如果cacheImage存在,直接显示cacheImage 一. JSON解析 [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil]; 提示:反序列化JSON数据后,可以将数据保存至plist文件,便于开发调试! 二. XML文件解析步骤 使用前,需要做的步骤 1) 实例化解析器 2) 设置代理 3) 解析器解析 解析步骤 1) 解析文档 在整个XML文件解析完成之前,2、3、4方法会不断被循环调用 2) 开始解析一个元素 3 接收元素的数据(因为元素内容过大,此方法可能会被重复调用,需要拼接数据) 4)结束解析一个元素 5) 解析文档结束 6) 解析出错 三. XML解析的思路 目前的资源:dataList记录表格中显示的数组,保存video对象。 0. 数据初始化的工作,实例化dataList和第3步需要使用的全局字符串 1. 如果在第2个方法中,elementName == video,会在attributeDict中包含videoId 2. 如果在第2个方法中,elementName == video,需要实例化一个全局的video属性, 记录2、3、4步骤中解析的当前视频信息对象 3. 其他得属性会依次执行2、3、4方法,同时第3个方法有可能会被多次调用 4. 在第3个方法中,需要拼接字符串——需要定义一个全局的属性记录中间的过程 5. 在第4个方法中,可以通过第3个方法拼接的字符串获得elementName对应的内容 可以设置全局video对象的elementName对应的数值 6. 在第4个方法中,如果elementName == video,则将该对象插入self.dataList 需要的准备工作 1) 全局的字符串,记录每一个元素的完整内容 2) 全局的video对象,记录当前正在解析的元素 四. 要使用块代码的方式对XML解析进行包装, 实际上是将所有的解析工作包装到另外一个类中, 而在实际开发中,简化XML解析的工作。 开发思路 1) 对六个解析方法依次分析,判断哪些方法需要和外部对象交互,以及交互的参数 2) 根据分析,定义块代码类型 3) 定义解析方法,接收所有块代码以及解析数据 4)调整代码,将数据与处理分离 提示:真正的数据处理,实际上还是在ViewController中完成的, 只是通过块代码的方式 将原有离散的处理方法,统一到了一个方法中。 */ #pragma mark 实例化视图 - (void)loadView { self.view = [[UIView alloc]initWithFrame: [UIScreen mainScreen].applicationFrame]; // 1. tableView CGRect frame = self.view.bounds; UITableView *tableView = [[UITableView alloc]initWithFrame: CGRectMake(0, 0, frame.size.width, frame.size.height - 44) style:UITableViewStylePlain]; // 1) 数据源 [tableView setDataSource:self]; // 2) 代理 [tableView setDelegate:self]; // 3) 设置表格高度 [tableView setRowHeight:80]; // 4) 设置分隔线 [tableView setSeparatorStyle:UITableViewCellSeparatorStyleSingleLine]; [self.view addSubview:tableView]; self.tableView = tableView; // 2. toolBar UIToolbar *toolBar = [[UIToolbar alloc]initWithFrame: CGRectMake(0, tableView.bounds.size.height, 320, 44)]; [self.view addSubview:toolBar]; // 添加toolBar按钮 UIBarButtonItem *item1 = [[UIBarButtonItem alloc]initWithTitle: @"load json" style:UIBarButtonItemStyleDone target:self action:@selector(loadJson)]; UIBarButtonItem *item2 = [[UIBarButtonItem alloc]initWithTitle: @"load xml" style:UIBarButtonItemStyleDone target:self action:@selector(loadXML)]; UIBarButtonItem *item3 = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; [toolBar setItems:@[item3, item1, item3, item2, item3]]; } - (void)viewDidLoad { [super viewDidLoad]; // 注册可重用单元格 [self.tableView registerClass:[VideoCell class] forCellReuseIdentifier:ID]; } #pragma mark - UITableView数据源方法 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.dataList.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // 1. 使用可重用标示符查询可重用单元格 VideoCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; // 注册可重用单元格后,不需要使用以下实例化方法 // if (cell == nil) { // cell = [[VideoCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID]; // } // 设置单元格内容 Video *v = self.dataList[indexPath.row]; cell.textLabel.text = v.name; cell.detailTextLabel.text = v.teacher; cell.lengthLabel.text = v.lengthStr; // 加载图片 // 1) 同步加载网络图片 // 注意:在开发网络应用时,不要使用同步方法加载图片,否则会严重影响用户体验 // 同步方法,意味着,这一指令执行完成之前,后续的指令都无法执行 // NSString *imagePath = [NSString stringWithFormat:@"%@%@", kBaseURL, v.imageURL]; // NSURL *imageUrl = [NSURL URLWithString:imagePath]; // NSData *imageData = [NSData dataWithContentsOfURL:imageUrl]; // UIImage *image = [UIImage imageWithData:imageData]; // 2) 异步加载网络图片 // gcd、nsoperation、nsthread // 网络连接本身就有异步命令 sendAsync // 如果缓存图像不存在 if (v.cacheImage == nil) { // 使用默认图像占位,既能够保证有图像,又能够保证有地方! UIImage *image = [UIImage imageNamed:@"user_default.png"]; [cell.imageView setImage:image]; // 开启异步连接,加载图像,因为加载完成之后,需要刷新对应的表格行 [self loadImageAsyncWithIndexPath:indexPath]; } else { [cell.imageView setImage:v.cacheImage]; } return cell; } #pragma mark 异步加载网络图片 // 由于UITableViewCell是可重用的,为了避免用户频繁快速刷新表格,造成数据冲突, // 不能直接将UIImageView传入异步方法 // 正确地解决方法是:将表格行的indexPath传入异步方法,加载完成图像后,直接刷新指定的行 - (void)loadImageAsyncWithIndexPath:(NSIndexPath *)indexPath { NSLog(@"=====indexpath %d", indexPath.row); Video *v = self.dataList[indexPath.row]; // 1. url NSString *imagePath = [NSString stringWithFormat:@"%@%@", kBaseURL, v.imageURL]; NSURL *imageUrl = [NSURL URLWithString:imagePath]; // 2. request NSURLRequest *request = [NSURLRequest requestWithURL:imageUrl]; // 3. connection sendasync [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { // 将网络数据保存至Video的缓存图像 v.cacheImage = [UIImage imageWithData:data]; // 刷新表格 [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationLeft]; }]; } #pragma mark - ACTIONs #pragma mark 处理JSON数据 - (void)handlerJSONData:(NSData *)data { // JSON文件中的[]表示是一个数组 // 反序列化JSON数据 /* 序列化: 将NSObject转换成序列数据,以便可以通过互联网进行传输 反序列化: 将网络上获取的数据,反向生成我们需要的对象 */ NSArray *array = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil]; // 提示:如果开发网络应用,可以将反序列化出来的对象,保存至沙箱,以便后续开发使用 NSArray *docs = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *path = [docs[0]stringByAppendingPathComponent:@"json.plist"]; [array writeToFile:path atomically:YES]; // 给数据列表赋值 NSMutableArray *arrayM = [NSMutableArray array]; for (NSDictionary *dict in array) { Video *video = [[Video alloc]init]; // 给video赋值 [video setValuesForKeysWithDictionary:dict]; [arrayM addObject:video]; } self.dataList = arrayM; // 刷新表格 [self.tableView reloadData]; NSLog(@"%@", arrayM); } #pragma mark - 加载JSON - (void)loadJson { NSLog(@"load json"); // 从web服务器直接加载数据 NSString *str = @"http://192.168.3.252/~apple/itcast/videos.php?format=json"; // 提示:NSData本身具有同步方法,但是在实际开发中,不要使用此方法 // 在使用NSData的同步方法时,无法指定超时时间,如果服务器连接不正常,会影响用户体验 // NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:str]]; // 1. 建立NSURL NSURL *url = [NSURL URLWithString:str]; // 2. 建立NSURLRequest NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:2.0f]; // 3. 利用NSURLConnection的同步方法加载数据 NSURLResponse *response = nil; NSError *error = nil; NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; // 不要忘记错误处理 if (data != nil) { // 仅用于跟踪调试使用 NSString *result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; // 做JSON数据的处理 // 提示:在处理网络数据时,不需要将NSData转换成NSString [self handlerJSONData:data]; } else if (data == nil && error == nil) { NSLog(@"空数据"); } else { NSLog(@"%@", error.localizedDescription); } } #pragma mark - 加载XML,使用代码块解析 // 先得到服务器返回的data,再调用MyXMLParser进行解析,并传入根节点名称 - (void)loadXML { // 从web服务器直接加载数据 NSString *str = @"http://192.168.3.252/~apple/itcast/videos.php?format=xml"; // 1) 建立NSURL NSURL *url = [NSURL URLWithString:str]; // 2) 建立NSURLRequest NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:2.0f]; // 3) 利用NSURLConnection的同步方法加载数据 NSURLResponse *response = nil; NSError *error = nil; NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; // 实例化 MyXMLParser对象 MyXMLParser *myParser = [[MyXMLParser alloc]init]; // 懒加载实例化数据 if (self.dataList == nil) { self.dataList = [NSMutableArray array]; } else { [self.dataList removeAllObjects]; } // 解析数据 [myParser xmlParserWithData:data startName:@"video" startElement:^(NSDictionary *dict) { // 1. 实例化currentVideo self.currentVideo = [[Video alloc]init]; // 2. 设置videoId self.currentVideo.videoId = [dict[@"videoId"]integerValue]; } endElement:^(NSString *elementName, NSString *result) { // 根据块的参数:元素名 拼接好的文本节点,为对象成员赋值 if ([elementName isEqualToString:@"name"]) { self.currentVideo.name = result; } else if ([elementName isEqualToString:@"length"]) { self.currentVideo.length = [result integerValue]; } else if ([elementName isEqualToString:@"videoURL"]) { self.currentVideo.videoURL = result; } else if ([elementName isEqualToString:@"imageURL"]) { self.currentVideo.imageURL = result; } else if ([elementName isEqualToString:@"desc"]) { self.currentVideo.desc = result; } else if ([elementName isEqualToString:@"teacher"]) { self.currentVideo.teacher = result; } else if ([elementName isEqualToString:@"video"]) { [self.dataList addObject:self.currentVideo]; } } finishedParser:^{ self.currentVideo = nil; // 完毕后,刷新表格视图 [self.tableView reloadData]; } errorParser:^{ NSLog(@"解析出现错误!"); // 清空临时数据 self.currentVideo = nil; // 清空数组 [self.dataList removeAllObjects]; }]; } @end </uitableviewdatasource>
H:/1010/01_MyXMLParser.h
// MyXMLParser.h // JSON & XML // Created by apple on 13-10-10. #import <Foundation/Foundation.h> // 2. 交互的元素:elementName attributeDict // 4. 交互的元素:elementName 中转的字符串 // 5. 完成仅通知即可 // 6. 出错仅通知即可 // 定义块代码 typedef void(^startElementBlock)(NSDictionary *dict); typedef void(^endElementBlock)(NSString *elementName, NSString *result); typedef void(^xmlParserNotificationBlock)(); @interface MyXMLParser : NSObject // 定义解析方法 /* 参数: data XML数据 rootElementName 开始的节点名称 startElementBlock 开始节点方法 endElementBlock 结束节点方法 finishedBlock 文档解析结束 errorBlock 文档解析出错 */ - (void)xmlParserWithData:(NSData *)data rootElementName:(NSString *)rootElementName startElementBlock:(startElementBlock)startElementBlock endElementBlock:(endElementBlock)endElementBlock finishedBlock:(xmlParserNotificationBlock)finishedBlock errorBlock:(xmlParserNotificationBlock)errorBlock; @end
H:/1010/01_MyXMLParser.m
// MyXMLParser.m // JSON & XML // Created by apple on 13-10-10. #import "MyXMLParser.h" @interface MyXMLParser() <NSXMLParserDelegate> { // 记录块代码的成员变量 startElementBlock _startElementBlock; endElementBlock _endElementBlock; xmlParserNotificationBlock _finishedBlock; xmlParserNotificationBlock _errorBlock; } // 开始节点名称,例如:video,如果检测到此名称,需要实例化对象 @property (strong, nonatomic) NSString *rootElementName; // 临时字符串 @property (strong, nonatomic) NSMutableString *tempStr; @end @implementation MyXMLParser // 参数1:服务器返回的data // 参数2:根元素结点名称,如video就对应一个实例对象 // 参数3:开始元素节点时 回调的代码块,回调传参 节点名和属性字典 // 参数4:结束一个元素节点时 回调的代码块,回调传参 节点名和拼接好的文本串 - (void)xmlParserWithData:(NSData *)data rootElementName:(NSString *)rootElementName startElementBlock:(startElementBlock)startElementBlock endElementBlock:(endElementBlock)endElementBlock finishedBlock:(xmlParserNotificationBlock)finishedBlock errorBlock:(xmlParserNotificationBlock)errorBlock { // 先全部用成员变量记住,然后设置代理后,代理方法中要用到 self.rootElementName = rootElementName; // 记录块代码 _startElementBlock = startElementBlock; _endElementBlock = endElementBlock; _finishedBlock = finishedBlock; _errorBlock = errorBlock; // 定义解析器,设置代理,并开始解析 NSXMLParser *parser = [[NSXMLParser alloc]initWithData:data]; // 设置代理 [parser setDelegate:self]; // 解析器开始解析 [parser parse]; } #pragma mark - XML解析器代理方法 // 所谓需要与外界交互,表示需要与调用方打交道,通知调用方执行某些操作 // 1. 开始解析文档,初始化数据,也不需要与外部交互 - (void)parserDidStartDocument:(NSXMLParser *)parser { // 实例化临时字符串 if (self.tempStr == nil) { self.tempStr = [NSMutableString string]; } } // 2. 开始解析元素(元素的头部video,需要实例化对象,attributeDict需要设置属性) // 需要与外部交互 - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { // 如果是根元素结点 如 if ([elementName isEqualToString:self.rootElementName]) { // 使用代码块,回调,并传入元素节点的属性字典 _startElementBlock(attributeDict); } // 开始循环执行第3个方法前,清空临时字符串 [self.tempStr setString:@""]; } // 3. 发现元素字符串(拼接字符串,不需要跟外部交互) - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { [self.tempStr appendString:string]; } // 4. 结束元素解析,根据elementName和第3步的拼接内容,确定对象属性,需要与外部交互 - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { NSString *result = [NSString stringWithString:self.tempStr]; // 使用代码块,回调,并传入结束元素节点的名称,和拼接好的文本结点 _endElementBlock(elementName, result); } // 5. 解析文档结束,通常需要调用方刷新数据(需要与外界交互) - (void)parserDidEndDocument:(NSXMLParser *)parser { [self.tempStr setString:@""]; // 使用代码块,回调 _finishedBlock(); } // 6. 解析出错,通知调用方解析出错(需要与外界交互) - (void)parser:(NSXMLParser *)parser parseErrorOccurred: (NSError *)parseError { NSLog(@"解析出错"); [self.tempStr setString:@""]; // 带一个NSError回去会更好! // 使用代码块,回调 _errorBlock(); } @end
H:/1010/01_NSArray+Log.m
// NSArray+Log.m /* // NSArray+Log.h // JSON & XML // Created by apple on 13-10-10. #import <foundation foundation="" h=""> @interface NSArray (Log) @end 自定义数组的打印输出方式 */ #import "NSArray+Log.h" @implementation NSArray (Log) - (NSString *)descriptionWithLocale:(id)locale { NSMutableString *str = [NSMutableString string]; [str appendFormat:@"%d (", self.count]; // 遍历数组中的对象 in self for (NSObject *obj in self) { [str appendFormat:@"\t%@\n,", obj]; } [str appendString:@")"]; return str; } @end </foundation>H:/1010/01_Video.m
// Video.h // JSON & XML // Created by apple on 13-10-10. /* 异步加载网络图像的内存缓存解决方法 1. 在对象中定义一个UIImage 2. 在控制器中,填充表格内容时,判断UIImage是否存在内容 1> 如果cacheImage不存在,显示占位图像,同时开启异步网络连接加载网络图像 网络图像加载完成后,设置对象的cacheImage 设置完成后,刷新表格对应的行 2> 如果cacheImage存在,直接显示cacheImage */ #import <Foundation/Foundation.h> @interface Video : NSObject @property (assign, nonatomic) NSInteger videoId; @property (strong, nonatomic) NSString *name; @property (assign, nonatomic) NSInteger length; @property (strong, nonatomic) NSString *videoURL; @property (strong, nonatomic) NSString *imageURL; @property (strong, nonatomic) NSString *desc; @property (strong, nonatomic) NSString *teacher; // 成员 缓存的图片 @property (strong, nonatomic) UIImage *cacheImage; // 视频时长的字符串 @property (strong, nonatomic, readonly) NSString *lengthStr; @end //==================================================================== //==================================================================== //==================================================================== // Video.m // JSON & XML // Created by apple on 13-10-10. #import "Video.h" @implementation Video // 格式化了一下,视频时长 - (NSString *)lengthStr { return [NSString stringWithFormat:@"%02d:%02d", self.length / 60, self.length % 60]; } // 重写了,toString方法 - (NSString *)description { return [NSString stringWithFormat:@"<Video: %p, video id: %d, name: %@" "length: %d videoURL: %@ imageURL: %@ desc: %@" "teacher: %@ >", self, self.videoId, self.name, self.length, self.videoURL, self.imageURL, self.desc, self.teacher]; } @end
H:/1010/01_VideoCell.m
// VideoCell.h // JSON & XML // Created by apple on 13-10-10. #import <UIKit/UIKit.h> // 自定义cell 多了一个时长的标签 @interface VideoCell : UITableViewCell // 时长标签 @property (weak, nonatomic) UILabel *lengthLabel; @end //==================================================================== //==================================================================== //==================================================================== // VideoCell.m // JSON & XML // Created by apple on 13-10-10. #import "VideoCell.h" @implementation VideoCell /* 如果想在自定义单元格中,修改默认子对象的位置 必须重写layoutSubviews方法,手动对cell中的所有子控件的位置进行调整 */ #pragma mark - 重新调整UITalbleViewCell中的控件布局 - (void)layoutSubviews { // 千万不要忘记super layoutSubViews [super layoutSubviews]; // 将imageView的宽高设置为60 [self.imageView setFrame:CGRectMake(10, 10, 60, 60)]; // 标题 [self.textLabel setFrame:CGRectMake(80, 10, 220, 30)]; [self.textLabel setTextColor:[UIColor redColor]]; // 子标题 [self.detailTextLabel setFrame:CGRectMake(80, 50, 150, 20)]; [self.detailTextLabel setTextColor:[UIColor darkGrayColor]]; } // 重写,init方法,生成指定的cell - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier: (NSString *)reuseIdentifier { // 千万要先调用 父类的init方法 self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier]; if (self) { // 取消显示选中时默认显示的蓝颜色 [self setSelectionStyle:UITableViewCellSelectionStyleNone]; // 创建时长的标签 UILabel *label3 = [[UILabel alloc]initWithFrame: CGRectMake(240, 50, 60, 20)]; // 设置时长标签的文字颜色 [label3 setTextColor:[UIColor darkGrayColor]]; // 清除时长标签的背景颜色 [label3 setBackgroundColor:[UIColor clearColor]]; // 必须添加到cell的contentView里面 [self.contentView addSubview:label3]; // 用成员变量,记住创建的时长标签 self.lengthLabel = label3; // 设置cell的最右边的附件样式 [self setAccessoryType:UITableViewCellAccessoryDisclosureIndicator]; } return self; } // 选中或者撤销选中单元格的方法 - (void)setSelected:(BOOL)selected animated:(BOOL)animated { // 千万要先调用 父类方法 [super setSelected:selected animated:animated]; // 选中表格行时,显示黄色 if (selected) { [self setBackgroundColor:[UIColor yellowColor]]; } else { // 撤销选中表格行时,显示白色 [self setBackgroundColor:[UIColor whiteColor]]; } } @end
H:/1010/01_XML解析器Block回调.m
// MyXMLParser.h // Created by apple on 13-10-10. #import <Foundation/Foundation.h> // 定义块代码 // 开始元素节点,参数是属性字典 typedef void(^startElementBlock)(NSDictionary *dict); // 结束元素节点,参数是元素节点名和拼接好的文本节点内容 typedef void(^endElementBlock)(NSString *elementName, NSString *tempStr); // finish和error回调 typedef void(^xmlParserNotificationBlock)(); @interface MyXMLParser : NSObject // 定义解析方法 /* 参数: data 要解析的XML数据 rootElementName 开始的根节点名称 startElementBlock 开始元素节点时的回调方法 endElementBlock 结束元素节点时的回调方法 finishedBlock 文档解析完毕时回调的方法 errorBlock 文档解析出错时回调的方法 */ - (void)xmlParserWithData:(NSData *)data rootElementName:(NSString *)rootElementName startElementBlock:(startElementBlock)startElementBlock endElementBlock:(endElementBlock)endElementBlock finishedBlock:(xmlParserNotificationBlock)finishedBlock errorBlock:(xmlParserNotificationBlock)errorBlock; @end //------------------------------------------------------------------------ // MyXMLParser.m // Created by apple on 13-10-10. #import "MyXMLParser.h" @interface MyXMLParser() <NSXMLParserDelegate> { // 成员变量 记住块代码 startElementBlock _startElementBlock; endElementBlock _endElementBlock; xmlParserNotificationBlock _finishedBlock; xmlParserNotificationBlock _errorBlock; } // 先全部用成员变量记住,然后设置代理后,代理方法中要用到 // 开始根节点名称,例如:video,如果检测到此名称,需要实例化一个对象 @property (strong, nonatomic) NSString *rootElementName; // 临时字符串,用于拼接文本节点用的 @property (strong, nonatomic) NSMutableString *tempStr; @end @implementation MyXMLParser // 方法的实现 - (void)xmlParserWithData:(NSData *)data rootElementName:(NSString *)rootElementName startElementBlock:(startElementBlock)startElementBlock endElementBlock:(endElementBlock)endElementBlock finishedBlock:(xmlParserNotificationBlock)finishedBlock errorBlock:(xmlParserNotificationBlock)errorBlock { // 先全部用成员变量记住,然后设置代理后,代理方法中要用到 self.rootElementName = rootElementName; // 记录块代码 _startElementBlock = startElementBlock; _endElementBlock = endElementBlock; _finishedBlock = finishedBlock; _errorBlock = errorBlock; // 定义解析器,设置代理,开始解析 NSXMLParser *parser = [[NSXMLParser alloc]initWithData:data]; // 设置代理 [parser setDelegate:self]; // 解析器开始解析 [parser parse]; } #pragma mark - XML解析器代理方法 // 所谓需要与外界交互,表示需要与调用方打交道,通知调用方执行某些操作 // 1. 开始解析文档,初始化数据,也不需要与外部交互 - (void)parserDidStartDocument:(NSXMLParser *)parser { // 懒加载 实例化临时字符串 if (self.tempStr == nil) { self.tempStr = [NSMutableString string]; } } // 2. 开始解析元素(元素的根video 需要实例化对象 attributeDict需要设置属性) // 需要与外部交互 - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { // 如果元素名 等于 传入的根元素节点名称,回调 if ([elementName isEqualToString:self.rootElementName]) { // 使用代码块,回调,并传入元素节点的属性字典 _startElementBlock(attributeDict); } // 清空临时字符串,为拼接文本做准备 [self.tempStr setString:@""]; } // 3. 发现元素字符串(拼接字符串,不需要跟外部交互) - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { [self.tempStr appendString:string]; } // 4. 结束元素解析,根据elementName和第3步的拼接内容,确定对象属性,需要与外部交互 - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { NSString *result = [NSString stringWithString:self.tempStr]; // 使用代码块,回调,并传入结束元素节点的名称,和拼接好的文本结点 // 目的是 为对象的成员 elementName 赋值 _endElementBlock(elementName, result); } // 5. 解析文档结束,通常需要调用方刷新数据(需要与外界交互) - (void)parserDidEndDocument:(NSXMLParser *)parser { // 解析完毕,也要清空临时字符串 [self.tempStr setString:@""]; // 使用代码块,回调 (如刷新tableView等) _finishedBlock(); } // 6. 解析出错,通知调用方解析出错(需要与外界交互) - (void)parser:(NSXMLParser *)parser parseErrorOccurred: (NSError *)parseError { NSLog(@"解析出错"); [self.tempStr setString:@""]; // 带一个NSError回去会更好! // 使用代码块,回调 _errorBlock(); } @end //------------------------------------------------------------------------ // 调用方,先得到服务器返回的data,再调用MyXMLParser进行解析,并传入根节点名称 - (void)loadXML { // 从web服务器直接加载数据 NSString *str = @"http://192.168.3.252/~apple/itcast/videos.php?format=xml"; // 1) 建立NSURL NSURL *url = [NSURL URLWithString:str]; // 2) 建立NSURLRequest NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:2.0f]; // 3) 利用NSURLConnection的同步方法加载数据 NSURLResponse *response = nil; NSError *error = nil; NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; // 实例化 MyXMLParser对象 MyXMLParser *myParser = [[MyXMLParser alloc]init]; // 懒加载实例化数据 if (self.dataList == nil) { self.dataList = [NSMutableArray array]; } else { [self.dataList removeAllObjects]; } // 解析数据 [myParser xmlParserWithData:data startName:@"video" startElement:^(NSDictionary *dict) { // 1. 实例化currentVideo self.currentVideo = [[Video alloc]init]; // 2. 设置videoId self.currentVideo.videoId = [dict[@"videoId"]integerValue]; } endElement:^(NSString *elementName, NSString *result) { // 根据块的参数:元素名 拼接好的文本节点,为对象成员赋值 if ([elementName isEqualToString:@"name"]) { self.currentVideo.name = result; } else if ([elementName isEqualToString:@"length"]) { self.currentVideo.length = [result integerValue]; } else if ([elementName isEqualToString:@"videoURL"]) { self.currentVideo.videoURL = result; } else if ([elementName isEqualToString:@"imageURL"]) { self.currentVideo.imageURL = result; } else if ([elementName isEqualToString:@"desc"]) { self.currentVideo.desc = result; } else if ([elementName isEqualToString:@"teacher"]) { self.currentVideo.teacher = result; } else if ([elementName isEqualToString:@"video"]) { [self.dataList addObject:self.currentVideo]; } } finishedParser:^{ // 解析完毕,刷新tableView self.currentVideo = nil; [self.tableView reloadData]; } errorParser:^{ // 解析出错,清空临时数据 self.currentVideo = nil; // 清空数组 [self.dataList removeAllObjects]; }]; }