iOS_JSON_XML_图片内存缓存_Block回调

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];
    }];
}

iOS_JSON_XML_图片内存缓存_Block回调,布布扣,bubuko.com

iOS_JSON_XML_图片内存缓存_Block回调

上一篇:Qt移动应用开发(三):使用精灵图片实现帧动画


下一篇:【APP UI 设计模式】(一)APP UI 设计原则和流程