傳智播客視頻學(xué)習(xí)筆記+個(gè)人總結(jié)
發(fā)送請(qǐng)求的步驟:
1.設(shè)置url
2.設(shè)置request
3.發(fā)送請(qǐng)求,同步or異步
使用同步方法下載文件
在主線程調(diào)用同步方法,一直在等待服務(wù)器返回?cái)?shù)據(jù),代碼會(huì)卡住,如果服務(wù)器,沒(méi)有返回?cái)?shù)據(jù),那么在主線程UI會(huì)卡住不能繼續(xù)執(zhí)行操作。有返回值NSData
//1.url
NSString *urlstr = @"xxxx";
urlstr = [urlstr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:urlstr];
//2.request
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3.connection
NSLog(@"start");
NSData *data=[NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
//一直在等待服務(wù)器返回?cái)?shù)據(jù)
NSLog(@"--%d--",data.length);
- 注意:同步的連接會(huì)阻塞調(diào)用它的線程,不一定是主線程
使用異步方法下載文件
沒(méi)有返回值
//1.url
NSString *urlstr = @"xxxx";
urlstr = [urlstr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:urlstr];
//2.request
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3.connection
NSLog(@"start");
//使用這個(gè)方法會(huì)有內(nèi)存峰值
//queue參數(shù)是指定block的執(zhí)行隊(duì)列
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
//將文件寫入磁盤
//內(nèi)存峰值:下載完成瞬間再把文件寫入磁盤。下載文件有多大,NSData就會(huì)占用多大的內(nèi)存。
[data writeToFile:@"XXXXXX" atomically:YES];
NSLog(@"finsh");
}];
- 開一條新的線程去發(fā)送請(qǐng)求,主線程繼續(xù)往下走,當(dāng)拿到服務(wù)器的返回?cái)?shù)據(jù)的數(shù)據(jù)的時(shí)候再回調(diào)block,執(zhí)行block代碼段。不會(huì)卡住主線程。
- 關(guān)于queue參數(shù),隊(duì)列的作用:An NSOperationQueue upon which the handler block will be dispatched.決定這個(gè)block操作放在哪個(gè)線程執(zhí)行。
如果是NSOperationQueue *queue=[[NSOperationQueue alloc]init];默認(rèn)是異步執(zhí)行。(接下來(lái)也會(huì)有提及)
如果是主隊(duì)列mainqueue,那么在子線程發(fā)送請(qǐng)求成功并獲取到服務(wù)器的數(shù)據(jù)響應(yīng)。就可以回到主線程中解析數(shù)據(jù),并刷新UI界面。 - 如果有刷新UI界面的操作應(yīng)該放在主線程執(zhí)行,不能放在子線程。
存在的問(wèn)題:
1.沒(méi)有下載進(jìn)度,會(huì)影響用戶體驗(yàn)
2.使用異步方法,下載完成執(zhí)行回調(diào)時(shí)再把文件寫入磁盤,會(huì)造成內(nèi)存峰值的問(wèn)題。下載文件有多大,NSData就會(huì)占用多大的內(nèi)存
使用代理
問(wèn)題1的解決辦法:使用代理NSURLConnectionDataDelegate
1.在響應(yīng)方法中獲得文件總大小
2.每次接收到數(shù)據(jù),計(jì)算數(shù)據(jù)的總長(zhǎng)度,和總大小相比,得出百分比
//要下載文件的總長(zhǎng)度
@property (nonatomic , assign)long long expectedContentLength;
//當(dāng)前下載的長(zhǎng)度
@property (nonatomic , assign)long long currentLength;
//1.url
NSString *urlstr = @"xxxx";
urlstr = [urlstr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:urlstr];
//2.request
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3.connection,不做設(shè)置的話是在主線程中執(zhí)行之后的下載
NSLog(@"start");
NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
//設(shè)置代理工作的操作隊(duì)列
[conn setDelegateQueue:[[NSOperationQueue alloc]init]];
//4.啟動(dòng)連接
[conn start];
//代理
//1.接收到服務(wù)器的響應(yīng) :狀態(tài)行和響應(yīng)頭,用來(lái)做一些準(zhǔn)備工作
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSLog(@"%@",response);//response里面有狀態(tài)行和響應(yīng)頭
//記錄文件總大小
self.expectedContentLength = response.expectedContentLength;
self.currentLength = 0;
}
//2.接收到服務(wù)器的數(shù)據(jù) :此方法可能會(huì)執(zhí)行多次,因?yàn)闀?huì)接收到多個(gè)data
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
NSLog(@"接收到的數(shù)據(jù)長(zhǎng)度%tu",data.length);
//計(jì)算百分比
self.currentLength += data.length;
float progress = (float)self.currentLength / self.expectedContentLength;
NSLog(@"%f",progress);
}
//3.所有數(shù)據(jù)加載完成 : 所有數(shù)據(jù)傳輸完畢之后被調(diào)用,是最后一個(gè)的通知
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
}
//4.下載失敗或者錯(cuò)誤
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
}
- 要下載的文件總大小在服務(wù)器返回的響應(yīng)頭里面可以拿到
NSDictionary *headerDic = response.allHeaderFields;
self.fileLength = [[headerDic objectForKey:@"Content-Length"] intValue];
或者
response.expectedContentLength 要下載的文件總大小
response.suggestedFilename 獲取下載的文件名
-
+(NSURLConnection*) connectionWithRequest:delegate:
During the download the connection maintains a strong reference to the delegate. It releases that strong reference when the connection finishes loading, fails, or is canceled. connection對(duì)代理方法強(qiáng)引用
問(wèn)題2的解決辦法:
保存文件的思路:
a.拼接完成寫入磁盤
b.下載一個(gè)寫一個(gè)
1>NSFileHandle
2>NSOutputStream
** a.拼接完成寫入磁盤**
1.生成目標(biāo)文件路徑
2.在didReceiveData里拼接數(shù)據(jù)
3.拼接完成寫入磁盤(在完成下載的方法里)
4.釋放內(nèi)存
//文件保存的路徑
@property (nonatomic , strong) NSString *targetFilePath;
//用來(lái)每次接收到數(shù)據(jù),拼接數(shù)據(jù)使用
@property (nonatomic , strong) NSMutableData *fileData;
//代理
//1.接收到服務(wù)器的響應(yīng) :狀態(tài)行和響應(yīng)頭,用來(lái)做一些準(zhǔn)備工作
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSLog(@"%@",response);
//放到沙盒
//NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
//self.targetFilePath = [cache stringByAppendingPathComponent:response.suggestedFilename];
//生成目標(biāo)文件路徑
self.targetFilePath = [@"/Users/apple/xxxxxx"stringByAppendingPathComponent:response.suggestedFilename];//下載后的文件名字不變
//刪除文件,如果文件存在,就會(huì)直接刪除,如果文件不存在就什么也不做
[[NSFileManager defaultManager]removeItemAtPath:self.targetFilePath error:NULL];
}
- (NSMutableData *)fileData{
if (_fileData == nil) {
_fileData = [[NSMutableData alloc]init];
}
return _fileData;
}
//2.接收到服務(wù)器的數(shù)據(jù) :次方法可能會(huì)執(zhí)行多次,因?yàn)闀?huì)接收到多個(gè)data
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
NSLog(@"接收到的數(shù)據(jù)長(zhǎng)度%tu",data.length);
//拼接數(shù)據(jù)
// a.拼接完成寫入磁盤
[self.fileData appendData:data];
}
//3.所有數(shù)據(jù)加載完成 : 所有數(shù)據(jù)傳輸完畢之后被調(diào)用,是最后一個(gè)的通知
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
//a.拼接完成寫入磁盤
[self.fileData writeToFile:self.targetFilePath atomically:YES];
//釋放內(nèi)存
self.fileData = nil;//不釋放的話,內(nèi)存一直被占用,文件多大被占用的就有多大
}
- 把下載好的文件放到沙盒:
NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *file = [cache stringByAppendingPathComponent:response.suggestedFilename];```
response.suggestedFilename:建議保存的文件名
- 文件寫入磁盤后要釋放data:不釋放的話,內(nèi)存一直被占用,文件多大被占用的就有多大
存在的問(wèn)題:測(cè)試結(jié)果:也是存在內(nèi)存峰值
** b.下載一個(gè)寫一個(gè)**
1>NSFileHandle
//2.接收到服務(wù)器的數(shù)據(jù) :次方法可能會(huì)執(zhí)行多次,因?yàn)闀?huì)接收到多個(gè)data
-
(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
//拼接數(shù)據(jù)
// b.下載一個(gè)寫一個(gè)
[self writeToFileWithData:data];
} -
(void)writeToFileWithData:(NSData )data{
//文件操作
/
NSFileManager:主要功能,創(chuàng)建目錄,檢查目錄是否存在,遍歷目錄,刪除文件...針對(duì)文件的操作,類似于Finder
NSFileHandle:文件“句柄(文件指針)”,如果在開發(fā)中,看到Handle這個(gè)單詞,就意味著是對(duì)前面的單詞“File”進(jìn)行操作的對(duì)象
主要功能,就是對(duì)同一個(gè)文件進(jìn)行二進(jìn)制的讀寫操作的對(duì)象。
*///如果文件不存在,fp在實(shí)例化的結(jié)果是空
NSFileHandle *fp = [NSFileHandle fileHandleForWritingAtPath:self.targetFilePath];
//判斷文件是否存在
//如果不存在,直接將數(shù)據(jù)存入磁盤
if(fp == nil){
[data writeToFile:self.targetFilePath atomically:YES];
}//如果存在,將Data追繳到現(xiàn)有文件
else{
//1.將文件指針移到文件的末尾
[fp seekToEndOfFile];
//2.寫入文件
[fp writeData:data];
//3.關(guān)閉文件。在c語(yǔ)言開發(fā)中,凡是涉及到文件讀寫,打開和關(guān)閉通常是成對(duì)出現(xiàn)的
[fp closeFile];
}
}
上面的寫法多次打開、關(guān)閉文件,下面進(jìn)行改進(jìn):
-
(void)connection:(NSURLConnection )connection didReceiveResponse:(NSURLResponse )response
{
NSString ceches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString filepath = [ceches stringByAppendingPathComponent:response.suggestedFilename];// 創(chuàng)建一個(gè)空的文件到沙盒中
NSFileManager* mgr = [NSFileManager defaultManager];
[mgr createFileAtPath:filepath contents:nil attributes:nil];
// 創(chuàng)建一個(gè)用來(lái)寫數(shù)據(jù)的文件句柄對(duì)象
self.writeHandle = [NSFileHandle fileHandleForWritingAtPath:filepath];
} (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.writeHandle seekToEndOfFile];
[self.writeHandle writeData:data];
}(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[self.writeHandle closeFile];
self.writeHandle = nil;
}
測(cè)試結(jié)果:徹底解決了內(nèi)存峰值的問(wèn)題。是傳統(tǒng)的文件操作方式。
2>NSOutputStream輸出流
1.創(chuàng)建流
2.打開流
3.將數(shù)據(jù)追加到流
4.關(guān)閉流
//保存文件的輸出流
/*
- (void)open;寫入之前打開流
- (void)close;完成之后關(guān)閉流
*/
@property (nonatomic , strong) NSOutputStream *fileStream;
//代理
//1.接收到服務(wù)器的響應(yīng) :狀態(tài)行和響應(yīng)頭,用來(lái)做一些準(zhǔn)備工作
-
(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSLog(@"%@",response);//生成目標(biāo)文件路徑
self.targetFilePath = [@"/Users/apple/xxxxxx"stringByAppendingPathComponent:response.suggestedFilename];
//刪除文件,如果文件存在,就會(huì)直接刪除,如果文件不存在沒(méi)就什么也不做
[[NSFileManager defaultManager]removeItemAtPath:self.targetFilePath error:NULL];self.fileStream = [[NSOutputStream alloc]initToFileAtPath:self.targetFilePath append:YES];
[self.fileStream open];
}
//2.接收到服務(wù)器的數(shù)據(jù) :次方法可能會(huì)執(zhí)行多次,因?yàn)闀?huì)接收到多個(gè)data
-
(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
//拼接數(shù)據(jù)
//將數(shù)據(jù)追加到文件流中
[self.fileStream write:data.bytes maxLength:data.length];
}
//3.所有數(shù)據(jù)加載完成 : 所有數(shù)據(jù)傳輸完畢之后被調(diào)用,是最后一個(gè)的通知
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
//關(guān)閉文件流
[self.fileStream close];
}
###斷點(diǎn)續(xù)傳
要實(shí)現(xiàn)斷點(diǎn)續(xù)傳要利用HTTP的range請(qǐng)求頭。bytes = 500-999表示從500-999的500個(gè)字節(jié)。續(xù)傳的Demo:[斷點(diǎn)續(xù)傳](http://www.cnblogs.com/GeekStar/p/4409714.html)
demo里面的例子只適合于應(yīng)用運(yùn)行期間續(xù)傳。比如,一旦應(yīng)用在下載期間中途退出,再次運(yùn)行時(shí),下載將會(huì)重新開始
// 設(shè)置請(qǐng)求頭數(shù)據(jù)
NSString *range = [NSString stringWithFormat:@"bytes=%lld-",self.currentLen];
[request setValue:range forHTTPHeaderField:@"Range"];
多線程斷點(diǎn)續(xù)傳:http://www.cnblogs.com/wendingding/p/3947550.html
###總結(jié)
- 小文件下載:可以使用sendAsynchronousRequest:queue:completionHandler 這個(gè)方法一次性返回整個(gè)下載到的文件,返回的data在內(nèi)存中,如果下載一個(gè)幾百兆的東西,這樣會(huì)造成內(nèi)存峰值。( [NSData dataWithContentsOfURL:url]這個(gè)方法也是一樣)
- 大文件下載:使用代理
####NSURLConnection+NSRunLoop
新問(wèn)題:下載默認(rèn)下在主線程工作。下載本身是不是異步的(NSURLConnection實(shí)例運(yùn)行在主線程)
```[conn setDelegateQueue:[[NSOperationQueue alloc]init]];```
指定了代理的工作隊(duì)列之后,整個(gè)下載仍然會(huì)受主線程的干擾,以及更新ui(進(jìn)度條)不及時(shí)。[在storyboard上添加了一個(gè)進(jìn)度條以及一個(gè)uitextview,下載的時(shí)候,進(jìn)度條會(huì)卡頓,滑動(dòng)textview下載會(huì)暫停,停止滑動(dòng)后又繼續(xù)下載]
``` NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];```在這里,delegate參數(shù)在api里面的說(shuō)明如下:
The delegate object for the connection. The connection calls methods on this delegate as the load progresses. Delegate methods are called on the same thread that called this method. For the connection to work correctly, the calling thread’s run loop must be operating in the default run loop mode.代理方法被調(diào)用connectionWithRequest:delegate:這個(gè)方法的同一個(gè)線程調(diào)用,為了保證連接的工作正常,調(diào)用線程的runloop必須運(yùn)行在默認(rèn)的運(yùn)行循環(huán)模式下。
[一個(gè)異步網(wǎng)絡(luò)請(qǐng)求的坑:關(guān)于NSURLConnection和NSRunLoopCommonModes](http://www.hrchen.com/2013/06/nsurlconnection-with-nsrunloopcommonmodes/)這篇博文有提到這個(gè)問(wèn)題:
如果是直接調(diào)用initWithRequest:delegate:startImmediately:(第三個(gè)參數(shù)用YES)或者方法initWithRequest:delegate:時(shí)(調(diào)用完connection就直接運(yùn)行了),NSURLConnection會(huì)默認(rèn)運(yùn)行在**NSDefaultRunLoopMode**模式下,即使再使用scheduleInRunLoop:forMode:設(shè)置運(yùn)行模式也沒(méi)有用。如果NSURLConnection是運(yùn)行在NSDefaultRunLoopMode,而當(dāng)前線程是**主線程**(NSURLConnection實(shí)例運(yùn)行在主線程,主線程有一個(gè)運(yùn)行的runloop實(shí)例來(lái)支持NSURLConnection的異步執(zhí)行),并且UI上有類似滾動(dòng)這樣的操作,那么主線程的Run Loop會(huì)運(yùn)行在**UITrackingRunLoopMode**下,就無(wú)法響應(yīng)NSURLConnnection的回調(diào)。此時(shí)需要首先使用initWithRequest:delegate:startImmediately:(第三個(gè)參數(shù)為NO)生成NSURLConnection,再重新設(shè)置NSURLConnection的運(yùn)行模式為**NSRunLoopCommonModes**,那么UI操作和回調(diào)的執(zhí)行都將是非阻塞的,因?yàn)镹SRunLoopCommonModes是一組run loop mode的集合,默認(rèn)情況下包含了NSDefaultRunLoopMode和UITrackingRunLoopMode。
- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode在api中的說(shuō)明
將connection實(shí)例回調(diào)加入到一個(gè)runloop,NSURLConnectionDelegate回調(diào)會(huì)在這個(gè)runloop中響應(yīng)
Determines the run loop and mode that the connection uses to call methods on its delegate.
By default, a connection is scheduled on the current thread in the default mode when it is created. If you create a connection with the initWithRequest:delegate:startImmediately: method and provide NO for the startImmediately parameter, you can schedule the connection on a different run loop or mode before starting it with the start method. You can schedule a connection on multiple run loops and modes, or on the same run loop in multiple modes.
You cannot reschedule a connection after it has started.It is an error to schedule delegate method calls with both this method and the setDelegateQueue: method.
方法參數(shù):
aRunLoop:The NSRunLoop instance to use when calling delegate methods。
mode:The mode in which to call delegate methods.
這個(gè)方法不能和setDelegateQueue方法一起使用
總結(jié)一下個(gè)人理解,默認(rèn)情況下,代理方法被調(diào)用connectionWithRequest:delegate:這個(gè)方法的同一個(gè)線程調(diào)用,NSURLConnection默認(rèn)運(yùn)行在NSDefaultRunLoopMode模式下;但是使用scheduleInRunLoop: forMode:可以設(shè)置代理方法運(yùn)行在哪個(gè)runloop(相當(dāng)于是設(shè)置運(yùn)行線程?)和mode下
//運(yùn)行在主線程下
NSMutableURLRequest* request = [[NSMutableURLRequest alloc]
initWithURL:self.URL
cachePolicy:NSURLCacheStorageNotAllowed
timeoutInterval:self.timeoutInterval];
self.connection =[[NSURLConnection alloc] initWithRequest:request
delegate:self
startImmediately:NO];
//設(shè)置回調(diào)代理方法運(yùn)行在那個(gè)runloop和mode,這里設(shè)置在當(dāng)前runloop也就是主線程里面執(zhí)行代理方法
[self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.connection start];
**1.為了不影響ui線程,把下載工作放到子線程里面**
dispatch_async(dispatch_get_global_queue(0, 0), ^{//這樣寫,下載不執(zhí)行了。[conn start];之后子線程被回收釋放內(nèi)存空間
//1.url
NSString *urlstr = @"xxxx";
urlstr = [urlstr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:urlstr];
//2.request
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3.connection,不做設(shè)置的話是在主線程中執(zhí)行之后的下載
NSLog(@"start,%@",[NSThread currentThread]);
NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
//設(shè)置代理工作的操作隊(duì)列
[conn setDelegateQueue:[[NSOperationQueue alloc]init]];
//4.啟動(dòng)連接
[conn start];
});
把網(wǎng)絡(luò)操作方法放到子線程執(zhí)行以后,回調(diào)代理方法無(wú)法執(zhí)行,無(wú)法下載。
原因:主線程runloop會(huì)自動(dòng)啟動(dòng),子線程runloop默認(rèn)不啟動(dòng)。將網(wǎng)絡(luò)操作放在異步執(zhí)行,異步的runloop不啟動(dòng),沒(méi)有辦法監(jiān)聽到網(wǎng)絡(luò)事件。[conn start];之后子線程被回收釋放內(nèi)存空間。
[一個(gè)異步網(wǎng)絡(luò)請(qǐng)求的坑:關(guān)于NSURLConnection和NSRunLoopCommonModes](http://www.hrchen.com/2013/06/nsurlconnection-with-nsrunloopcommonmodes/)這篇博文有提到這個(gè)問(wèn)題:如果是用GCD在其他線程中啟動(dòng)NSURLConnection:不會(huì)得到NSURLConnection回調(diào),而從主線程中啟動(dòng)NSURLConnection可以得到回調(diào),這是由于在GCD全局隊(duì)列中執(zhí)行時(shí)沒(méi)有運(yùn)行Run Loop,那么NSURLConnection也就無(wú)法觸發(fā)回調(diào)了。
解決的辦法如下:
**2.啟動(dòng)子線程runloop**
a.[[NSRunLoop currentRunLoop] run];使用這種方法使用,runloop永遠(yuǎn)釋放不掉
b.開啟一個(gè)循環(huán),判斷下載是否完成。這種方法對(duì)系統(tǒng)消耗非常大
@property (nonatomic , assign , getter = isFinished) BOOL finished;
//主線程runloop會(huì)自動(dòng)啟動(dòng),子線程runloop默認(rèn)不啟動(dòng)
//將網(wǎng)絡(luò)操作放在異步執(zhí)行,異步的runloop不啟動(dòng),沒(méi)有辦法監(jiān)聽到網(wǎng)絡(luò)事件
dispatch_async(dispatch_get_global_queue(0, 0), ^{//這樣寫,下載不執(zhí)行了。[conn start];之后子線程被回收釋放內(nèi)存空間
......
//4.啟動(dòng)連接
[conn start];
self.finished = NO;
//5.啟動(dòng)運(yùn)行循環(huán)
// a. [[NSRunLoop currentRunLoop] run];//這樣寫永遠(yuǎn)釋放不掉
// b.
while (!self.isFinished) {
//啟動(dòng)一個(gè)死循環(huán),每次監(jiān)聽0.1秒.
//對(duì)系統(tǒng)消耗非常大
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}
});
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
......
self.finished = YES;
}
c.方法b的改進(jìn),例子里面使用到CFRunLoop
@property (nonatomic , assign) CFRunLoopRef rl;
//主線程runloop會(huì)自動(dòng)啟動(dòng),子線程runloop默認(rèn)不啟動(dòng)
//將網(wǎng)絡(luò)操作放在異步執(zhí)行,異步的runloop不啟動(dòng),沒(méi)有辦法監(jiān)聽到網(wǎng)絡(luò)事件
dispatch_async(dispatch_get_global_queue(0, 0), ^{//這樣寫,下載不執(zhí)行了。[conn start];之后子線程被回收釋放內(nèi)存空間
//1.url
NSString *urlstr = @"xxxx";
urlstr = [urlstr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:urlstr];
//2.request
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3.connection,不做設(shè)置的話是在主線程中執(zhí)行之后的下載
//開始時(shí)的線程是由dispatch_async 創(chuàng)建的
NSLog(@"start,%@",[NSThread currentThread]);
NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
//設(shè)置代理工作的操作隊(duì)列
[conn setDelegateQueue:[[NSOperationQueue alloc]init]];
//指定調(diào)度代理工作的操作隊(duì)列。操作隊(duì)列的特點(diǎn):添加任務(wù),立即異步執(zhí)行,具體的線程程序員不能決定
//4.啟動(dòng)連接
[conn start];
//5。啟動(dòng)運(yùn)行循環(huán)
//CF框架
// CFRunLoopStop(CFRunLoopRef rl);停止指定的runloop
// CFRunLoopGetCurrent();當(dāng)前線程的runloop
// CFRunLoopRun();直接運(yùn)行當(dāng)前線程的runloop
//1.拿到當(dāng)前的runloop
self.rl = CFRunLoopGetCurrent();
//2.啟動(dòng)運(yùn)行循環(huán)
CFRunLoopRun();
});
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
//結(jié)束時(shí)代理工作的線程,是由指定的NSOperationQueue創(chuàng)建的,和創(chuàng)建下載操作的線程是不一樣的。
//關(guān)閉runloop,要關(guān)閉指定線程上的runloop。在這里拿到創(chuàng)建下載那個(gè)線程的runloop
NSLog(@"finish,%@",[NSThread currentThread]);
//關(guān)閉文件流
......
CFRunLoopStop(self.rl);
}

- 開始時(shí)的下載線程是由dispatch_async 創(chuàng)建的,```NSLog(@"start,%@",[NSThread currentThread]);```
結(jié)束時(shí)**代理方法**工作的線程,是由指定的NSOperationQueue創(chuàng)建的:```[conn setDelegateQueue:[[NSOperationQueue alloc]init]];```,和創(chuàng)建下載操作的線程是不一樣的。```NSLog(@"finish,%@",[NSThread currentThread]);```這里打印的兩個(gè)線程結(jié)果是不一樣的。
- ```[conn setDelegateQueue:[[NSOperationQueue alloc]init]];```指定調(diào)度**代理方法**工作的操作隊(duì)列。操作隊(duì)列的特點(diǎn):添加任務(wù),立即異步執(zhí)行,具體的線程程序員不能決定.
- 啟動(dòng)runloop:1.拿到當(dāng)前的runloop ```self.rl = CFRunLoopGetCurrent();```2.啟動(dòng)runloop``` CFRunLoopRun();```
關(guān)閉runloop:要關(guān)閉指定線程上的runloop。在這里拿到創(chuàng)建下載那個(gè)線程的runloop ```CFRunLoopStop(self.rl);```
相關(guān)文章:https://blog.cnbluebox.com/blog/2014/07/01/cocoashen-ru-xue-xi-nsoperationqueuehe-nsoperationyuan-li-he-shi-yong/