NSURLConnection 具体了解

NSURLConnection 网络下载,是iOS2.0就开始提供的API,一直到iOS9的时候被弃用。

+ (void)sendAsynchronousRequest:(NSURLRequest*) request  queue:(NSOperationQueue*) queue completionHandler:(void (^)(NSURLResponse* _Nullable response, NSData* _Nullable data, NSError* _Nullable connectionError)) 这个异步下载的方式是在iOS5才有的,在此之前是没有这个方法的。而且,使用这个方法你无法获取到下载进度,并且当下载完成后才能对数据进行操作。特别是在大数据视频类的下载任务时,占用内存过高,可能就会崩掉。在开发简单的下载任务,并且文件不大的时候还是没有问题的,并且比较方便。

问题来了,那么开发复杂的网络请求,或者文件较大时,怎么用NSURLConnection来做呢?!答案是:NSURLConnectionDataDelegate<代理>。记住,不要和NSURLConnectionDownloadDelegate搞混淆了。NSURLConnectionDownloadDelegate是用于杂志下载的,iOS中有一个系统自带的应用就是杂志。NSURLConnectionDownloadDelegate可以监听下载进度,但是下载完毕后的文件你是拿不到的。

 

通过代理来下载,直接看代码:

 NSString *str = @"https://github.com/chunnilzp/StudyCoreAnimation/archive/master.zip";

 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:str]];

 NSURLConnection *con = [NSURLConnection connectionWithRequest:request delegate:self];

 [con start];

 

下面我们来看看NSURLConnectionDataDelegate的几个代理方法:

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response

1.接收到服务器的响应,做接收文件前的准备工作。

NSURLResponse中包含了下载文件的信息:

expectedContentLength 文件大小

suggestedFilename 文件名称

 

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data

2.接收到服务器的数据,可能被调用很多次

下载并不是一次性把数据全部给你,而是一段一段的下载。所以可能被调用很多次。

 

- (void)connectionDidFinishLoading:(NSURLConnection *)connection

3.下载完毕后的一个通知

 

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error

4.下载失败或错误是的一个通知

 

那么,现在开始解决刚才说的问题:

1.进度条。

定义一个long long的全局变量expectedContentLength,一个long long的全局变量dataSize,一个文件保存路径的变量filePath

在didReceiveResponse中获取文件大小,初始化dataSize,并生成下载文件的保存路径

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{

    self.expectedContentLength = response.expectedContentLength;

    self.dataSize = 0;

    self.filePath = [NSString stringWithFormat:@"***************/%@", response.suggestedFilename];//具体保存在哪,看需求了

}

 

在didReceiveData中计算下载进度

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{

    self.dataSize += [data length];

    float progress = (float)self.dataSize/self.expectedContentLength;

    NSLog(@"当前的进度:%f", progress);

}

 

2.实时把文件写入磁盘,减少内存消耗,这里有2种方式

A.使用NSFileHandle文件句柄,主要功能是对同一个文件进行二进制的读写!

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{

    self.dataSize += [data length];

    float progress = (float)self.dataSize/self.expectedContentLength;

    NSLog(@"当前的进度:%f", progress);

    NSFileHandle *fp = [NSFileHandle fileHandleForWritingAtPath:self.filePath];

    //判断文件是否存在,如果文件不存在,先把文件写入磁盘

    if (fp == nil) {

        [data writeToFile:self.filePath atomically:YES];

    }else{

        //将文件指针移动到文件的末尾

        [fp seekToEndOfFile];

        //在文件指针的地方写入文件

        [fp writeData:data];

        //在C语言的开发中,凡是涉及到文件的读写,都会有打开和关闭的操作

        [fp closeFile];

    }

}

B.NSOutputStream 输出流的形式写入数据,这个可以理解为水瓶,需要操作时,先打开瓶盖,然后添加水(不需要指定在哪里添加,直接当进去就OK),操作完以后盖起来。看代码吧,一个语言能力非常有限的码农。。望理解!

定义NSOutputStream全局变量stream

在didReceiveResponse中初始化stream,并打开盖子!!!

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{

    self.expectedContentLength = response.expectedContentLength;

    self.dataSize = 0;

    self.filePath = [NSString stringWithFormat:@"/Users/lizeping/Desktop/测试数据/%@", response.suggestedFilename];

    self.stream = [[NSOutputStream alloc] initToFileAtPath:self.filePath append:YES];

    [self.stream open];

}

//往水瓶中倒水,并告知水有多少

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{

    //计算下载进度

    self.dataSize += [data length];

    float progress = (float)self.dataSize/self.expectedContentLength;

    NSLog(@"当前的进度:%f", progress);

    [self.stream write:data.bytes maxLength:data.length];

}

盖上盖子!

- (void)connectionDidFinishLoading:(NSURLConnection *)connection{

[self.stream close];

}

3步解决,很简单的处理方式!!!

2种解决方式都可以,看个人喜号自取自用。

下面是重点,说到下载操作,我们都会想到多线程,耗时操作我们不可能丢在主线程,上面的所有东西都是在主线程中执行的。那么,我们如何用子线程去处理,不是简单的把初始化下载任务丢到子线程就OK了。因为是网络事件,可能会有响应延迟,那么子线程跑完就释放了,那么下载的操都不会执行。所以,我们需要开启RunLoop。

会用到CoreFoundation 框架 CFRunLoopRef ,定义全局变量CFRunLoopRef 的downLoadRunLoop。开始线程初始化下载任务的同时NSURLConnection调用setDelegateQueue,让NSURLConnection的所有代理都在子线程中进行(就是上面那4个代理方法全是在子线程中跑)。

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        NSString *str = @"https://github.com/chunnilzp/StudyCoreAnimation/archive/master.zip";

        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:str]];

        NSURLConnection *con = [NSURLConnection connectionWithRequest:request delegate:self];

        [con setDelegateQueue:[[NSOperationQueue alloc] init]];

        [con start];

        self.downLoadRunLoop = CFRunLoopGetCurrent();

        CFRunLoopRun();

    });

这样就可以在子线程中下载了。

当然你启动了Runloop。在执行完后就需要关闭,不然线程无法释放,消耗会很大。

- (void)connectionDidFinishLoading:(NSURLConnection *)connection{

    [self.stream close];

    CFRunLoopStop(self.downLoadRunLoop);

}

代码在这里。明后天是NSURLSession。

很多东西一直在用,特别是经常用的第三方,但是具体里面怎么实现的,用的哪些东西还需要好好了解。

 

你可能感兴趣的:(学习)