H:/1010/00_JSON_XML_MainViewController.m
// 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];
}];
}