你要知道的iOS多線程NSThread芳杏、GCD矩屁、NSOperation、RunLoop都在這里
轉(zhuǎn)載請注明出處 http://www.reibang.com/p/bf0916ee1492
本系列文章主要講解iOS中多線程的使用爵赵,包括:NSThread档插、GCD、NSOperation以及RunLoop的使用方法詳解亚再,本系列文章不涉及基礎(chǔ)的線程/進程、同步/異步威彰、阻塞/非阻塞征绸、串行/并行剃浇,這些基礎(chǔ)概念,有不明白的讀者還請自行查閱如捅。本系列文章將分以下幾篇文章進行講解,讀者可按需查閱调煎。
- iOS多線程——你要知道的NSThread都在這里
- iOS多線程——你要知道的GCD都在這里
- iOS多線程——你要知道的NSOperation都在這里
- iOS多線程——你要知道的RunLoop都在這里
- iOS多線程——RunLoop與GCD镜遣、AutoreleasePool
NSOperation&&NSOperationQueue的使用姿勢全解
經(jīng)過前面的學(xué)習(xí),講解了最基礎(chǔ)的NSThread
使用方法士袄,封裝更完善的GCD
悲关,GCD
提供了極其便捷的方法來編寫多線程程序,可以自動實現(xiàn)多核的真正并行計算娄柳,自動管理線程的生命周期寓辱,好處不言而喻,但可定制性就有點不足了赤拒,Foundation
框架提供了NSOperation
和NSOperationQueue
這一面向?qū)ο蟮亩嗑€程類秫筏,這兩個類與GCD
提供的功能類似诱鞠,NSOperation
提供任務(wù)的封裝,NSOperationQueue
顧名思義这敬,提供執(zhí)行隊列航夺,可以自動實現(xiàn)多核并行計算,自動管理線程的生命周期崔涂,如果是并發(fā)的情況阳掐,其底層也使用線程池模型來管理,基本上可以說這兩個類提供的功能覆蓋了GCD
堪伍,并且提供了更多可定制的開發(fā)方式锚烦,開發(fā)者可以按需選擇。
使用NSOperation
和NSOperationQueue
來編寫多線程程序非常簡單帝雇,只需要創(chuàng)建一個任務(wù)對象涮俄,創(chuàng)建一個執(zhí)行隊列或者和獲取主線程一樣獲取一個主任務(wù)隊列,然后將任務(wù)提交給隊列即可實現(xiàn)并發(fā)尸闸,如過你想要串行只需要將隊列的并發(fā)數(shù)設(shè)置為一即可彻亲。接下來將分別介紹兩個類的使用。
NSOperation “任務(wù)的封裝”
和GCD
類似吮廉,GCD
向隊列提交任務(wù)苞尝,NSOperation
就是對任務(wù)進行的封裝,封裝好的任務(wù)交給不同的NSOperationQueue
即可進行串行隊列的執(zhí)行或并發(fā)隊列的執(zhí)行宦芦。這里的任務(wù)就是NSOperation
類的一個方法宙址,main
方法或start
方法(兩個方法有區(qū)別,后文會講)调卑,但NSOperation
類的這兩個方法是空方法抡砂,沒有干任何事情,因此恬涧,我們需要自定義繼承NSOperation
類并重寫相關(guān)方法注益,OC
也提供了兩個子類供我們使用NSBlockOperation
和NSInvocationOperation
。
接下來看一下NSOperation
類中比較重要的屬性和方法:
/*
對于并發(fā)Operation需要重寫該方法
也可以不把operation加入到隊列中溯捆,手動觸發(fā)執(zhí)行丑搔,與調(diào)用普通方法一樣
*/
- (void)start;
/*
非并發(fā)Operation需要重寫該方法
*/
- (void)main;
//只讀屬性任務(wù)是否取消,如果自定義子類提揍,需要重寫該屬性
@property (readonly, getter=isCancelled) BOOL cancelled;
/*
設(shè)置cancelled屬性為YES
僅僅標(biāo)記cancelled屬性啤月,不退出任務(wù),和NSThread的cancel一個機制
自定義子類時需要使用該屬性判斷是否在外部觸發(fā)了取消任務(wù)的操作碳锈,手動退出任務(wù)
*/
- (void)cancel;
//只讀屬性顽冶,任務(wù)是否正在執(zhí)行,如果自定義子類售碳,需要重寫該屬性
@property (readonly, getter=isExecuting) BOOL executing;
/*
只讀屬性强重,任務(wù)是否結(jié)束绞呈,如果自定義子類,需要重寫該方法
對于加入到隊列的任務(wù)來說间景,當(dāng)finished設(shè)置為YES后佃声,隊列會將任務(wù)移除出隊列
*/
@property (readonly, getter=isFinished) BOOL finished;
//是否為并發(fā)任務(wù),該屬性已經(jīng)被標(biāo)識即將棄用倘要,應(yīng)該使用下面的asynchronous屬性
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
/*
只讀屬性圾亏,判斷任務(wù)是否為并發(fā)任務(wù),默認(rèn)返回NO
如果需要自定義并發(fā)任務(wù)子類封拧,需要重寫getter方法返回YES
*/
@property (readonly, getter=isAsynchronous) BOOL asynchronous;
/*
只讀屬性志鹃,任務(wù)是否準(zhǔn)備就緒
對于加入隊列的任務(wù),當(dāng)ready為YES泽西,標(biāo)識該任務(wù)即將開始執(zhí)行
如果任務(wù)有依賴的任務(wù)沒有執(zhí)行完成ready為NO
*/
@property (readonly, getter=isReady) BOOL ready;
/*
添加一個NSOperation為當(dāng)前任務(wù)的依賴
如果一個任務(wù)有依賴曹铃,需要等待依賴的任務(wù)執(zhí)行完成才能開始執(zhí)行
*/
- (void)addDependency:(NSOperation *)op;
//刪除一個依賴
- (void)removeDependency:(NSOperation *)op;
//任務(wù)在隊列里的優(yōu)先級
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
//任務(wù)在隊列里的優(yōu)先級
@property NSOperationQueuePriority queuePriority;
/*
任務(wù)完成后的回調(diào)方法
當(dāng)finished屬性設(shè)置為YES時才會執(zhí)行該回調(diào)
*/
@property (nullable, copy) void (^completionBlock)(void);
上述內(nèi)容中有一些屬性和方法是在自定義NSOperation
子類中必須要重寫的,自定義子類能夠提供更高的可定制性捧杉,因此陕见,編寫自定義子類更復(fù)雜,自定義子類在后面會講味抖,如果我們只需要實現(xiàn)GCD
那樣的功能评甜,提交一個并發(fā)的任務(wù),OC
為我們提供了兩個子類NSBlockOperation
和NSInvocationOperation
仔涩,這兩個子類已經(jīng)幫我們完成了各種屬性的設(shè)置操作忍坷,我們只需要編寫一個任務(wù)的block
或者一個方法即可像使用GCD
一樣方便的編寫多線程程序。
接下來舉兩個創(chuàng)建任務(wù)的栗子:
//創(chuàng)建一個NSBlockOperation對象熔脂,傳入一個block
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 100; i++)
{
NSLog(@"Task1 %@ %d", [NSThread currentThread], i);
}
}];
/*
創(chuàng)建一個NSInvocationOperation對象承匣,指定執(zhí)行的對象和方法
該方法可以接收一個參數(shù)即object
*/
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task:) object:@"Hello, World!"];
可以發(fā)現(xiàn),創(chuàng)建任務(wù)真的很簡單锤悄,就像GCD
中創(chuàng)建任務(wù)一樣簡潔,任務(wù)創(chuàng)建完成就可以創(chuàng)建隊列了嘉抒。
NSOperationQueue
NSOperationQueue
就是任務(wù)的執(zhí)行隊列零聚,看一下該類中有哪些比較重要的屬性和方法:
//向隊列中添加一個任務(wù)
- (void)addOperation:(NSOperation *)op;
/*
向隊列中添加一組任務(wù)
是否等待任務(wù)完成,如果YES些侍,則阻塞當(dāng)前線程直到所有任務(wù)完成
如果為False隶症,不阻塞當(dāng)前線程
*/
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;
//向隊列中添加一個任務(wù),任務(wù)以block的形式傳入岗宣,使用更方便
- (void)addOperationWithBlock:(void (^)(void))block;
//獲取隊列中的所有任務(wù)
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
//獲取隊列中的任務(wù)數(shù)量
@property (readonly) NSUInteger operationCount;
/*
隊列支持的最大任務(wù)并發(fā)數(shù)
如果為1蚂会,則只支持同時處理一個任務(wù),即串行隊列耗式,主隊列就是串行隊列使用主線程執(zhí)行任務(wù)
如果為大于1的數(shù)胁住,則支持同時處理多個任務(wù)趁猴,即并發(fā)隊列,底層使用線程池管理多個線程來執(zhí)行任務(wù)
*/
@property NSInteger maxConcurrentOperationCount;
//隊列是否掛起
@property (getter=isSuspended) BOOL suspended;
//隊列的名稱
@property (nullable, copy) NSString *name;
/*
取消隊列中的所有任務(wù)
即所有任務(wù)都執(zhí)行cancel方法彪见,所有任務(wù)的cancelled屬性都置為YES
*/
- (void)cancelAllOperations;
//阻塞當(dāng)前線程直到所有任務(wù)完成
- (void)waitUntilAllOperationsAreFinished;
//類屬性儡司,獲取當(dāng)前隊列
@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue;
//類屬性,獲取主隊列余指,任務(wù)添加到主隊列就會使用主線程執(zhí)行捕犬,主隊列的任務(wù)并發(fā)數(shù)為1,即串行隊列
@property (class, readonly, strong) NSOperationQueue *mainQueue;
上述屬性中比較重要的就是maxConcurrentOperationCount
酵镜,該屬性直接決定了隊列是串行的還是并發(fā)的碉碉,接下來看一個栗子:
- (void)task:(id)obj
{
for (int i = 0; i < 100; i++)
{
NSLog(@"Task2 %@ %d %@", [NSThread currentThread], i, obj);
}
}
- (void)viewWillAppear:(BOOL)animated
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:2];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 100; i++)
{
NSLog(@"Task1 %@ %d", [NSThread currentThread], i);
}
}];
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task:) object:@"Hello, World!"];
[queue addOperation:operation];
[queue addOperation:invocationOperation];
}
上面這個栗子就很簡單了,首先創(chuàng)建了一個隊列淮韭,最大任務(wù)并發(fā)數(shù)設(shè)置為2垢粮,接下來創(chuàng)建了兩個任務(wù)并添加進了隊列,摘取幾個輸出如下:
Task2 <NSThread: 0x1c427e440>{number = 3, name = (null)} 0 Hello, World!
Task1 <NSThread: 0x1c0462fc0>{number = 4, name = (null)} 0
從輸出中可以發(fā)現(xiàn)缸濒,兩個任務(wù)使用了兩個不同的線程來執(zhí)行足丢,如果將最大任務(wù)并發(fā)數(shù)量設(shè)置為1這里就會使用同一個線程串行執(zhí)行,任務(wù)2必須得等任務(wù)1執(zhí)行完成才能開始執(zhí)行庇配,就不再做實驗了斩跌。使用Foundation
提供的NSBlockOperation
和NSInvocationOperation
很方便,這兩個子類已經(jīng)幫我們完成了各個重要屬性的設(shè)置操作捞慌,當(dāng)block
或傳入的方法任務(wù)在執(zhí)行時會設(shè)置executing
屬性值為YES
耀鸦,執(zhí)行完成后將executing
設(shè)置為NO
并將finished
設(shè)置為YES
,但是啸澡,如果在block
中使用另一個線程或是GCD
異步執(zhí)行任務(wù)袖订,block
或方法會立即返回,此時就會將finished
設(shè)置為YES
嗅虏,但是其實任務(wù)并沒有完成洛姑,所以這種情況下不能使用該屬性,當(dāng)需要更高定制性時需要使用自定義NSOperation
子類皮服。
這個栗子很簡單楞艾,效果就和我們使用GCD
編寫的多線程程序一樣,接下來再舉個添加依賴的栗子:
- (void)task:(id)obj
{
for (int i = 0; i < 100; i++)
{
NSLog(@"Task5 %@ %d %@", [NSThread currentThread], i, obj);
}
}
- (void)viewWillAppear:(BOOL)animated
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:4];
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 100; i++)
{
NSLog(@"Task1 %@ %d", [NSThread currentThread], i);
}
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 100; i++)
{
NSLog(@"Task2 %@ %d", [NSThread currentThread], i);
}
}];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 100; i++)
{
NSLog(@"Task3 %@ %d", [NSThread currentThread], i);
}
}];
NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 100; i++)
{
NSLog(@"Task4 %@ %d", [NSThread currentThread], i);
}
}];
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task:) object:@"Hello, World!"];
[operation2 addDependency:operation1];
[operation3 addDependency:operation1];
[operation4 addDependency:operation3];
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
[queue addOperation:operation4];
[queue addOperation:invocationOperation];
}
上述栗子添加了五個任務(wù)龄广,任務(wù)依賴關(guān)系如下圖所示:
如圖所示硫眯,任務(wù)2依賴任務(wù)1,任務(wù)3依賴任務(wù)1择同,任務(wù)4依賴任務(wù)3两入,而任務(wù)5是獨立的,所以任務(wù)2需要等待任務(wù)1完成后才可以開始執(zhí)行敲才,任務(wù)3也是同樣裹纳,而任務(wù)4需要等待任務(wù)3完成后才可以開始執(zhí)行择葡,所以任務(wù)34是串行執(zhí)行的,任務(wù)5是獨立的沒有任何依賴痊夭,所以任務(wù)5與其他任務(wù)并行執(zhí)行刁岸,輸出結(jié)果就不給出了,我們還可以根據(jù)業(yè)務(wù)的不同設(shè)置不同的更復(fù)雜的依賴她我。
自定義NSOperation子類
經(jīng)過前文的講解虹曙,關(guān)于NSOperation
和NSOperationQueue
的基礎(chǔ)使用已經(jīng)有了一個初步的掌握,如果我們?nèi)ラ喿xAFNetworking
或SDWebImage
的源碼時可以發(fā)現(xiàn)番舆,這些庫中大量用了NSOperation
和NSOperationQueue
酝碳,當(dāng)然也用了GCD
,比如SDWebImage
下載圖片的任務(wù)是自定義的NSOperation
子類SDWebImageDownloaderOperation
恨狈,之所以選擇使用自定義子類疏哗,正是因為自定義子類可以提供更多定制化的方法,而不僅僅局限于一個block
或一個方法禾怠,接下來將講解具體的自定義實現(xiàn)方法返奉。
在官方文檔中指出經(jīng)自定義NSOperation
子類有兩種形式,并發(fā)和非并發(fā)吗氏,非并發(fā)形式只需要繼承NSOperation
類后實現(xiàn)main
方法即可芽偏,而并發(fā)形式就比較復(fù)雜了,接下來會分別介紹兩種形式弦讽。
非并發(fā)的NSOperation自定義子類
官方文檔中有說明污尉,非并發(fā)的自定義子類只需要實現(xiàn)main
方法即可,栗子如下:
@interface TestOperation: NSOperation
@property (nonatomic, copy) id obj;
- (instancetype)initWithObject:(id)obj;
@end
@implementation TestOperation
- (instancetype)initWithObject:(id)obj
{
if (self = [super init])
{
self.obj = obj;
}
return self;
}
- (void)main
{
for (int i = 0; i < 100; i++)
{
NSLog(@"Task %@ %d %@", self.obj, i, [NSThread currentThread]);
}
NSLog(@"Task Complete!");
}
@end
- (void)viewWillAppear:(BOOL)animated
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:4];
TestOperation *operation = [[TestOperation alloc] initWithObject:@"Hello, World!"];
[operation main];
//[operation start];
//[queue addOperation:operation];
}
上述栗子也很簡單往产,就是自定義子類繼承了NSOperation
并且實現(xiàn)了main
方法被碗,在官方文檔中指出,非并發(fā)任務(wù)仿村,直接調(diào)用main
方法即可锐朴,調(diào)用之后就和調(diào)用普通對象的方法一樣,使用當(dāng)前線程來執(zhí)行main
方法蔼囊,在本栗中即主線程包颁,這個栗子沒有什么特別奇特的地方,但其實也可以將其加入到隊列中压真,但這樣存在一個問題,由于我們沒有實現(xiàn)finished
屬性蘑险,所以獲取finished
屬性時只會返回NO
滴肿,任務(wù)加入到隊列后不會被隊列刪除,一直會保存佃迄,而且任務(wù)執(zhí)行完成后的回調(diào)塊也不會執(zhí)行泼差,所以最好不要只實現(xiàn)一個main
方法就交給隊列去執(zhí)行贵少,即使我們沒有實現(xiàn)start
方法,這里調(diào)用start
方法以后依舊會執(zhí)行main
方法堆缘。這個非并發(fā)版本不建議寫滔灶,好像也沒有什么場景需要這樣寫,反而更加復(fù)雜吼肥,如果不小心加入到隊列中還會產(chǎn)生未知的錯誤录平。
并發(fā)的NSOperation自定義子類
關(guān)于并發(fā)的NSOperation
自定義子類就比較復(fù)雜了,但可以提供更高的可定制性缀皱,這也是為什么SDWebImage
使用自定義子類來實現(xiàn)下載任務(wù)斗这。
按照官方文檔的要求,實現(xiàn)并發(fā)的自定義子類需要重寫以下幾個方法或?qū)傩?
start方法: 任務(wù)加入到隊列后啤斗,隊列會管理任務(wù)并在線程被調(diào)度后適時調(diào)用start方法表箭,start方法就是我們編寫的任務(wù),需要注意的是钮莲,不論怎樣都不允許調(diào)用父類的start方法
isExecuting: 任務(wù)是否正在執(zhí)行免钻,需要手動調(diào)用KVO方法來進行通知,這樣崔拥,其他類如果監(jiān)聽了任務(wù)的該屬性就可以獲取到通知
isFinished: 任務(wù)是否結(jié)束极舔,需要手動調(diào)用KVO方法來進行通知,隊列也需要監(jiān)聽該屬性的值握童,用于判斷任務(wù)是否結(jié)束姆怪,由于我們編寫的任務(wù)很可能是異步的,所以start方法返回也不一定代表任務(wù)就結(jié)束了澡绩,任務(wù)結(jié)束需要開發(fā)者手動修改該屬性的值稽揭,隊列就可以正常的移除任務(wù)
isAsynchronous: 是否并發(fā)執(zhí)行,之前需要使用isConcurrent肥卡,但isConcurrent被廢棄了溪掀,該屬性標(biāo)識是否并發(fā)
直接看栗子吧:
@interface MyOperation: NSOperation
@property (nonatomic, assign, getter=isExecuting) BOOL executing;
@property (nonatomic, assign, getter=isFinished) BOOL finished;
@end
@implementation MyOperation
@synthesize executing = _executing;
@synthesize finished = _finished;
- (void)start
{
//在任務(wù)開始前設(shè)置executing為YES,在此之前可能會進行一些初始化操作
self.executing = YES;
for (int i = 0; i < 500; i++)
{
/*
需要在適當(dāng)?shù)奈恢门袛嗤獠渴欠裾{(diào)用了cancel方法
如果被cancel了需要正確的結(jié)束任務(wù)
*/
if (self.isCancelled)
{
//任務(wù)被取消正確結(jié)束前手動設(shè)置狀態(tài)
self.executing = NO;
self.finished = YES;
return;
}
//輸出任務(wù)的各個狀態(tài)以及隊列的任務(wù)數(shù)
NSLog(@"Task %d %@ Cancel:%d Executing:%d Finished:%d QueueOperationCount:%ld", i, [NSThread currentThread], self.cancelled, self.executing, self.finished, [[NSOperationQueue currentQueue] operationCount]);
[NSThread sleepForTimeInterval:0.1];
}
NSLog(@"Task Complete.");
//任務(wù)執(zhí)行完成后手動設(shè)置狀態(tài)
self.executing = NO;
self.finished = YES;
}
- (void)setExecuting:(BOOL)executing
{
//調(diào)用KVO通知
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
//調(diào)用KVO通知
[self didChangeValueForKey:@"isExecuting"];
}
- (BOOL)isExecuting
{
return _executing;
}
- (void)setFinished:(BOOL)finished
{
//調(diào)用KVO通知
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
//調(diào)用KVO通知
[self didChangeValueForKey:@"isFinished"];
}
- (BOOL)isFinished
{
return _finished;
}
- (BOOL)isAsynchronous
{
return YES;
}
@end
- (void)cancelButtonClicked
{
[self.myOperation cancel];
}
- (void)btnClicked
{
NSLog(@"MyOperation Cancel:%d Executing:%d Finished:%d QueueOperationCount:%ld", self.myOperation.isCancelled, self.myOperation.isExecuting, self.myOperation.isFinished, self.queue.operationCount);
}
- (void)viewWillAppear:(BOOL)animated
{
self.queue = [[NSOperationQueue alloc] init];
[self.queue setMaxConcurrentOperationCount:1];
self.myOperation = [[MyOperation alloc] init];
[self.queue addOperation:self.myOperation];
}
上面的栗子也比較簡單步鉴,各個狀態(tài)需要根據(jù)業(yè)務(wù)邏輯來設(shè)置揪胃,需要注意的是,一定要正確的設(shè)置各個狀態(tài)氛琢,并且在設(shè)置狀態(tài)時需要手動觸發(fā)KVO
進行通知喊递,因為可能有其他對象在監(jiān)聽任務(wù)的某一個狀態(tài),比如finished
屬性阳似,隊列就會監(jiān)聽任務(wù)的屬性骚勘,start
方法內(nèi)部很可能會有異步方法的執(zhí)行,所以start
方法返回并不代表任務(wù)結(jié)束,隊列不能根據(jù)start
方法是否返回來判斷任務(wù)是否結(jié)束俏讹,所以需要開發(fā)者手動修改相關(guān)屬性并觸發(fā)KVO
通知当宴。
上述栗子的輸出如下:
//任務(wù)的輸出內(nèi)容
Task 95 <NSThread: 0x1c027c780>{number = 3, name = (null)} Cancel:0 Executing:1 Finished:0 QueueOperationCount:1
//任務(wù)正在執(zhí)行的時候,點擊按鈕的輸出
MyOperation Cancel:0 Executing:1 Finished:0 QueueOperationCount:1
//當(dāng)任務(wù)執(zhí)行完成后泽疆,點擊按鈕的輸出
MyOperation Cancel:0 Executing:0 Finished:1 QueueOperationCount:0
從輸出中可以看到任務(wù)和執(zhí)行隊列的相關(guān)屬性的變化户矢。
接下來舉一個下載文件的栗子,使用自定義的NSOperation
子類,提供了取消下載的功能殉疼,具體代碼如下:
//FileDownloadOperation.h文件代碼
#ifndef FileDownloadOperation_h
#define FileDownloadOperation_h
#import <Foundation/Foundation.h>
@class FileDownloadOperation;
//定義一個協(xié)議梯浪,用于反饋下載狀態(tài)
@protocol FileDownloadDelegate <NSObject>
@optional
- (void)fileDownloadOperation:(FileDownloadOperation *)downloadOperation downloadProgress:(double)progress;
- (void)fileDownloadOperation:(FileDownloadOperation *)downloadOperation didFinishWithData:(NSData *)data;
- (void)fileDownloadOperation:(FileDownloadOperation *)downloadOperation didFailWithError:(NSError *)error;
@end
@interface FileDownloadOperation: NSOperation
//定義代理對象
@property (nonatomic, weak) id<FileDownloadDelegate> delegate;
//初始化構(gòu)造函數(shù),文件URL
- (instancetype)initWithURL:(NSURL*)url;
@end
#endif /* FileDownloadOperation_h */
FileDownloadOperation.m
文件源碼如下:
#import "FileDownloadOperation.h"
@interface FileDownloadOperation() <NSURLConnectionDelegate>
//定義executing屬性
@property (nonatomic, assign, getter=isExecuting) BOOL executing;
//定義finished屬性
@property (nonatomic, assign, getter=isFinished) BOOL finished;
//要下載的文件的URL
@property (nonatomic, strong) NSURL *fileURL;
//使用NSURLConnection進行網(wǎng)絡(luò)數(shù)據(jù)的獲取
@property (nonatomic, strong) NSURLConnection *connection;
//定義一個可變的NSMutableData對象株依,用于添加獲取的數(shù)據(jù)
@property (nonatomic, strong) NSMutableData *fileMutableData;
//記錄要下載文件的總長度
@property (nonatomic, assign) NSUInteger fileTotalLength;
//記錄已經(jīng)下載了的文件的長度
@property (nonatomic, assign) NSUInteger downloadedLength;
@end
@implementation FileDownloadOperation
@synthesize delegate = _delegate;
@synthesize executing = _executing;
@synthesize finished = _finished;
@synthesize fileURL = _fileURL;
@synthesize connection = _connection;
@synthesize fileMutableData = _fileMutableData;
@synthesize fileTotalLength = _fileTotalLength;
@synthesize downloadedLength = _downloadedLength;
//executing屬性的setter
- (void)setExecuting:(BOOL)executing
{
//設(shè)置executing屬性需要手動觸發(fā)KVO方法進行通知
[self willChangeValueForKey:@"executing"];
_executing = executing;
[self didChangeValueForKey:@"executing"];
}
//executing屬性的getter
- (BOOL)isExecuting
{
return _executing;
}
//finished屬性的setter
- (void)setFinished:(BOOL)finished
{
//同上驱证,需要手動觸發(fā)KVO方法進行通知
[self willChangeValueForKey:@"finished"];
_finished = finished;
[self didChangeValueForKey:@"finished"];
}
//finished屬性的getter
- (BOOL)isFinished
{
return _finished;
}
//返回YES標(biāo)識為并發(fā)Operation
- (BOOL)isAsynchronous
{
return YES;
}
//內(nèi)部函數(shù),用于結(jié)束任務(wù)
- (void)finishTask
{
//中斷網(wǎng)絡(luò)連接
[self.connection cancel];
//設(shè)置finished屬性為YES恋腕,將任務(wù)從隊列中移除
//會調(diào)用setter方法抹锄,并觸發(fā)KVO方法進行通知
self.finished = YES;
//設(shè)置executing屬性為NO
self.executing = NO;
}
//初始化構(gòu)造函數(shù)
- (instancetype)initWithURL:(NSURL *)url
{
if (self = [super init])
{
self.fileURL = url;
self.fileMutableData = [[NSMutableData alloc] init];
self.fileTotalLength = 0;
self.downloadedLength = 0;
}
return self;
}
//重寫start方法
- (void)start
{
//任務(wù)開始執(zhí)行前檢查是否被取消,取消就結(jié)束任務(wù)
if (self.isCancelled)
{
[self finishTask];
return;
}
//構(gòu)造NSURLConnection對象荠藤,并設(shè)置不立即開始伙单,手動開始
self.connection = [[NSURLConnection alloc] initWithRequest:[[NSURLRequest alloc] initWithURL:self.fileURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:25] delegate:self startImmediately:NO];
//判斷是否連接,沒有連接就結(jié)束任務(wù)
if (self.connection == nil)
{
[self finishTask];
return;
}
//成功連接到服務(wù)器后檢查是否取消任務(wù)哈肖,取消任務(wù)就結(jié)束
if (self.isCancelled)
{
[self finishTask];
return;
}
//設(shè)置任務(wù)開始執(zhí)行
self.executing = YES;
//獲取當(dāng)前RunLoop
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
//將任務(wù)交由RunLoop規(guī)劃
[self.connection scheduleInRunLoop:currentRunLoop forMode:NSRunLoopCommonModes];
//開始從服務(wù)端獲取數(shù)據(jù)
[self.connection start];
//判斷執(zhí)行任務(wù)的是否為主線程
if (currentRunLoop != [NSRunLoop mainRunLoop])
{
//不為主線程啟動RunLoop
CFRunLoopRun();
}
}
//MARK - NSURLConnectionDelegate 方法
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
//獲取并設(shè)置將要下載文件的長度大小
self.fileTotalLength = response.expectedContentLength;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
//網(wǎng)絡(luò)獲取失敗吻育,調(diào)用代理方法
if ([self.delegate respondsToSelector:@selector(fileDownloadOperation:didFailWithError:)])
{
//需要將代理方法放到主線程中執(zhí)行,防止代理方法需要修改UI
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate fileDownloadOperation:self didFailWithError:error];
});
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
//收到數(shù)據(jù)包后判斷任務(wù)是否取消淤井,取消則結(jié)束任務(wù)
if (self.isCancelled)
{
[self finishTask];
return;
}
//添加獲取的數(shù)據(jù)
[self.fileMutableData appendData:data];
//修改已下載文件長度
self.downloadedLength += [data length];
//調(diào)用回調(diào)函數(shù)
if ([self.delegate respondsToSelector:@selector(fileDownloadOperation:downloadProgress:)])
{
//計算下載比例
double progress = self.downloadedLength * 1.0 / self.fileTotalLength;
//同上布疼,放在主線程中調(diào)用,防止主線程有修改UI的操作
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate fileDownloadOperation:self downloadProgress:progress];
});
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//網(wǎng)絡(luò)下載完成前檢查是否取消任務(wù)币狠,取消就結(jié)束任務(wù)
if (self.isCancelled)
{
[self finishTask];
return;
}
//調(diào)用回調(diào)函數(shù)
if ([self.delegate respondsToSelector:@selector(fileDownloadOperation:didFinishWithData:)])
{
//同理游两,放在主線程中調(diào)用
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate fileDownloadOperation:self didFinishWithData:self.fileMutableData];
});
}
//下載完成,任務(wù)結(jié)束
[self finishTask];
}
@end
上述代碼的注釋很詳盡漩绵,就不再贅述了贱案,只提供了取消下載的功能,還可以添加暫停和斷點下載的功能止吐,讀者可自行實現(xiàn)宝踪。具體效果如下圖,點擊取消后就會結(jié)束任務(wù):
備注
由于作者水平有限碍扔,難免出現(xiàn)紕漏瘩燥,如有問題還請不吝賜教。