在网络开发中,从服务器获取的二进制数据包括:
> html
> 图片
> 视频
> 音频
> zip 等
除了以上文件格式之外,最常见的数据莫过于JSON,偶尔也会有XML。
无论 JSON还是 XML都是一种特殊格式的字符串,按照特定的规则描述数据结构。
一、准备工作--->安装Apache服务器
> 参考备课笔记/笔记/02-Apache服务器安装笔记.m
二、JSON
1、JSON格式介绍
# JSON本质上,就是一个“特殊格式”的字符串。
> JSON是网络上用来传输数据使用最广泛的数据格式,没有之一。
> JSON出生草根,是Javascript的子集,是Javascript的SON,专门用来描述数据结构。
> Javascrpit 是做网页开发使用的一种"脚本"语言
> Javascrpit & Java没有任何关系! 就好像 雷锋 和 雷峰塔 之间的关系。
#参考网站:http//www.w3cschool.cc。w3c 是国际互联网组织的缩写。
2、JSON的语法规则
> 数据以 key/value 键值对表示
> 数据由逗号分割
> 花括号保存对象,对应OC的NSDictionary。
> 方括号保存数组,对应OC的NSArray。
3> JSON值
* 数字(整数或浮点数)
* 字符串(在双引号中)
* 逻辑值(true 或 false)
* 数组(在方括号中)
* null
4、JSON解析--->解析天气预报
> 序列化:在向服务器发送数据之前,将 NSArray/NSDcitionary 转成二进制的过程。
> 反序列化:在从服务器接收到数据之后,将二进制数据转换成NSArray/NSDcitionary的过程。
/**
* 解析天气预报
*/
- (void)loadData{
// 创建 url
NSURL *url = [NSURL URLWithString:@"http://www.weather.com.cn/adat/sk/101010100.html"];
// 创建请求对象
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:10.0];
// 发送异步请求
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSLog(@"data = %@",data);
/*
> 序列化:在向服务器发送数据之前,将 NSArray/NSDcitionary 转成二进制的过程。
> 反序列化:在从服务器接收到数据之后,将二进制数据转换成NSArray/NSDcitionary的过程。
*/
NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
/*
NSJSONReadingMutableContainers = (1UL << 0), 容器可变
NSJSONReadingMutableLeaves = (1UL << 1), 叶子可变
NSJSONReadingAllowFragments(碎片) = (1UL << 2) 顶级节点可以既不是数组也不是字典
*/
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSLog(@"%@市 温度 %@ 风向 %@ 风力 %@ ",dict[@"weatherinfo"][@"city"],dict[@"weatherinfo"][@"temp"],dict[@"weatherinfo"][@"WD"],dict[@"weatherinfo"][@"WS"]);
}];
}
5、JSON解析第三方框架
> 常见的JSON解析第三方框架
* JSONKit(最快)
* SBJson
* TouchJSON
# 以上三个框架的性能依次降低。
> 介绍JSONKit第三方框架的目的
* JSON的解析并不是表面上这么简单
* 官方说JSONKit 比苹果原生的 JSON 解析速度要快。
* JSONKit在很多老的项目中仍然在使用
#知道JSONKit 说明我们是资深的iOS程序员。
* JSONKit已经在2012年停止更新,适用于iOS5.0之前的版本开发使用。(苹果原生的NSJSONSerialization是iOS5.0之后出现)
* 了解 ARC & MRC 混编的方法
> JSONKit解析和性能测试
使用步骤:
* 下载框架:http://github.com/johnezang/JSONKit
* 导入框架文件:JSONKit.h & JSONKit.m
* 设置 MRC 标记
> 选择"项目"-"Build Phases"-"Compile Sources"
> 找到 JSONKit.m 并且在 Compiler Flags 中添加 -fno-objc-arc。
# -fno-objc-arc 告诉编译器,编译 JSONKit.m 时不使用ARC。
* 修改错误
> 利用自动修复功能,修改两处isa的错误。
* 反序列化
> id result = [[JSONDecoder decoder] objectWithData:data];
使用代码和测试如下:
int largeNumber = 100 * 1000;
- (void)loadData{
// 创建 url
NSURL *url = [NSURL URLWithString:@"http://localhost/demo.json"];
// 创建请求对象
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:10.0];
// 发送异步请求
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
/*
官方说JSONKit 比苹果原生的 JSON 解析速度要快。我们可以通过测试验证下
JSONKit:4.907761 秒
官方:0.299994 秒
通过测试得出:苹果原生的JSON解析速度远远比JSONKit快。
如果工作中遇到使用第三方框架解析 JSON 的旧项目,建议替换为苹果原生的。
替换的原则:看项目是否要支持 iOS5.0之前的版本。如果不需要果断替换。
替换步骤:
1> 备份(给自己一个后悔的机会)
2> 删除 JSONKit.h & .m 文件
3> 哪里出错改哪里
*/
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
for (int index = 0; index < largeNumber; index ++) {
// NSMutableDictionary *result = [[JSONDecoder decoder] objectWithData:data];
NSMutableDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
}
NSLog(@"-----endTime = %f",CFAbsoluteTimeGetCurrent() - start);
//JKDictionary 其实就是 NSMutableDictionary 的 子类
// NSLog(@"------%@,%@",result,result[@"message"]);
}];
}
6、PList解析
> PList 主要在苹果开发中常用,很多后台并不会返回 Plist 的格式数据。有关 PList的反序列化知道即可。
> 代码如下:
/**
* 解析plist 数据
*/
- (void)loadData{
// 创建 url
NSURL *url = [NSURL URLWithString:@"http://localhost/videos.plist"];
// 创建请求对象
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:10.0];
// 发送异步请求
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
/**
参数:
- data:要反序列化的二进制数据
- options:选项,位移枚举类型
NSPropertyListImmutable = 0 不可变,
NSPropertyListMutableContainers = 1 容器可变,
NSPropertyListMutableContainersAndLeaves = 2 容器和叶子可变
- format:如果不希望知道格式,传人 NULL 即可
- error:错误信息
*/
id result = [NSPropertyListSerialization propertyListWithData:data options:0 format:NULL error:nil];
NSLog(@"%@ %@",result,[result class]);
}];
}
三、XML解析
1、XML介绍
> XML的英文全称(eXtensible Markup Language),中文名称可扩展标记语言。
> XML的特点:语法规则很简单,且很有逻辑。
#出身名门,w3c制定,微软和 IBM 曾经共同大力推荐过的数据格式。
> 专门设计用来传输和存储数据
> 与HTML的区别:HTML的标记不是所有的都需要成对出现,
XML要求所有的标记必须成对出现。
HTML标记不区分大小写,它则大小敏感,即区分大小写。
2、XML解析的方式
> DOM解析
* 是在MAC使用的解析方式。
* 内存消耗极大,不适用于手机。因此苹果没有提供手机上DOM解析。
* iPhone无法直接使用DOM方式解析XML。
> SAX解析
* 是只读的方式,从上向下的方式解析
* 是苹果提供的解析方式
* 使用 NSXMLParser 通过 代理 实现解析。
3、SAX解析步骤
1> 开始文档--准备工作
2> 开始"节点"
3> 发现节点内部的内容,每一个节点,可能需要多次才能找完
4> 结束"节点"
5> 结束文档--解析结束
#以上步骤,2,3,4会不断循环,一直到所有解析完成。
4、代码演示XML的解析。
/**
* 加载 xml 数据
*/
- (void)loadData{
// 创建 url
NSURL *url = [NSURL URLWithString:@"http://localhost/videos.xml"];
// 创建请求对象
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:10.0];
// 发送异步请求(由于解析 xml 是一个耗时的过程,所以新开启队列)
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// 1.创建 xml 解析器
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
// 2.设置代理来解析
parser.delegate = self;
// 3.解析器开始解析 -- 一旦开始解析,后续的解析工作全部交给代理完成
[parser parse];
}];
}
> 通过NSLog打印,确认XML解析思路
#pragma mark - NSXMLParserDelegate 代理方法
/**
* 开始解析
*/
- (void)parserDidStartDocument:(NSXMLParser *)parser {
NSLog(@"1、开始解析");
}
/**
* 解析到一个开始节点调用
*/
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
NSLog(@"2、开始节点 %@ %@",elementName,attributeDict);
}
/**
* 解析到节点文字调用 一个节点的内容可以会调用多次该方法
*/
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
NSLog(@"3、======> %@",string);
}
/**
* 解析到结束节点调用 elementName 没有反斜线 /
*/
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
NSLog(@"4、结束节点------%@",elementName);
}
/**
* 解析结束调用
*/
- (void)parserDidEndDocument:(NSXMLParser *)parser{
NSLog(@"5、结束文档");
}
5、画XML解析思维导图
6、XML解析代码实现
#pragma mark - NSXMLParserDelegate 代理方法
/**
* 开始解析
*/
- (void)parserDidStartDocument:(NSXMLParser *)parser {
// 清空数组
[self.videos removeAllObjects];
}
/**
* 解析到一个开始节点调用
*/
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
if ([elementName isEqualToString:@"video"]) {
// 创建 video 模型
self.currentVideo = [[Video alloc] init];
// 设置 id
self.currentVideo.videoId = @([attributeDict[@"videoId"] integerValue]);
}
// 清空当前正在拼接的字符内容
[self.elementString setString:@""];
// 错误写法
//self.elementString = @"";
}
/**
* 解析到节点文字调用 一个节点的内容可以会调用多次该方法
*/
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
[self.elementString appendString:string];
}
/**
* 解析到结束节点调用 elementName 没有反斜线 /
*/
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
// 如果 elementName 是 video,则添加到数组中
/* 最笨方法
if ([elementName isEqualToString:@"video"]){
[self.videos addObject:self.currentVideo];
} else if ([elementName isEqualToString:@"name"]){
self.currentVideo.name = self.elementString;
} else if ([elementName isEqualToString:@"length"]){
self.currentVideo.length = @(self.elementString.integerValue);
} else if ([elementName isEqualToString:@"desc"]){
self.currentVideo.desc = self.elementString;
} else if ([elementName isEqualToString:@"imageURL"]) {
self.currentVideo.imageURL = self.elementString;
} else if ([elementName isEqualToString:@"videoURL"]){
self.currentVideo.videoURL = self.elementString;
} else if ([elementName isEqualToString:@"teacher"]){
self.currentVideo.teacher = self.elementString;
}
*/
// 如果是 name,length,desc....则设置属性
// 简便方法
if ([elementName isEqualToString:@"video"]){
[self.videos addObject:self.currentVideo];
} else if(![elementName isEqualToString:@"videos"]){
//使用 KVC 设置属性, KVC是一种以字符串形式间接设置数据的方式
[self.currentVideo setValue:self.elementString forKey:elementName];
}
}
/**
* 解析结束调用
*/
- (void)parserDidEndDocument:(NSXMLParser *)parser{
NSLog(@"%@",self.videos);
}
四、tableView展示数据
1、展示视频数据--Storyboard自定义cell
2、设置时间格式
> HMVideo模型中提供属性
//视频时长
@property (nonatomic, strong,readonly) NSString *time;
# 计算时间方式有两种
# 方式一:重写time属性getter方法
- (NSString *)time {
int len = self.length.intValue;
return [NSString stringWithFormat:@"%02d:%02d:%02d", len / 3600, (len % 3600) / 60, (len % 60)];
}
# 方式二:
- (void)setLength:(NSNumber *)length {
_length = length;
int len = self.length.intValue;
_time = [NSString stringWithFormat:@"%02d:%02d:%02d", len / 3600, (len % 3600) / 60, (len % 60)];
}
3、SDWebImage下载头像
UIImage *placeholder = [UIImage imageNamed:@"user_default"];
// SDWebImageLowPriority:会在表格滚动的时候,暂时图像下载,性能会好很多
// SDWebImageRetryFailed:图片下载失败后重试
[self.iconView sd_setImageWithURL:video.fullImageUrl placeholderImage:placeholder options:SDWebImageRetryFailed | SDWebImageLowPriority];
4、集成下列刷新控件-->通过代码和storyboard
/**
* 集成下拉刷新控件(通过代码)
*/
- (void)setRefreshControl{
// 集成下拉控件(不需要设置frame)
self.refreshControl = [[UIRefreshControl alloc] init];
// 监听下拉事件
[self.refreshControl addTarget:self action:@selector(loadData) forControlEvents:UIControlEventValueChanged];
// 设置提示文字,颜色
NSMutableAttributedString *attributedTitle = [[NSMutableAttributedString alloc] initWithString:@"努力加载中……" attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:15],NSForegroundColorAttributeName:[UIColor redColor]}];
self.refreshControl.attributedTitle = attributedTitle;
// 设置菊花颜色
self.refreshControl.tintColor = [UIColor grayColor];
}
# UIRefreshControl使用说明
1> 目前只对UITableviewController有用
2> 只能下拉刷新,不能上拉刷新
3> init或者viewdidload中创建UIRefreshControl,设置文字,颜色等信息
4> 系统自动管理UIRefreshControl,自动添加到tableview视图中;
5> 给UIRefreshControl添加方法,当值改变的时候调用,方法用于数据请求
6> 该方法中请求数据确认完成之后,调用endRefreshing方法,关闭刷新
五、XML解析重构
1> 新建一个模型类HMSAXVideo,专门做解析工作。
2> 提供类方法:+(void)saxParser:(NSData *)data finished:(void (^)(NSArray *))finished。
// 如果回调的 block 在当前方法不执行,需要用一个属性记录
+(void)saxParser:(NSData *)data finished:(void (^)(NSArray *))finished{
// 使用断言确保外界传人的回调不为空,如果为空,则崩溃提示
NSAssert(finished != nil, @"必须传人回调");
// 0.创建解析模型
HMSAXVideo *sax = [[HMSAXVideo alloc] init];
// 记录回调 block
sax.finishedBlock = finished;
// 1.创建 xml 解析器
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
// 2.设置代理来解析
parser.delegate = sax;
// 3.解析器开始解析
// parse:是同步方法,会等所用代理方法执行完毕后,才会执行后面的代码
[parser parse];
#warning 不能在parse方法调用后记录回调
// 记录回调 block
// sax.finishedBlock = finished;
}
六、Copy,NSNumber,id
1、为什么模型HMVideo中的属性都使用copy? 改为strong运行看看效果。
> strong:在设置数值的时候,仅仅是引用计算+1
> copy:在设置数值的时候,如果有方法是可变的,会copy一个不可变的副本。copy动作是在 setter方法中执行的。
> 画图解析使用strong导致的效果。
> 如果属性使用strong:
#下面的代码
[self.currentVideo setValue:self.elementString forKey:elementName];
#修改为
[self.currentVideo setValue:self.elementString.copy forKey:elementName];
> 重新了属性的setter方法,就是接管了原有系统提供的setter方法。
# 如果重新了copy属性的 setter 方法,一定要 copy,否则外面的copy属性就是strong属性。
比如重写了模型的name属性的 setter方法:
- (void)setName:(NSString *)name {
_name = [name copy];
// _name = name; 属性就相当于 strong 了
}
2、NSNumber,id属性
> 代码演示实际开发中不用NSNumber带来的问题。
> 代码演示服务器返回数据中有id键值对的问题。