零基础iOS开发学习日记-功能篇-访问网络

622 阅读14分钟

开头

访问网络

实际用处

  • 获取网络数据
  • 上传、下载数据

基础要点

  • 访问网络,可以理解为A(客户端)通过一种方式(协议)到B(服务端)送(上传)/拿(下载)/告诉(传输)/听到(取回)消息的过程
  • 而这个协议往往是http或者https,这个服务端则是一个网站,而后面的动作则是post/get/put等方式
  • 在实际访问过程中,需要进行的操作实际上只有,确定地址(访问网站)确定路径(方式)和钥匙(访问体)取回消息,而具体显示内容这一工作,一般由浏览器自己去解析,或者另外进行数据处理
  • 因为我对网络的理解没有特别深,所以这部分主要以,我在仿写项目中的案例,进行梳理,尽量能梳理清楚,覆盖能用到的功能
  • OC中目前访问网络主要用到NSURLSessionTask
  • 下面的案例,是建立在本地上的apache服务器,也可以用这网址测试httpbin

请求和响应

  • 请求request,包含请求行,请求头,请求体(POST方式有)。请求行包括请求方式,资源地址,协议版本;请求头包含申请格式、处理缓存的方式、显示的客户端型号等;请求体包含POST方式的参数
  • 响应response,包含响应行,响应头,响应体。响应行包含协议的版本,访问的状态结果;响应头包含格式键值对;响应体包含显示的正文

NSURLSessionTask

  • OC网络访问的主要类,主要有三个子类
  1. NSURLSessionDataTask get/post
  2. NSURLSessionUploadTask put
  3. NSURLSessionDownloadTask 下载

GET

实际用处

  • 获取网络资源、数据
  • 搜索
  • 效率高,但是能被浏览器缓存,不安全

基础用法

  • 普通访问界面
//确定网站
NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/demo.json"];
//生成请求
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//建立访问任务
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    //错误判断
    if (error) {
        NSLog(@"连接错误 %@", error);
        return;
    }
    //响应
    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
    //响应头状态结果的判断
    if (httpResponse.statusCode == 200 || httpResponse.statusCode == 304) {
        //解析数据,显示的正文
        NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@", str);
        NSLog(@"%@", data);
    }
    else{
        NSLog(@"服务器内部错误");
    }
}];
//开始访问
[task resume];
  • 带参数
NSString *name = @"zhang san";
NSString *pwd = @"123";
NSString *strUrl = [NSString stringWithFormat:@"http://127.0.0.1/php/login.php?username=%@&password=%@", name, pwd];
//当地址中出现空格或者汉子 url返回空。下面这个函数可以转义字符串中的空格
strUrl = [strUrl stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSURL *url = [NSURL URLWithString:strUrl];

小结

  • 这里也可以看出,GET访问的方式其实就是将需要的参数显行的放在网址中,具体结构为网址?参数1=值&参数2=值....
  • 也因为这种结构的原因的,GET不适用于传输大量参数,或者传输隐私需求较高的参数

POST

实际用处

  • 发送隐私数据,发送比较多的数据
  • 不会被浏览器缓存
  • 效率低,但是相对安全

基础用法

//确定网址
NSString *strUrl = @"http://127.0.0.1/php/login.php";
NSURL *url = [NSURL URLWithString:strUrl];
//因为要添加请求体,所以要用可变请求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//设置请求头 请求体
request.HTTPMethod = @"post";
//添加请求体
NSString *body = @"username=zhang san&password=123";
request.HTTPBody = [body dataUsingEncoding:NSUTF8StringEncoding];
//建立访问任务
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (error) {
        NSLog(@"连接错误 %@", error);
        return;
    }
    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
    if (httpResponse.statusCode == 200 || httpResponse.statusCode == 304) {
        //解析数据
        //解析出字符串
        NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@", str);
        //解析出键值对
        NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
        NSLog(@"%@", dic[@"userName"]);
    }
    else{
        NSLog(@"服务器内部错误");
    }
}];
[task resume];

自定义请求体上传文件

  • 主要是为了整理一下自定义请求体
  • 谷歌浏览器抓不到请求体,我用的safari
  • 请求体内容
 ------WebKitFormBoundaryOMAsfP2G4gU9nai3
 Content-Disposition: form-data; name="userfile"; filename="99.png"
 Content-Type: image/png

文件二进制数据
 ------WebKitFormBoundaryOMAsfP2G4gU9nai3--
  • 对应的请求头中的内容。Content-Type是表单类型,由html中设置,----WebKitFormBoundaryUmfoTOZhwXatboQB是浏览器自己生成的分割符
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryUmfoTOZhwXatboQB
  • 自定义请求体,kBOUNDARY自定义分割符
- (NSData *)makeBody:(NSString *)fieldName andfilePath:(NSString *)filePath{
    //可变二进制
    NSMutableData *mData = [NSMutableData data];
    NSMutableString *mString = [NSMutableString new];
    [mString appendFormat:@"--%@\r\n", kBOUNDARY];
    //name对应表单名 filename对应文件名
    [mString appendFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", fieldName, [filePath lastPathComponent]];
     //二进制数据格式
    [mString appendString:@"Content-Type: application/octet-stream\r\n"];
    [mString appendString:@"\r\n"];
    [mData appendData:[mString dataUsingEncoding:NSUTF8StringEncoding]];
    //上传文件内容的二进制
    NSData *data = [NSData dataWithContentsOfFile:filePath];
    [mData appendData:data];
    NSString *end = [NSString stringWithFormat:@"\r\n--%@--",kBOUNDARY];
    [mData appendData:[end dataUsingEncoding:NSUTF8StringEncoding]];
    return mData.copy;
    
}
  • 设置请求体
request.HTTPBody = [self makeBody:fieldName andfilePath:filePath];
  • 设置请求头中的Content-Type
[request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", kBOUNDARY] forHTTPHeaderField:@"Content-Type"];

小结

  • POST方式其实与GET相差无几,只不过是传参数的方式不同罢了
  • 在实际开发中,在面对隐私数据传输的时候,大部分解决方法是通过编码方式,进行数据保护
  • 在AFN中参数可以通过参数字典的方式进行传输,也比较方便
  • 自定义请求体本质上就是通过拼接字符串,来组成指定格式的数据,在实际开发中,根据前端需要的数据格式,进行对应的拼接和内容填充即可

PUT

基础用法

//获取文件
NSString *path = [[NSBundle mainBundle] pathForResource:@"06.jpg" ofType:nil];
NSURL *url = [NSURL URLWithString:@"https://httpbin.org/put"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//设置访问方式
request.HTTPMethod = @"put";
//获取数据
NSData *data = [NSData dataWithContentsOfFile:path];
NSLog(@"%@", data);
NSURLSessionTask *task = [[NSURLSession sharedSession] uploadTaskWithRequest:request fromData:data completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (error) {
        NSLog(@"连接错误 %@", error);
        return;
    }
    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
    if (httpResponse.statusCode == 200 || httpResponse.statusCode == 304) {
        //解析数据
        NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@", str);
        NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
        NSLog(@"%@", dic[@"headers"]);
    }
    else{
        NSLog(@"服务器内部错误");
    }
}];
[task resume];
  • 通过文件路径上传
NSURL *fileUrl = [[NSBundle mainBundle] URLForResource:@"06.jpg" withExtension:nil];

put权限

  • 在实际使用时,put需要设置请求头中的Authorization,但是现在mac好像配不了上传的webDav服务器,就只能用httpbin测试一下功能行不行,具体要用的时候,设置请求头就好
[request setValue:@"Basic YWRtaW46MTIzNDU2" forHTTPHeaderField:@"Authorization"];

下载

基础用法

NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/itcast/images/head1.png"];
NSURLSessionDownloadTask *downloadTask = [[NSURLSession sharedSession] downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    NSLog(@"%@", location);
}];
[downloadTask resume];
  • 但是,这有一个问题。由于NSURLSessionTask的特性,下载的数据会保存在tmp的目录下,下载完毕后就会被删除,所以需要另外保存
//获取路径,保存在doc目录下
NSString *saveLocation = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"head1.png"];
//复制到其他位置
[[NSFileManager defaultManager] copyItemAtPath:location.path toPath:saveLocation error:NULL];

断点续传

  • 断点续传的含义就是,当一个下载开始,在任何时段,都可以进行任意的暂停和继续,并且保存先前下载好的数据
  • 那么,就需要用到代理,和全局的任务对象,进行统一操作。另外,基本上所有的对象,都是全局对象
  • 控件是通过storyboard添加的
  • 对象设置,对象的设置结合下面可以更理解
@property (nonatomic, strong) NSURLSession *session; //会话
@property (nonatomic, strong) NSData *resumeData; //缓存数据
@property (nonatomic, strong) NSMutableDictionary *downloadCache; //下载缓存池,管理下载
@property (nonatomic, copy) NSString *urlStr; //下载目标网址
@property (nonatomic, copy) NSString *saveLaction; //保存路径
@property (nonatomic, copy) NSString *fileName; //文件名
@property (nonatomic, copy) NSString *tmpPath; //缓存路径
@property (nonatomic, strong) NSFileManager *fileManager; //文件管理对象,用于判断文件是否存在和文件增删改查
  • 懒加载,根据下载任务的不同,会话,下载缓存池,文件管理器实际上是不会变的,所以使用懒加载生成
- (NSURLSession *)session{
    if (!_session) {
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
        _session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    }
    return _session;
}
- (NSMutableDictionary *)downloadCache{
    if (!_downloadCache) {
        _downloadCache = [NSMutableDictionary dictionaryWithCapacity:10];
    }
    return _downloadCache;
}
- (NSFileManager *)fileManager{
    if (!_fileManager) {
        _fileManager = [NSFileManager defaultManager];
    }
    return _fileManager;
}
  • 设置接口内容,这里就以单个网站做事例,如果要在一个页面控制多个内容的下载,实质上就通过按钮的值来赋值即可,通过数组或者字典来保存
- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *urlStr = @"http://127.0.0.1/3.zip"; //目标网页
    self.fileName = [urlStr lastPathComponent]; //文件名,带扩展名
    self.urlStr = urlStr; //作为下载缓存池的标识
    NSString *tmpStr = [NSString stringWithFormat:@"%@.tmp", [self.fileName stringByDeletingPathExtension]]; //带tmp扩展名的文件名
    self.tmpPath = [NSTemporaryDirectory() stringByAppendingPathComponent:tmpStr]; //缓存路径的文件,用来保存断续下载的数据
    self.saveLaction = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:self.fileName]; //文件的保存路径
}
  • 代理方法,需要注意的是,使用代理,就不要使用回调函数,底层优先使用回调。
//下载完成
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location{
    NSLog(@"%@", location);
    //复制到其他位置
    [self.fileManager copyItemAtPath:location.path toPath:self.saveLaction error:NULL];
    NSLog(@"%@", self.saveLaction);
    //删除tmp文件
    [self.fileManager removeItemAtPath:self.tmpPath error:nil];
    //从下载缓存池中移除该下载
    [self.downloadCache removeObjectForKey:self.urlStr];
}
//获取进度
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
    float process = totalBytesWritten * 1.0 / totalBytesExpectedToWrite;
    self.processView.progress = process;
    NSLog(@"%f", process);
}
//续传,并没有用到,可以用来获取已下载的进度,就要结合app信息的保存里,后面整理了再改
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{
    float offset = fileOffset * 1.0 / expectedTotalBytes;
    NSLog(@"续传 %f", offset);
}
  • 准备了下载任务,抓到了下载过程的状态,就可以进行开始、暂停、下载的处理。那么,就要明确一下,什么情况下可以开始,什么情况下可以暂停,什么情况下可以继续
  • 开始,没有同名下载;保存目录下没有当前文件;缓存的目录下没有缓存数据
  • 暂停,有任务
  • 继续,已有保存好的数据;当前没有任务
- (void)download{
    NSURL *url = [NSURL URLWithString:self.urlStr];
    //建立该下载任务
    NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithURL:url];
    //添加到下载缓存池
    self.downloadCache[self.urlStr] = downloadTask;
    [downloadTask resume];
}
- (IBAction)startClick:(id)sender {
    //存在该下载任务
    if (self.downloadCache[self.urlStr]) {
        NSLog(@"已经在下载了");
        return;
    }
    //保存目录或者缓存目录存在该文件
    if ([self.fileManager fileExistsAtPath:self.saveLaction] || [self.fileManager fileExistsAtPath:self.tmpPath]) {
        NSLog(@"文件已经存在");
        return;
    }
    [self download];
}
- (IBAction)pauseClick:(id)sender {
    //没有这个下载任务
    if (!self.downloadCache[self.urlStr]) {
        NSLog(@"没有任务");
        return;
    }
    [self.downloadCache[self.urlStr] cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
        //保存续传数据
        self.resumeData = resumeData;
        //续传数据保存在沙盒
        [self.resumeData writeToFile:self.tmpPath atomically:YES];
        NSLog(@"已经保存数据");
        //给空对象发送消息不做任何处理
        //删除该下载任务
        self.downloadCache[self.urlStr] = nil;
        
    }];
}

- (IBAction)resumeClick:(id)sender {
    //已有下载任务
    if (self.downloadCache[self.urlStr]) {
        NSLog(@"已经有下载任务了");
        return;
    }
    //从沙盒中读取续传数据
    //缓存目录下是否有这个数据
    if ([self.fileManager fileExistsAtPath:self.tmpPath]) {
        self.resumeData = [NSData dataWithContentsOfFile:self.tmpPath];
        NSLog(@"本地有暂停的数据");
    }
    if (self.resumeData == nil) {
        NSLog(@"本地没有暂停的数据");
        return;
    }
    //重新给缓存池添加该下载任务
    self.downloadCache[self.urlStr] = [self.session downloadTaskWithResumeData:self.resumeData];
    [self.downloadCache[self.urlStr] resume];
    self.resumeData = nil;
}

断点续传要点总结

  • 对于字典来说,无论是remove还是直接设置为nil,还是在没有设置这个键的时候,都不会影响判断。一句话,只要这个键没有对应的值,那么就是nil,而字典不会管这个字典里有没有这个键
  • tmp文件,其实只是个plist,保存缓存文件的相关信息,并不直接保存缓存数据,系统是通过这个文件,寻找缓存数据在进行加载的,有机会了解底层的处理,这里挖个坑

网络数据解析

JSON

  • 具有自我描述性、更易理解
  • 本质上是一个字典,这个字典中可以放字符串,数据,字典
  • 字典JSON实例
{
	"word" : "今天天气真好", //字符串
	"name" : "Tommy",
	"array" : ["Time is short", "Always be humble", "how are u"], //数组
	"dic" : { //字典
		"age" : 18, //数字
		"address" : "China"
                "adult" : true //bool
	}
}
  • 解析输出JSON
NSString *path = [[NSBundle mainBundle] pathForResource:@"test.json" ofType:nil];
NSData *data = [NSData dataWithContentsOfFile:path];
id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
  • 这里会有一个问题,直接NSLog只会输出中文的编码,所以要冲重写description
  • 下面的例子写在NSDictionary+JSON.h的扩展中
- (NSString *)description
{
    NSMutableString *mStr = [NSMutableString string];
    [mStr appendFormat:@"(\r\n"];
    [self enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
            [mStr appendFormat:@"\t%@ : %@,\r\n", key, obj];
    }];
    [mStr appendString:@")"];
    return mStr.copy;
}
  • JSON转字典
  • 这里需要考虑到几个问题
  1. 并不是所有的值需要在App上显示
  2. 如果字典中仍包含字典,则需要用两层模型来解析
//转模型关键函数
- (instancetype)initWithDict:(NSDictionary *)dict
{
    if (self = [super init]) {
        [self setValuesForKeysWithDictionary:dict];
    }
    return self;
}
+(instancetype)testModelWithDict:(NSDictionary *)dict
{
    return  [[self alloc] initWithDict:dict];
}
//遇到字典特殊处理
- (void)setValue:(id)value forKey:(NSString *)key {
    if ([key isEqualToString:@"dic"]) {
        self.dic = [TestDicModel testDicModel:value];
        return;
    }
    [super setValue:value forKey:key];
}
//遇到不需要的键值对特殊处理
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    NSLog(@"%@ %@", key, value);
}
//重写测试函数
- (NSString *)description
{
    return [NSString stringWithFormat:@"%@\r\n{\r\n\tname:%@,\r\n \tarray:%@,\r\n\tdic:%@,\r\n\tword:%@\r\n}", [super description], self.name, self.array, self.dic, self.word];
}

XML

  • 可扩展标记语言
  • 类似HTML,目的是存储和传输数据
  • 解析步骤,寻找过程中在234步中循环
1. 加载xml数据
2. 找开始节点
3. 找节点之间的内容
4. 找结束节点
5. 直到最后一个节点,解析结束
  • XML实例,将下边的XML转换成videos模型数组
<?xml version="1.0" encoding="utf-8"?>
<videos>
    <video videoId="1">
        <name>01.C语言-语法预览</name>
        <length>320</length>
        <videoURL>/itcast/videos/01.C语言-语法预览.mp4</videoURL>
        <imageURL>/itcast/images/head1.png</imageURL>
        <desc>C语言-语法预览</desc>
        <teacher>李雷</teacher>
    </video>
    <video videoId="2">
        <name>02.C语言-第一个C程序</name>
        <length>2708</length>
        <videoURL>/itcast/videos/02.C语言-第一个C程序.mp4</videoURL>
        <imageURL>/itcast/images/head2.png</imageURL>
        <desc>C语言-第一个C程序</desc>
        <teacher>李雷</teacher>
    </video>
    <video videoId="3">
        <name>03.C语言-函数</name>
        <length>822</length>
        <videoURL>/itcast/videos/03.C语言-函数.mp4</videoURL>
        <imageURL>/itcast/images/head3.png</imageURL>
        <desc>C语言-函数</desc>
        <teacher>韩梅梅</teacher>
    </video>
</videos>
  • 上面只是为了更好的展示XML的结构,在实际解析中,XML是不允许随意增加换行和空格,否则在下面的解析中会导致,内容错误
<?xml version="1.0" encoding="utf-8"?>
<videos><video videoId="1"><name>01.C语言-语法预览</name><length>320</length><videoURL>/itcast/videos/01.C语言-语法预览.mp4</videoURL><imageURL>/itcast/images/head1.png</imageURL><desc>C语言-语法预览</desc><teacher>李雷</teacher></video><video videoId="2"><name>02.C语言-第一个C程序</name><length>2708</length><videoURL>/itcast/videos/02.C语言-第一个C程序.mp4</videoURL><imageURL>/itcast/images/head2.png</imageURL><desc>C语言-第一个C程序</desc><teacher>李雷</teacher></video><video videoId="3"><name>03.C语言-函数</name><length>822</length><videoURL>/itcast/videos/03.C语言-函数.mp4</videoURL><imageURL>/itcast/images/head3.png</imageURL><desc>C语言-函数</desc><teacher>韩梅梅</teacher></video></videos>
  • 开始解析,利用NSXMLParser类的代理NSXMLParserDelegate方法进行
NSString *path = [[NSBundle mainBundle] pathForResource:@"demo.xml" ofType:nil];
NSData *data = [NSData dataWithContentsOfFile:path];
//解析数据
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
//设置代理
parser.delegate = self;
//开始执行代理的方法,代理的方法中开始解析
[parser parse];
  • 通过代理方法进行数据处理,注意点
  1. KVC的赋值方式,本质上是将模型对象属性的地址指向数据中值的地址,进行的是浅拷贝,所以要将对象中的属性定义为copy进行深拷贝。特别的,当要将NSString通过浅拷贝赋值给NSNumber时,会出错,是因为错误的指向了类,所以要通过copy将内容拷贝一份赋值
  2. 在找节点内容的时候,一般会分段找,所以要通过可变字符串进行拼接,并且在找到结束节点的时候,通过KVC赋值,并且清零,保证过程中正常使用
//代理方法执行和设置代理属性再同一线程
//开始解析文档
- (void)parserDidStartDocument:(NSXMLParser *)parser{
    NSLog(@"1 开始解析文档 %@", [NSThread currentThread]);
}
//开始找节点
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary<NSString *,NSString *> *)attributeDict{
    //elementName 节点名称
    //attributeDict 标签的属性
    NSLog(@"2 开始找节点 %@-----%@", elementName, attributeDict);
    if ([elementName isEqualToString:@"video"]) {
        self.currentVideo = [Video new];
        self.currentVideo.videoId = @([attributeDict[@"videoId"] intValue]);
        //添加到数组
        [self.videos addObject:self.currentVideo];
    }
}
//找节点之间的内容,也会执行多次,要通过可变字符串进行拼接
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
    NSLog(@"3 找节点之间的内容 %@", string);
    [self.mString appendString:string];
}
//找结束节点
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
    NSLog(@"4 找结束节点 %@", elementName);
    //判断标签是否是对应的属性,只要内容标签中的数据
    if (![elementName isEqualToString:@"video"] && ![elementName isEqualToString:@"videos"]) {
        //kvc赋值的过程就是地址指向的过程,属性的地址被指向值的地址,nsnumber无法指向string,不会做类型转换
        [self.currentVideo setValue:self.mString forKey:elementName];
    }
    //清空可变字符串
    [self.mString setString:@""];
}
//结束解析文档
- (void)parserDidEndDocument:(NSXMLParser *)parser{
    NSLog(@"5 结束解析文档");
    NSLog(@"%@", self.videos);
    //要返回主队列刷新view
}
//错误判断
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError{
    NSLog(@"出错");
}
  • 懒加载
@property (nonatomic, strong) NSMutableArray *videos;
//当前video对象
@property (nonatomic, strong) Video *currentVideo;
//存储当前节点内容,负责数据拼接和赋值
@property (nonatomic, copy) NSMutableString *mString;
//存储模型
- (NSMutableArray *)videos{
    if (!_videos) {
        _videos = [NSMutableArray arrayWithCapacity:10];
    }
    return _videos;
}
- (NSMutableString *)mString{
    if (!_mString) {
        _mString = [NSMutableString new];
    }
    return _mString;
}
  • 模型类中的属性
@property (nonatomic, copy) NSNumber *videoId;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSNumber *length;
@property (nonatomic, copy) NSString *videoURL;
@property (nonatomic, copy) NSString *imageURL;
@property (nonatomic, copy) NSString *desc;
@property (nonatomic, copy) NSString *teacher;
  • 更快的解析方式,GDataXMLNode,利用节点,直接找到每个节点的内容和属性
NSData *data = [NSData dataWithContentsOfFile: [[NSBundle mainBundle] pathForResource:@"demo.xml" ofType:nil]];
GDataXMLDocument *document = [[GDataXMLDocument alloc] initWithData:data error:NULL];
//获取xml根节点
GDataXMLElement *rootElement = document.rootElement;
//获取所有子节点 遍历
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:10];
for (GDataXMLElement *element in rootElement.children) {
    Video *v = [Video new];
    [mArray addObject:v];
    //给对象的属性赋值
    //1.遍历video的子标签
    for (GDataXMLElement *subElement in element.children) {
        //属性赋值
        [v setValue:subElement.stringValue forKey:subElement.name];
    }
    //2.遍历video的所有属性
    NSLog(@"%@", element.attributes);
    for (GDataXMLNode *attr in element.attributes) {
        [v setValue:attr.stringValue forKey:attr.name];
    }
}

总结

  • 无论是JSON还是XML,都只是一种存储数据的模型,说到底,还是键值对的形式,而因为是键值对的形式,就可以进行转模型,通过一个模型对象进行管理,核心处理思路也是围绕数组转模型进行的