IOS 多線程的四種創(chuàng)建方案及比較

簡單的整理了一下,多線程的創(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)系蜈缤。
要將操作間的依賴建立好了之后拾氓,再添加到隊列中。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末底哥,一起剝皮案震驚了整個濱河市咙鞍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌趾徽,老刑警劉巖续滋,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異孵奶,居然都是意外死亡疲酌,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門了袁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來朗恳,“玉大人,你說我怎么就攤上這事早像∑ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵卢鹦,是天一觀的道長臀脏。 經(jīng)常有香客問我,道長冀自,這世上最難降的妖魔是什么揉稚? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮熬粗,結(jié)果婚禮上搀玖,老公的妹妹穿的比我還像新娘。我一直安慰自己驻呐,他們只是感情好灌诅,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布芳来。 她就那樣靜靜地躺著,像睡著了一般猜拾。 火紅的嫁衣襯著肌膚如雪即舌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天挎袜,我揣著相機與錄音顽聂,去河邊找鬼。 笑死盯仪,一個胖子當著我的面吹牛紊搪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播全景,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼耀石,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蚪燕?” 一聲冷哼從身側(cè)響起娶牌,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤奔浅,失蹤者是張志新(化名)和其女友劉穎馆纳,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體汹桦,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡鲁驶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了舞骆。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钥弯。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖督禽,靈堂內(nèi)的尸體忽然破棺而出脆霎,到底是詐尸還是另有隱情,我是刑警寧澤狈惫,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布睛蛛,位于F島的核電站,受9級特大地震影響胧谈,放射性物質(zhì)發(fā)生泄漏忆肾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一菱肖、第九天 我趴在偏房一處隱蔽的房頂上張望客冈。 院中可真熱鬧,春花似錦稳强、人聲如沸场仲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽渠缕。三九已至摹闽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間褐健,已是汗流浹背付鹿。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蚜迅,地道東北人舵匾。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像谁不,于是被迫代替她去往敵國和親坐梯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容