NSURLConnection使用详解

8 篇文章 0 订阅

NSURLConnection的历史

iOS2.0推出的,至今有10多年的历史
苹果几乎没有对 NSURLConnection 做太大的改动
sendAsynchronousRequest 方法是 iOS5.0之后,苹果推出的
在 iOS5.0之前,苹果的网络开发是处于黑暗时代
需要使用 代理 方法,还需要使用运行循环,才能够处理复杂的网络请求

1、sendAsynchronousRequest异步下载

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // url 字符串
    NSString *urlStr = @"http://localhost/图片浏览器.mp4";
    // 添加百分号转义
    urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    // 请求路径
    NSURL *url = [NSURL URLWithString:urlStr];
    // 请求对象
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSLog(@"开始下载");
    // 发送异步请求
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
        // 将数据写入到文件中
        [data writeToFile:@"/Users/pkxing/desktop/123.mp4" atomically:YES];
        NSLog(@"下载完毕");
    }];
}

上面下载大文件代码存在的问题:
* 没有进度跟进
* 出现内存峰值

2、代理方法下载—>错误的代理

# 能解决没有进度跟进的问题,但是是错误的代理
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // url 字符串
    NSString *urlStr = @"http://localhost/图片浏览器.mp4";
    // 添加百分号转义
    urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    // 请求路径
    NSURL *url = [NSURL URLWithString:urlStr];
    // 请求对象
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSLog(@"开始下载");
    // 发送请求
    [NSURLConnection connectionWithRequest:request delegate:self];
}

#pragma mark - NSURLConnectionDownloadDelegate 代理方法
/**
 *  下载完成后回调方法
 */
- (void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *) destinationURL{
    NSLog(@"destinationURL = %@",destinationURL);
}

/**
 *  每当接收到服务器返回数据后的回调方法
 *
 *  @param bytesWritten       本次下载的字节数
 *  @param totalBytesWritten  已经下载的字节数
 *  @param expectedTotalBytes 期望下载的字节数(总大小)
 */
- (void)connection:(NSURLConnection *)connection didWriteData:(long long)bytesWritten totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long)expectedTotalBytes {
    //    NSLog(@"%lld--%lld---%lld",bytesWritten,totalBytesWritten,expectedTotalBytes);
    // 小 long long / 大 long long = 0
    CGFloat progress = (CGFloat)totalBytesWritten / expectedTotalBytes;
    NSLog(@"progress = %f",progress);
}
> 使用NSURLConnectionDownloadDelegate代理方法问题:
* NSURLConnectionDownloadDelegate只适用于使用 NewsstandKit创建的NSURLConnection对象。对于Foundation框架创建的NSURLConnection对象,使用该代理下载完成后无法找到下载的文件。
* NewsstandKit.framework来支持newsstand类型的程序,就是在sprint board上看到在书架中的程序,NSURLConnectionDownloadDelegate用于刊物/电子杂志的下载。

3、代理方法下载—>正确的代理方法

// 文件总大小
@property(nonatomic,assign) long  long expectedContentLenght;
// 当前接收文件大小
@property(nonatomic,assign) long  long currentFileSize;
// 文件数据
@property(nonatomic,strong) NSMutableData *fileData;

- (NSMutableData *)fileData {
    if (_fileData == nil) {
        _fileData = [NSMutableData data];
    }
    return _fileData;
}

#pragma mark - NSURLConnectionDataDelegate 代理方法
/**
 *  接收到服务器响应的时候调用(状态行和响应头)
 */
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    // 获得要下载文件总大小
    self.expectedContentLenght = response.expectedContentLength;
    // 设置当前接收文件大小为0
    self.currentFileSize = 0;
}

/**
 * 接收到服务器返回的数据时调用,可能会被调用多次
 */
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    self.currentFileSize += data.length;
    // 计算进度值
    CGFloat progress = (CGFloat)self.currentFileSize / self.expectedContentLenght;
    // 拼接数据
    [self.fileData appendData:data];
    NSLog(@"progress = %f",progress);
}

/**
 *  网络请求结束调用(断开网络)
 */
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    NSLog(@"下载完成");
    // 将数据写入到文件中
    [self.fileData writeToFile:@"/Users/pkxing/desktop/1234.mp4" atomically:YES];
    // 释放数据
    self.fileData = nil;
}
/**
 *  网络连接发生错误的时候调用(任何网络请求都有可能出现错误)
 *  在实际开发中一定要进行出错处理
 */
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    NSLog(@"error = %@",error);
}
> 上面代码通过NSURLConnectionDataDelegate代理下载数据,解决了进度问题,但内存问题没有解决。

4、利用NSFileHandle拼接文件

/**
 *  将数据写入文件
 */
- (void)writeData:(NSData *)data{
    // NSFileHandle:Handle(句柄/文件指针)是针对前面一个单词(File)进行操作的对象
    // 利用NSFileHandle可以对文件进行读写操作。
    // NSFileManager:对文件的复制,删除,检查是否存在,检查文件大小...类似于Finder
    // 创建文件句柄对象
    NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:@"/Users/pkxing/desktop/aaa.mp4"];
    // 如果文件不存在,创建出来的句柄对象为 nil
    if (fileHandle == nil) {
        [data writeToFile:@"/Users/pkxing/desktop/aaa.mp4" atomically:YES];
    } else {
        // 将文件指针移动到后面
        [fileHandle seekToEndOfFile];
        // 写入数据
        [fileHandle writeData:data];
        // 关闭文件(在对文件进行操作时,一定要记得打开和关闭成对出现)
        [fileHandle closeFile];
    }
}

#pragma mark - NSURLConnectionDataDelegate 代理方法
/**
 *  接收到服务器响应的时候调用(状态行和响应头)
 */
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    // 获得要下载文件总大小
    self.expectedContentLenght = response.expectedContentLength;
    // 设置当前接收文件大小为0
    self.currentFileSize = 0;
}

/**
 * 接收到服务器返回的数据时调用,可能会被调用多次
 */
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    self.currentFileSize += data.length;
    // 计算进度值
    CGFloat progress = (CGFloat)self.currentFileSize / self.expectedContentLenght;
    // 将数据写入文件
    [self writeData:data];
}

5、利用NSOutputStream拼接文件

#pragma mark - NSURLConnectionDataDelegate 代理方法
/**
 *  接收到服务器响应的时候调用(状态行和响应头)
 */
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    // 获得要下载文件总大小
    self.expectedContentLenght = response.expectedContentLength;
    // 设置当前接收文件大小为0
    self.currentFileSize = 0;

    // 根据文件名 创建输出流对象
    self.fileStream = [NSOutputStream outputStreamToFileAtPath:@"/Users/pkxing/desktop/bbb.mp4" append:YES];
    // 打开流
    [self.fileStream open];
}
/**
 * 接收到服务器返回的数据时调用,可能会被调用多次(所有的 data 的数据都是按顺序传递过来的)
 */
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    self.currentFileSize += data.length;
    // 计算进度值
    CGFloat progress = (CGFloat)self.currentFileSize / self.expectedContentLenght;
    // 拼接数据
    [self.fileStream write:data.bytes maxLength:data.length];
}
/**
 *  网络请求结束调用(断开网络)
 */
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    // 关闭流
    [self.fileStream close];
}
/**
 *  网络连接发生错误的时候调用(任何网络请求都有可能出现错误)
 *  在实际开发中一定要进行出错处理
 */
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    // 关闭流
    [self.fileStream close];
}

6、大文件下载多线程—NSURLConnection

#warning 子线程中要手动开启runLoop,runLoop 是死循环
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    // url 字符串
    NSString *urlStr = @"http://localhost/图片浏览器.mp4";
    // 添加百分号转义
    urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    // 请求路径
    NSURL *url = [NSURL URLWithString:urlStr];
    // 请求对象
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSLog(@"开始下载=%@",[NSThread currentThread]);
    // 发送请求
    [NSURLConnection connectionWithRequest:request delegate:self];
    // 启动 runLoop
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"下载结束");
});

7、大文件下载—暂停下载

> 调用NSURLConnection的cancel方法即可暂停。
> 一旦调用了cancel方法暂停,下次重新下载需要重新NSURLConnection对象。
> 下载默认都是从零开始下载,如果上次下载的文件还存在,则下载的数据会拼接到文件后面。
    #简单粗暴的方法---删除上一次没有下载完成的文件
    // removeItemAtPath:文件存在,则删除,不存在则什么也不做。可以不用判断文件是否存在
    [[NSFileManager defaultManager] removeItemAtPath:@"/Users/pkxing/desktop/bbb.mp4" error:NULL];

8、大文件下载—断点续传

1> 思路
    1、检查服务器文件大小(HEAD请求)
    2、检查本地是否存在文件
    3、如果本地存在文件
        > 如果小于服务器的文件,从当前文件大小开始下载
        > 如果等于服务器的文件,下载完成
        > 如果大于服务器的文件,直接删除,重新下载

2> 代码实现
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // url 字符串
        NSString *urlStr = @"http://localhost/图片浏览器.mp4";
        // 添加百分号转义
        urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        // 请求路径
        NSURL *url = [NSURL URLWithString:urlStr];

        // 检查服务器文件信息
        [self checkServerFileInfo:url];
        // 检查本地文件信息
        self.currentFileSize =[self checkLocalFileInfo];
        // 文件大小相等
        if (self.currentFileSize == self.expectedContentLenght) {
            NSLog(@"下载完成");
            return;
        }
        // 断点续传---一定不能使用缓存数据
        // 请求对象
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:15.0];
        // 创建 range 头
        NSString *range = [NSString stringWithFormat:@"bytes=%lld-",self.currentFileSize];
        [request setValue:range forHTTPHeaderField:@"Range"];
        // 建立连接,立即启动
        self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
        // 启动 runLoop
        [[NSRunLoop currentRunLoop] run];
    });
}

// 检查服务器的文件信息
- (void)checkServerFileInfo:(NSURL *)url{
    // 创建请求对象
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    // 设置请求方法
    request.HTTPMethod = @"HEAD";

    NSURLResponse *response = nil;
    // 发送同步请求(这里必须要用同步)
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];
    // 得到服务器响应
    // 1> 目标文件大小
    self.expectedContentLenght = response.expectedContentLength;
    // 2> 保存文件路径
    self.destinationPath = [NSTemporaryDirectory() stringByAppendingPathComponent:response.suggestedFilename];
}

// 检查本地文件的信息
- (long long)checkLocalFileInfo{
    // 获得文件管理对象
    NSFileManager *fileManager = [NSFileManager defaultManager];
    // 记录本地文件的大小
    long long fileSize = 0;
    // 判断文件是否存在
    if([fileManager fileExistsAtPath:self.destinationPath]) {
        // 文件存在,则获得文件信息
        NSDictionary *attr = [fileManager attributesOfItemAtPath:self.destinationPath error:NULL];
        // 直接从字典中获得文件大小
        fileSize = attr.fileSize;
    }
    // 如果大于服务器文件大小,直接删除
    if(fileSize > self.expectedContentLenght) {
        [fileManager removeItemAtPath:self.destinationPath error:NULL];
        fileSize = 0;
    }
    return fileSize;
}

9, 注意事项:

 1,使用代理模式,GCD多线程下载,一定要开启 runloop,因为下载也是一个事件,需要不断的调用代理方法,所以开启子线程运行循环
 2,NSTimer 也是需要开启 runloop 
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // url 字符串
        NSString *urlStr = @"http://soft1.xitongzhijia.net:808/201205/MicrosoftOffice2007_SP1_XiTongZhiJia.rar";
        // 添加百分号转义
        urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        // 请求路径
        NSURL *url = [NSURL URLWithString:urlStr];
        // 请求对象
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        NSLog(@"开始下载=%@",[NSThread currentThread]);
        // 发送请求
        [NSURLConnection connectionWithRequest:request delegate:self];
        // 启动 runLoop
       [[NSRunLoop currentRunLoop] run];
    });
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值