簡單的整理了一下,多線程的創(chuàng)建方式巧颈,它們之間的優(yōu)缺點胚嘲,以及在項目中我們在什么樣的場景下選擇哪一種方式。水平有限洛二,寫得比較淺顯易懂馋劈,太深入的可以自己去學習、去鉆研晾嘶,沒有什么是學不會的妓雾,前提只要你肯學。廢話不多說垒迂,let's goP狄觥!机断!
創(chuàng)建線程的的方案有pthread楷拳,NSThread,GCD吏奸,NSOperation欢揖,那么我就依次說一下每種方案有什么優(yōu)缺點,及它們是怎樣創(chuàng)建線程的奋蔚。
1.0 pthread
a. 簡介
pthread(POSIX thread)表示跨平臺的的線程接口她混,適用于Unix\Linux\Windows等系統(tǒng),是跨平臺和可移植的泊碑,使用的語言是C語言坤按,線程的生命周期是由程序員管理的,適用難度很大馒过,所以幾乎是不用的臭脓。
b. 創(chuàng)建子線程
所用函數(shù):
int pthread_create(pthread_t * __restrict, const pthread_attr_t * __restrict,
void *(*)(void *), void * __restrict);
參數(shù):
參數(shù)1:線程的編號(地址)
參數(shù)2:線程屬性,傳入指向線程屬性的指針地址。
參數(shù)3:新線程要執(zhí)行的函數(shù)(任務)腹忽,傳入函數(shù)地址来累,即函數(shù)名。
參數(shù)4: 給調(diào)用用的函數(shù)傳遞的參數(shù)留凭。
返回值:
返回int類型的值佃扼,0表示創(chuàng)建新線程成功偎巢,反之蔼夜,創(chuàng)建新線程失敗,返回失敗的編號。
很多C語言框架里面并不是非零即真原則压昼;因為他們認為成功的結(jié)果只有一個求冷,但是失敗的原因有很多瘤运。
demo
- (void)pthreadDemo {
pthread_t ID;
int returnValue = pthread_create( &ID, NULL, pthreadAction, NULL);
if (returnValue == 0) {
NSLog(@"線程創(chuàng)建成功");
} else {
NSLog(@"線程創(chuàng)建失敗");
}
}
void *pthreadAction(void *param) {
NSLog(@"%@",[NSThread currentThread]);
return NULL;
}
打印結(jié)果
2018-10-18 11:09:41.738469+0800 多線程Demo[8228:2385086] 線程創(chuàng)建成功
2018-10-18 11:09:41.738770+0800 多線程Demo[8228:2385731] <NSThread: 0x600000268840>{number = 3, name = (null)}
2018-10-18 11:09:41.903999+0800 多線程Demo[8228:2385086] 線程創(chuàng)建成功
2018-10-18 11:09:41.904245+0800 多線程Demo[8228:2385735] <NSThread: 0x600000260bc0>{number = 4, name = (null)}
從上述結(jié)果看出,創(chuàng)建新線程成功匠题。
c.還有一點我們需要注意的是__bridge使用拯坟,例:
NSString *ocString = @"tianyao";
int returnValue = pthread_create( &ID, NULL, pthreadAction, (__bridge void *)(ocString));
在混合開發(fā)的時候,在C和OC或者swift之間傳遞數(shù)據(jù)韭山,需要使用__bridge進行橋接郁季,它的目的就是告訴編譯器怎樣管理內(nèi)存。因為在ARC開發(fā)模式下钱磅,OC和swift編譯器在編譯的時候梦裂,根據(jù)代碼的結(jié)構(gòu),自動的添加retain/release/autorelease盖淡。但是年柠,ARC 只負責管理 OC 部分的內(nèi)存管理,而不負責C語言代碼的內(nèi)存管理褪迟。因此冗恨,如果使用的 C 語言框架出現(xiàn) retain/create/copy/new 等字樣的函數(shù),大多都需要 release味赃,否則會出現(xiàn)內(nèi)存泄漏掀抹。
像上述代碼,在c的函數(shù)里面?zhèn)鬟fOC的字符串心俗,就需要用到__bridge渴丸,因為c的內(nèi)存需要手動釋放,而OC的出了作用域就會自動釋放另凌,這樣就會出問題谱轨,所以使用__bridge就是告訴編譯器C函數(shù)中的OC代碼的內(nèi)存管理交給C處理了,你不用管了吠谢。
2.0 NSThread
a. 簡介
NSThread的使用更加面向?qū)ο笸镣唵我子茫梢灾苯硬僮骶€程對象工坊,使用的語言是OC献汗,但是線程的生命周期是由程序員自行管理的,所以偶爾會使用王污。
b. 創(chuàng)建線程的三種方式
- 對象方法創(chuàng)建
手動開啟線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"tianyao"]
// 手動開啟線程
[thread start]
- 類方法創(chuàng)建
自動開啟線程罢吃,這樣的話就無法獲取線程對象
[NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"tianyao"];
- NSObject(NSThreadPerformAdditions) 的分類創(chuàng)建
方便任何繼承自NSObject的對象,都可以很容易的調(diào)用線程方法昭齐,也是無法獲取線程對象
[self performSelectorInBackground:@selector(demo:) withObject:@"tianyao"];
c. 線程屬性
- name: 線程名稱
當線程執(zhí)行的方法的內(nèi)部出現(xiàn)異常時尿招,可以記錄當前和異常的線程。 - stackSize:棧區(qū)大小
可以通過[NSThread currentThread].stackSize = 512 * 1024設置棧區(qū)的大小(必須是4KB的倍數(shù))就谜。 - isMainThread: 是否主線程
- threadPriority: 線程優(yōu)先級
它是一個浮點數(shù)怪蔑,范圍是0~1.0,1.0表示最高優(yōu)先級丧荐,0.0表示最低優(yōu)先級缆瓣,系統(tǒng)默認的優(yōu)先級是0.5,線程的優(yōu)先級并不能絕對的保證優(yōu)先級高的線程執(zhí)行完所有的代碼虹统,它只是提高了優(yōu)先級高的代碼執(zhí)行代碼的概率弓坞。 - qualityOfService:服務質(zhì)量(IOS8推出)
NSQualityOfServiceUserInteractive - 用戶交互,例如繪圖或者處理用戶事件
NSQualityOfServiceUserInitiated - 用戶需要
NSQualityOfServiceUtility - 實用工具车荔,用戶不需要立即得到結(jié)果
NSQualityOfServiceBackground - 后臺
NSQualityOfServiceDefault - 默認昼丑,介于用戶需要和實用工具之間
注意
開發(fā)中最好不好修改優(yōu)先級,不要相信用戶交互服務質(zhì)量
3.0 GCD
a. 簡介(IOS4.0后推出)
GCD(Grand Central Dispatch)是蘋果公司為多核的并行運算提出的解決方案夸赫,會自動利用更多的CPU內(nèi)核(比如雙核菩帝、四核),是純C語言的茬腿,提供了很多強大的函數(shù)呼奢,能夠自動的管理線程的生命周期(創(chuàng)建線程、調(diào)度任務切平、銷毀線程)握础,在平時的工作中經(jīng)常使用。
b. GCD核心
GCD的核心就是將任務添加到隊列中悴品。
- 隊列
串行隊列:任務按順序有序的執(zhí)行禀综。就像單行車道,車只能一輛一輛的按照順序行駛
并發(fā)隊列:可以讓多個任務并發(fā)執(zhí)行苔严。那么并發(fā)隊列就可類比于多行車道定枷。 - 任務
任務的執(zhí)行可分為兩種方式
同步執(zhí)行:在當前線程中依次執(zhí)行任務。
異步執(zhí)行:創(chuàng)建一個新線程來執(zhí)行任務届氢。
c. 串行隊列
串行同步:
- (void)serialSyncDemo {
// 串行隊列
/*
參數(shù)1:隊列名稱
參數(shù)2:隊列類型
*/
dispatch_queue_t serialQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_SERIAL);
for(int i = 0; i < 5; i++){
dispatch_sync(serialQueue, ^{
NSLog(@"%d %@", i, [NSThread currentThread]);
});
}
}
打印結(jié)果:
2018-10-18 15:58:53.085081+0800 多線程Demo[8994:2663401] 0 <NSThread: 0x60400006cd80>{number = 1, name = main}
2018-10-18 15:58:53.085400+0800 多線程Demo[8994:2663401] 1 <NSThread: 0x60400006cd80>{number = 1, name = main}
2018-10-18 15:58:53.086193+0800 多線程Demo[8994:2663401] 2 <NSThread: 0x60400006cd80>{number = 1, name = main}
2018-10-18 15:58:53.086402+0800 多線程Demo[8994:2663401] 3 <NSThread: 0x60400006cd80>{number = 1, name = main}
2018-10-18 15:58:53.086700+0800 多線程Demo[8994:2663401] 4 <NSThread: 0x60400006cd80>{number = 1, name = main}
得出結(jié)論:串行同步不具備開啟新線程的能力欠窒,任務按照順序執(zhí)行。
串行異步:
- (void)serialAsyncDemo {
dispatch_queue_t serialQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT);
for(int i = 0; i < 5; i++){
dispatch_async(serialQueue, ^{
NSLog(@"%d %@", i, [NSThread currentThread]);
});
}
}
打印結(jié)果:
2018-10-18 16:13:58.676331+0800 多線程Demo[9064:2685387] 0 <NSThread: 0x60000047ac80>{number = 3, name = (null)}
2018-10-18 16:13:58.678881+0800 多線程Demo[9064:2685387] 1 <NSThread: 0x60000047ac80>{number = 3, name = (null)}
2018-10-18 16:13:58.679375+0800 多線程Demo[9064:2685387] 2 <NSThread: 0x60000047ac80>{number = 3, name = (null)}
2018-10-18 16:13:58.679847+0800 多線程Demo[9064:2685387] 3 <NSThread: 0x60000047ac80>{number = 3, name = (null)}
2018-10-18 16:13:58.680204+0800 多線程Demo[9064:2685387] 4 <NSThread: 0x60000047ac80>{number = 3, name = (null)}
得出結(jié)論:串行異步會開啟一條子線程退子,任務一次執(zhí)行岖妄。
d. 并行隊列
并行同步:
- (void)concurrentSyncDemo {
dispatch_queue_t concurrentQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT);
for(int i = 0; i < 5; i++){
dispatch_sync(concurrentQueue, ^{
NSLog(@"%d %@", i, [NSThread currentThread]);
});
}
}
打印結(jié)果:
2018-10-18 16:43:00.811969+0800 多線程Demo[9161:2713449] 0 <NSThread: 0x60000007fac0>{number = 1, name = main}
2018-10-18 16:43:00.812157+0800 多線程Demo[9161:2713449] 1 <NSThread: 0x60000007fac0>{number = 1, name = main}
2018-10-18 16:43:00.812328+0800 多線程Demo[9161:2713449] 2 <NSThread: 0x60000007fac0>{number = 1, name = main}
2018-10-18 16:43:00.812867+0800 多線程Demo[9161:2713449] 3 <NSThread: 0x60000007fac0>{number = 1, name = main}
2018-10-18 16:43:00.813012+0800 多線程Demo[9161:2713449] 4 <NSThread: 0x60000007fac0>{number = 1, name = main}
得出結(jié)論:并行同步不會開啟新線程,任務在當前線程依次執(zhí)行寂祥。
并行異步:
- (void)concurrentAsyncDemo {
dispatch_queue_t concurrentQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT);
for(int i = 0; i < 5; i++){
dispatch_async(concurrentQueue, ^{
NSLog(@"%d %@", i, [NSThread currentThread]);
});
}
}
打印結(jié)果:
2018-10-18 17:02:30.044475+0800 多線程Demo[9208:2730824] 2 <NSThread: 0x604000462380>{number = 4, name = (null)}
2018-10-18 17:02:30.044706+0800 多線程Demo[9208:2730823] 1 <NSThread: 0x604000462000>{number = 5, name = (null)}
2018-10-18 17:02:30.044829+0800 多線程Demo[9208:2730825] 0 <NSThread: 0x60000027c440>{number = 3, name = (null)}
2018-10-18 17:02:30.045005+0800 多線程Demo[9208:2730822] 3 <NSThread: 0x60000027f140>{number = 6, name = (null)}
2018-10-18 17:02:30.045009+0800 多線程Demo[9208:2730834] 4 <NSThread: 0x60000027f400>{number = 7, name = (null)}
得出結(jié)論:并行異步會開啟多條線程荐虐,任務不按順序執(zhí)行。
e. 主隊列
主隊列是專門在主線程上面調(diào)度任務的隊列丸凭,會隨著程序啟動一起創(chuàng)建福扬,不會開啟新的線程腕铸,以先進先出的方式執(zhí)行任務
主隊列只有當主線程空閑的時候才會調(diào)度任務,也就是說當主線程有任務正在執(zhí)行時忧换,無論主隊列被添加了任何任務都不會被執(zhí)行恬惯。
主隊列同步
- (void)mainSyncDemo {
dispatch_queue_t mainQueue = dispatch_get_main_queue();
NSLog(@"start");
dispatch_sync(mainQueue, ^{
NSLog(@"%@", [NSThread currentThread]);
});
NSLog(@"end");
}
執(zhí)行上面的代碼會造成死鎖向拆,程序崩潰亚茬。因為主隊列上面的代碼只有當主線程空閑的時候才會執(zhí)行,又因為是同步浓恳,代碼按順序執(zhí)行刹缝,當主線程執(zhí)行到主隊列的代碼的時候,主線程此時不是處于空閑的狀態(tài)颈将,所以沒法執(zhí)行主隊列的代碼梢夯,這就造成了主隊列和主線程之間的相互等待,造成死鎖晴圾。
解決死鎖的辦法: 就是將主隊列的代碼放到子線程中颂砸,不讓其阻礙主線程的執(zhí)行,這樣等主線程空閑下來的時候死姚,就可以去執(zhí)行主隊列上面的代碼人乓。
- (void)mainSyncDemo {
dispatch_queue_t mainQueue = dispatch_get_main_queue();
NSLog(@"start");
dispatch_async(dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT), ^{
dispatch_sync(mainQueue, ^{
NSLog(@"%@", [NSThread currentThread]);
});
});
NSLog(@"end");
}
打印結(jié)果:
2018-10-18 18:03:12.375942+0800 多線程Demo[9377:2796585] start
2018-10-18 18:03:12.376192+0800 多線程Demo[9377:2796585] end
2018-10-18 18:03:12.376542+0800 多線程Demo[9377:2796585] <NSThread: 0x60400006acc0>{number = 1, name = main}
主隊列異步
- (void)mainAsyncDemo {
dispatch_queue_t mainQueue = dispatch_get_main_queue();
NSLog(@"start");
dispatch_async(mainQueue, ^{
NSLog(@"%@", [NSThread currentThread]);
});
NSLog(@"end");
}
打印結(jié)果:
2018-10-18 18:10:35.030586+0800 多線程Demo[9435:2808199] start
2018-10-18 18:10:35.030802+0800 多線程Demo[9435:2808199] end
2018-10-18 18:10:35.031140+0800 多線程Demo[9435:2808199] <NSThread: 0x60000006ac40>{number = 1, name = main}
得出結(jié)論:主線程異步不會創(chuàng)建新的線程,任務在主線程上面依次執(zhí)行都毒。
f. 全局隊列
全局隊列又叫全局并發(fā)隊列色罚,是系統(tǒng)為了方便程序員開發(fā)提供的,其工作狀態(tài)與并發(fā)隊列一致账劲,無論 MRC & ARC 都不需要考慮釋放戳护。
全局隊列同步:
- (void)globalSync {
/*
參數(shù)1:A quality of service 服務質(zhì)量
iOS 8.0及以后
QOS_CLASS_USER_INTERACTIVE 0x21, 用戶交互(希望最快完成-不能用太耗時的操作)
QOS_CLASS_USER_INITIATED 0x19, 用戶期望(希望快,也不能太耗時)
QOS_CLASS_DEFAULT 0x15, 默認(用來底層重置隊列使用的瀑焦,不是給程序員用的)
QOS_CLASS_UTILITY 0x11, 實用工具(專門用來處理耗時操作腌且!)
QOS_CLASS_BACKGROUND 0x09, 后臺
QOS_CLASS_UNSPECIFIED 0x00, 未指定,可以和iOS 7.0 適配
iOS 7.0及以前
DISPATCH_QUEUE_PRIORITY_HIGH 2 高優(yōu)先級
DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默認優(yōu)先級
DISPATCH_QUEUE_PRIORITY_LOW (-2) 低優(yōu)先級
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后臺優(yōu)先級
參數(shù)2:Reserved for future use 未來使用:為未來保留使用的榛瓮,應該永遠傳入0
*/
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
for(int i = 0; i < 5; i++){
dispatch_sync(globalQueue, ^{
NSLog(@"%d %@", i, [NSThread currentThread]);
});
}
}
打印結(jié)果:
2018-10-19 10:56:41.119513+0800 多線程Demo[10930:3237013] 0 <NSThread: 0x600000064440>{number = 1, name = main}
2018-10-19 10:56:41.119773+0800 多線程Demo[10930:3237013] 1 <NSThread: 0x600000064440>{number = 1, name = main}
2018-10-19 10:56:41.120436+0800 多線程Demo[10930:3237013] 2 <NSThread: 0x600000064440>{number = 1, name = main}
2018-10-19 10:56:41.120701+0800 多線程Demo[10930:3237013] 3 <NSThread: 0x600000064440>{number = 1, name = main}
2018-10-19 10:56:41.120832+0800 多線程Demo[10930:3237013] 4 <NSThread: 0x600000064440>{number = 1, name = main}
得出結(jié)論:全局同步不會創(chuàng)建新的現(xiàn)場切蟋,程序在當前現(xiàn)場按順序執(zhí)行。
全局隊列異步:
- (void)globalAsync {
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
for(int i = 0; i < 5; i++){
dispatch_async(globalQueue, ^{
NSLog(@"%d %@", i, [NSThread currentThread]);
});
}
}
打印結(jié)果:
2018-10-19 10:58:54.635833+0800 多線程Demo[10964:3242388] 1 <NSThread: 0x604000460340>{number = 4, name = (null)}
2018-10-19 10:58:54.635987+0800 多線程Demo[10964:3242022] 0 <NSThread: 0x604000274f80>{number = 3, name = (null)}
2018-10-19 10:58:54.636380+0800 多線程Demo[10964:3242393] 2 <NSThread: 0x600000464b40>{number = 5, name = (null)}
2018-10-19 10:58:54.636981+0800 多線程Demo[10964:3242394] 3 <NSThread: 0x600000464bc0>{number = 7, name = (null)}
2018-10-19 10:58:54.637505+0800 多線程Demo[10964:3242395] 4 <NSThread: 0x60400027dc00>{number = 6, name = (null)}
得出結(jié)論:全局異步會創(chuàng)建多條線程榆芦,任務不按順序執(zhí)行柄粹。
g. GCD阻塞(Barrier)
應用場景:主要用于多個異步操作完成之后,統(tǒng)一對非線程安全的對象(例如:NSMutableArray匆绣,NSMutableDictionary等)做處理驻右。適用于大規(guī)模數(shù)據(jù)的I/O操作。舉例說明:
NSMutableArray *_imageArr = [NSMutableArray array];
- (void)barrierDemo {
dispatch_queue_t concurrentQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT);
for(int i = 0; i < 2500; i++){
dispatch_async(concurrentQueue, ^{
NSString *name = [NSString stringWithFormat:@"%02d.jpg",i%10 + 1];
NSURL *url = [[NSBundle mainBundle]URLForResource:name withExtension:nil];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *img = [UIImage imageWithData:data];
NSLog(@"%@ %@",name,[NSThread currentThread]);
[self.imageArr addObject:img];
});
}
執(zhí)行上述代碼程序會崩潰崎淳,原因是NSMutableArray是非線程安全的堪夭,如果出現(xiàn)兩個線程同時向數(shù)組中添加對象,程序就會崩潰。解決的方案如下:
- (void)barrierDemo {
dispatch_queue_t concurrentQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT);
for(int i = 0; i < 2500; i++){
dispatch_async(concurrentQueue, ^{
NSString *name = [NSString stringWithFormat:@"%02d.jpg",i%10 + 1];
NSURL *url = [[NSBundle mainBundle]URLForResource:name withExtension:nil];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *img = [UIImage imageWithData:data];
NSLog(@"%@ %@",name,[NSThread currentThread]);
dispatch_barrier_async(concurrentQueue, ^{
[self.imageArr addObject:img];
});
});
}
}
dispatch_barrier_async可以保證同一時間內(nèi)只有一條線程執(zhí)行block內(nèi)的代碼森爽,也就是說在其之前添加的block全部執(zhí)行完畢之后恨豁,才在同一個線程順序執(zhí)行,從而保證了非線程安全的對象的正確操作爬迟。
f. GCD延遲操作
dispatch_after這個函數(shù)默認是異步執(zhí)行的橘蜜。
- (void)afterDemo {
NSLog(@"start");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 執(zhí)行的任務
});
NSLog(@"end");
// 拆分
/*
參數(shù)1 : dispatch_time_t when,表示延遲的時間
參數(shù)2 : dispatch_queue_t queue,表示任務執(zhí)行的隊列
參數(shù)3 : dispatch_block_t block,表示線程要執(zhí)行的任務
*/
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_block_t block = ^ {
// 執(zhí)行的任務
};
dispatch_after(time, queue, block);
}
上述代碼dispatch_after里面的代碼延遲一秒執(zhí)行。
g. GCD一次執(zhí)行
GCD的一次執(zhí)行運用最多的場景就是創(chuàng)建單例付呕,核心就是dispatch_once_t计福,它的里面有一把鎖,能夠保證線程的安全徽职。
蘋果公司推薦使用
單例:
一個應用程序里面有且只有一個這樣的實例對象象颖,例如:網(wǎng)絡請求,音樂播放器等姆钉。單例一旦創(chuàng)建就會一直存在说订,直到app退出,單例存在靜態(tài)區(qū)潮瓶,所以不能濫用陶冷。
向我們應用程序中經(jīng)常使用的好多,也都是單例筋讨。例如:
[NSNotificationCenter defaultCenter];
[NSUserDefaults standardUserDefaults];
[UIApplication sharedApplication];
[NSFileManager defaultManager];
如果不使用GCD的話埃叭,也可以這樣創(chuàng)建單例,使用互斥鎖
+ (instancetype)sharedTool {
// 添加互斥鎖
@synchronized (self) {
if (instance == nil) {
instance = [[self alloc] init];
}
}
return instance;
}
開發(fā)中一般不使用這種方式悉罕,因為互斥鎖使用的是線程同步的原理赤屋,線程之間需要等待,相比dispatch_once效率不高壁袄。
懶漢式和餓漢式單例
static id instance;
// 懶漢式單例:使用時才會創(chuàng)建
+ (instancetype)sharedTool {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
// 餓漢式單例:重寫initialize這個類方法类早,在類第一次使用的時候就會創(chuàng)建
// initialize會在類第一次被使用時調(diào)用, 且調(diào)用是線程安全的
+ (void)initialize {
instance = [[self alloc] init];
}
+ (instancetype)sharedTool {
return instance;
}
有時候我們會看到有些代碼會重寫allocWithZone和copyWithZone。例如:
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
因為是擔心合作開發(fā)中嗜逻,別人會使用TYOnce *tools = [[TYOnce alloc] init]; [tools copy]這種方式來創(chuàng)建單例涩僻,但是一般正常的程序員都知道我們創(chuàng)建單例的方式都是使用TYOnce *tools = [TYOnce sharedTool];這種方式,所以這種顧慮基本上可以不用考慮栈顷。
h. 調(diào)度組
監(jiān)聽一組異步任務是否執(zhí)行結(jié)束逆日,在其執(zhí)行結(jié)束后得到統(tǒng)一的通知。例如:監(jiān)聽幾部電影同時下載:
- (void)groupDemo {
//調(diào)度組
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"下載第1部電影 %@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"下載第2部電影 %@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"下載第3部電影 %@",[NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"電影全部下載完成");
});
}
打印結(jié)果:
2018-10-19 15:20:07.473239+0800 05-調(diào)度組[11616:3513382] 下載第1首歌曲 <NSThread: 0x604000278b80>{number = 3, name = (null)}
2018-10-19 15:20:07.473239+0800 05-調(diào)度組[11616:3513384] 下載第3首歌曲 <NSThread: 0x60400027a980>{number = 4, name = (null)}
2018-10-19 15:20:07.474253+0800 05-調(diào)度組[11616:3513381] 下載第2首歌曲 <NSThread: 0x60400027be00>{number = 5, name = (null)}
2018-10-19 15:20:07.474631+0800 05-調(diào)度組[11616:3513290] 歌曲下載完成了
由打印的結(jié)果可以看出萄凤,任務是異步執(zhí)行的室抽,在三部電影全部下載完成后,可以得到統(tǒng)一的通知靡努。
調(diào)度組的執(zhí)行原理:實現(xiàn)監(jiān)聽一組異步任務是否執(zhí)行結(jié)束
/*
enter等于eave : 監(jiān)測成功
enter多于leave : 監(jiān)測失效
enter小于leave : 程序崩潰
*/
- (void)groupDemo {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"下載第1部電影 %@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0];
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"下載第2部電影 %@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"下載第3部電影 %@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"電影全部下載完成");
});
}
4.0 NSOperation
a. 簡介(IOS2.0后推出)
NSOperation是OC語言中基于GCD的面向?qū)ο蟮姆庋b坪圾,使用起來比GCD更加簡單(面向?qū)ο螅┫郏峁┝艘恍┯肎CD不好實現(xiàn)的功能(例如:添加依賴),能夠自動的管理線程的生命周期兽泄,在平時的工作中經(jīng)常使用漓概。
蘋果推薦使用
NSOperation是一個抽象類,所以無法直接使用病梢,因為它的方法只有聲明沒有實現(xiàn)胃珍。核心就是將操作添加到隊列當中。
使用時其實我們是對NSOperation子類的使用飘千,它的子類:
NSInvocationOperation;
NSBlockOperation;
// 自定義operation
NSOperation;
b. NSInvocationOperation的使用
- 試例一
- (void)operationDemo {
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo) object:nil];
[op start];
}
- (void)demo {
NSLog(@"%@", [NSThread currentThread]);
}
打印結(jié)果:
2018-10-19 16:25:58.252280+0800 多線程Demo[11856:3583208] <NSThread: 0x604000078680>{number = 1, name = main}
得出結(jié)論: [op start]方法堂鲜,會在當前線程執(zhí)行selector方法栈雳。
- 試例二
- (void)operationDemo {
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(demo) object:nil];
// 隊列: 并發(fā)隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// 把操作添加到隊列
[queue addOperation:op];
}
- (void)demo {
NSLog(@"%@", [NSThread currentThread]);
}
打印結(jié)果:
2018-10-19 16:32:03.564602+0800 多線程Demo[11895:3592479] <NSThread: 0x60400026dc00>{number = 3, name = (null)}
得出結(jié)論:將操作添加到隊列护奈,默認是異步執(zhí)行。
- 試例三:驗證隊列的并發(fā)性
- (void)operationDemo {
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
for(int i = 0;i< 5;i++){
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(demo) object:nil];
[queue addOperation:op];
}
}
- (void)demo {
NSLog(@"%@", [NSThread currentThread]);
}
打印結(jié)果:
2018-10-19 16:36:15.437686+0800 多線程Demo[11936:3600229] <NSThread: 0x60000026c7c0>{number = 6, name = (null)}
2018-10-19 16:36:15.437641+0800 多線程Demo[11936:3600228] <NSThread: 0x604000276c80>{number = 4, name = (null)}
2018-10-19 16:36:15.437744+0800 多線程Demo[11936:3600234] <NSThread: 0x60000026c400>{number = 3, name = (null)}
2018-10-19 16:36:15.437808+0800 多線程Demo[11936:3600232] <NSThread: 0x60000026c780>{number = 5, name = (null)}
2018-10-19 16:36:15.438371+0800 多線程Demo[11936:3600308] <NSThread: 0x604000279840>{number = 7, name = (null)}
得出結(jié)論:會開啟多條線程哥纫,不是順序執(zhí)行的霉旗。與GCD中并發(fā)異步執(zhí)行效果一樣。
c. NSBlockOperation的使用
- 試例一
- (void)blockDemo {
// 封裝NSOperation對象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[op start];
}
得出結(jié)論:
2018-10-19 17:12:50.353660+0800 多線程Demo[12060:3633014] <NSThread: 0x600000067e80>{number = 1, name = main}
得出結(jié)論: [op start]方法蛀骇,操作只在當前線程執(zhí)行厌秒。
- 試例二
- (void)blockDemo {
// 隊列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// 封裝NSOperation對象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
// 把操作添加到隊列
[queue addOperation:op];
}
打印結(jié)果:
2018-10-19 17:15:00.513886+0800 多線程Demo[12079:3637107] <NSThread: 0x60400027e5c0>{number = 3, name = (null)}
得出結(jié)論:將操作添加到隊列,操作默認是異步的擅憔。
- 試例三
- (void)blockDemo {
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
for(int i = 0;i< 5;i++){
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[queue addOperation:op];
}
}
打印結(jié)果:
2018-10-19 17:19:32.740483+0800 多線程Demo[12120:3644204] <NSThread: 0x6000002698c0>{number = 5, name = (null)}
2018-10-19 17:19:32.740487+0800 多線程Demo[12120:3644205] <NSThread: 0x604000269fc0>{number = 6, name = (null)}
2018-10-19 17:19:32.740483+0800 多線程Demo[12120:3644203] <NSThread: 0x60400026b800>{number = 4, name = (null)}
2018-10-19 17:19:32.740524+0800 多線程Demo[12120:3644201] <NSThread: 0x600000269d00>{number = 3, name = (null)}
2018-10-19 17:19:32.740646+0800 多線程Demo[12120:3644202] <NSThread: 0x604000269f80>{number = 7, name = (null)}
得出結(jié)論:隊列默認是并發(fā)性的鸵闪。
- 試例四
在實際開發(fā)時,如果要使用到NSOperationQueue,可以直接定義成全局的隊列
// 全局隊列
@property (nonatomic, strong) NSOperationQueue *queue;
- (NSOperationQueue *)queue {
if (_queue == nil) {
_queue = [[NSOperationQueue alloc]init];
}
return _queue;
}
- (void) blockDemo {
[self.queue addOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
}
- 試例五
添加操作服務質(zhì)量和監(jiān)聽操作執(zhí)行結(jié)束
操作服務質(zhì)量:解決隊列里面的操作有更多的機會被隊列調(diào)度執(zhí)行,類似于線程優(yōu)先級暑诸。
監(jiān)聽操作執(zhí)行結(jié)束:這個監(jiān)聽的回調(diào)是異步的蚌讼。
- (void)OperationDemo {
// 操作1
NSBlockOperation *firstOp = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 10; i++) {
NSLog(@"firstOp == %d %@",i,[NSThread currentThread]);
}
}];
// 監(jiān)聽操作1什么時候執(zhí)行結(jié)束 : 這個監(jiān)聽是異步監(jiān)聽
[firstOp setCompletionBlock:^{
NSLog(@"操作1執(zhí)行結(jié)束 == %@",[NSThread currentThread]);
}];
// 設置操作優(yōu)先級 : 設置為最高
firstOp.qualityOfService = NSQualityOfServiceUserInteractive;
[self.queue addOperation: firstOp];
// 操作2
NSBlockOperation *secondOp = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 10; i++) {
NSLog(@"secondOp == %d %@",i,[NSThread currentThread]);
}
}];
// 設置操作優(yōu)先級 : 設置為最低
secondOp.qualityOfService = NSQualityOfServiceBackground;
[self.queue addOperation: secondOp];
}
- 試例六
添加操作執(zhí)行塊
- (void)OperationDemo {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
// 添加操作的執(zhí)行塊 : 這個執(zhí)行塊也是屬于op對象的
// 當添加操作執(zhí)行塊后, 這個執(zhí)行塊里面的任務就新開子線程執(zhí)行
[op addExecutionBlock:^{
NSLog(@"ExecutionBlock %@",[NSThread currentThread]);
}];
// 在沒有添加執(zhí)行塊之前,這個操作是在當前線程執(zhí)行
[op start];
}
打印結(jié)果:
2018-10-22 10:18:26.121270+0800 多線程Demo[16173:4731928] ExecutionBlock <NSThread: 0x6000002745c0>{number = 3, name = (null)}
2018-10-22 10:18:26.121270+0800 多線程Demo[16173:4730187] <NSThread: 0x600000261dc0>{number = 1, name = main}
得出結(jié)論:由打印的結(jié)果可以看出執(zhí)行塊里面的任務開啟了新的線程執(zhí)行,在沒有添加執(zhí)行塊之前,這個操作是在當前線程執(zhí)行的个榕。
d. NSOperation的高級應用
- 隊列的最大并發(fā)數(shù)
self.queue.maxConcurrentOperationCount = 3;
設置最大并發(fā)數(shù)為三篡石,每次只能調(diào)度三個操作。
- 隊列的暫停西采、繼續(xù)和取消全部
// 隊列暫停
self.queue.suspended = YES;
只能夠暫停還沒有執(zhí)行的操作凰萨,正在執(zhí)行的操作沒有辦法暫停。如果先暫停隊列械馆,再添加操作到隊列,隊列不會調(diào)度操作執(zhí)行胖眷。
// 隊列繼續(xù)
self.queue.suspended = NO;
// 隊列全部取消
[self.queue cancelAllOperations];
這個方法只能夠取消還沒有執(zhí)行的操作,正在執(zhí)行的操作沒有辦法取消霹崎。如果要取消珊搀,需要自定義NSOperation。隊列取消全部操作時仿畸,會有一定的時間延遲食棕。
- 操作依賴
- (void)dependencyDemo {
// 登陸->付費->下載
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"登陸");
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"付費");
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載");
}];
// 設置操作依賴
[op3 addDependency:op2];
[op2 addDependency:op1];
// [op1 addDependency:op3]; 注意1:不要搞成循環(huán)依賴
// 不同的隊列之中的操作也可以設置操作依賴
[[NSOperationQueue mainQueue]addOperation:op1];
// 把操作添加到隊列
[self.queue addOperations:@[op2,op3] waitUntilFinished:NO];
}
很多時候我們希望自己所執(zhí)行的事情能按照順序執(zhí)行朗和,比如我們我們下載一首付費的歌曲,需要我們按順序執(zhí)行登錄-付費-下載的操作簿晓,由于這些操作我們放在后臺去執(zhí)行眶拉,所以它們執(zhí)行的順序是不確定的,看CPU怎么調(diào)度憔儿,所以我們?yōu)榱诉_到我們的要求忆植。就需要添加操作依賴來實現(xiàn)。
注意:
不能循環(huán)建立操作間依賴關(guān)系谒臼。否則朝刊,隊列不調(diào)度操作執(zhí)行。
操作間可以跨隊列建立依賴關(guān)系蜈缤。
要將操作間的依賴建立好了之后拾氓,再添加到隊列中。