iOS多線程詳解

前面已經(jīng)有一篇文章(學(xué)習(xí)GCD看我就夠了)專門介紹了GCD,下面來介紹一下另外三個與多線程相關(guān)的方法

一、pthreads(現(xiàn)在幾乎不用了)

pthread是POSIX thread的簡寫,一套通用的多線程API,適用于Unix、Linux、Windows等系統(tǒng),跨平臺、可移植,使用難度大,C語言框架,線程生命周期由程序員管理,由于iOS開發(fā)幾乎用不到,以下就簡單運(yùn)用pthread開啟一個子線程,用來處理耗時操作

// 創(chuàng)建線程,并且在線程中執(zhí)行 demo 函數(shù)
- (void)pthreadDemo {
     返回值:
     - 若線程創(chuàng)建成功,則返回0
     - 若線程創(chuàng)建失敗,則返回出錯編號
     */
    pthread_t threadId = NULL;
    NSString *str = @"Hello Pthread";
    // 這邊的demo函數(shù)名作為第三個參數(shù)寫在這里可以在其前面加一個&,也可以不加,因?yàn)楹瘮?shù)名就代表了函數(shù)的地址。
    int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(str));

    if (result == 0) {
        NSLog(@"創(chuàng)建線程 OK");
    } else {
        NSLog(@"創(chuàng)建線程失敗 %d", result);
    }
    // pthread_detach:設(shè)置子線程的狀態(tài)設(shè)置為detached,則該線程運(yùn)行結(jié)束后會自動釋放所有資源。
    pthread_detach(threadId);
}

// 后臺線程調(diào)用函數(shù)
void *demo(void *params) {
    NSString *str = (__bridge NSString *)(params);

    NSLog(@"%@ - %@", [NSThread currentThread], str);
    return NULL;
}
二、NSThread

NSThread是基于線程使用,輕量級的多線程編程方法(相對GCD和NSOperation),一個NSThread對象代表一個線程,需要手動管理線程的生命周期,處理線程同步等問題。

創(chuàng)建線程
  • 方法一
// 1. 創(chuàng)建線程
 NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test:) object:nil];
    thread.name = @"thread1"; //設(shè)置線程名
    [thread start];   // 2. 啟動線程,此方法需要我們手動開啟線程
- (void)test:(NSString *)string {
  NSLog(@"test - %@ - %@", [NSThread currentThread], string);
}

打印如下:

2017-09-29 10:28:44.203914+0800 aegewgr[9577:3142906] test - <NSThread: 0x600000461a40>{number = 3, name = thread1} - (null)

這里我們最好設(shè)置一下線程名,便于我們的調(diào)試

  • 方法二
[NSThread detachNewThreadSelector:@selector(test:) toTarget:self withObject:@"分離子線程"];

該方法會自動創(chuàng)建一個子線程,并在子線程中執(zhí)行

2017-09-29 10:33:21.702512+0800 aegewgr[9617:3159015] test - <NSThread: 0x6000004621c0>{number = 4, name = (null)} - 分離子線程
  • 方法三
[self performSelectorInBackground:@selector(test:) withObject:@"后臺線程"];

該方法會開啟一條后臺線程,并在后臺線程中執(zhí)行。
上面所有的方法都還有與之對應(yīng)的通過block創(chuàng)建的方法。

另外通過線程間通信的幾個方法

[self performSelectorOnMainThread:<#(nonnull SEL)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>]
[self performSelectorOnMainThread:<#(nonnull SEL)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#> modes:<#(nullable NSArray<NSString *> *)#>]

[self performSelector:<#(nonnull SEL)#> onThread:<#(nonnull NSThread *)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>]
[self performSelector:<#(nonnull SEL)#> onThread:<#(nonnull NSThread *)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#> modes:<#(nullable NSArray<NSString *> *)#>]

關(guān)于進(jìn)程間通信,可以看我另一篇文章Runloop的應(yīng)用與深入理解

還有一下常用的屬性和方法直接去看文檔就可以了。

三、NSOperation和NSOperationQueue

NSOperation是基于GCD開發(fā)的,但是比GCD擁有更強(qiáng)的可控性和代碼可讀性。NSOperation是一個抽象基類,表示一個獨(dú)立的計(jì)算單元,可以為子類提供有用且線程安全的建立狀態(tài),優(yōu)先級,依賴和取消等操作。我們使用比較多的就是它的子類NSInvocationOperation和NSBlockOperation。不過我們更多的使用是自己繼承并定制自己的操作。

使用NSInvocationOperation

    NSInvocationOperation *invo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test:) object:nil];
    [invo start];
    NSLog(@"111");
- (void)test:(NSString *)string {
    sleep(1);
    NSLog(@"test - %@ - %@", [NSThread currentThread], string);
}
2017-09-29 14:19:13.242517+0800 aegewgr[10143:3388734] test - <NSThread: 0x600000078180>{number = 1, name = main} - (null)
2017-09-29 14:19:13.242967+0800 aegewgr[10143:3388734] 111

可以看到NSInvocationOperation是同步并且串行的,所以只是用NSInvocationOperation并沒有什么卵用,主要還是要和NSOperationQueue結(jié)合使用。這個放到后面再講。

使用NSBlockOperation
NSBlockOperation支持并發(fā)的實(shí)行一個或多個block,使用起來非常方便

NSBlockOperation *blockOperation = [[NSBlockOperation alloc]init];
    [blockOperation addExecutionBlock:^{
        NSLog(@"block 1 in thread:%@",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"block 2 in thread:%@",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"block 3 in thread:%@",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"block 4 in thread:%@",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        sleep(1);
        NSLog(@"block 5 in thread:%@",[NSThread currentThread]);
    }];
    [blockOperation start];
    NSLog(@"123");
2017-09-29 14:32:03.710936+0800 aegewgr[10335:3439694] block 1 in thread:<NSThread: 0x60400006d740>{number = 1, name = main}
2017-09-29 14:32:03.710939+0800 aegewgr[10335:3439916] block 3 in thread:<NSThread: 0x60400027a780>{number = 4, name = (null)}
2017-09-29 14:32:03.710943+0800 aegewgr[10335:3439920] block 4 in thread:<NSThread: 0x60400027a840>{number = 5, name = (null)}
2017-09-29 14:32:03.710961+0800 aegewgr[10335:3439919] block 2 in thread:<NSThread: 0x600000270c00>{number = 3, name = (null)}
2017-09-29 14:32:04.712532+0800 aegewgr[10335:3439920] block 5 in thread:<NSThread: 0x60400027a840>{number = 5, name = (null)}
2017-09-29 14:32:04.712932+0800 aegewgr[10335:3439694] 123

注意,這里我故意讓block5 sleep了1秒才執(zhí)行。而123這個輸出也是直到block5執(zhí)行完了才執(zhí)行,所以,NSBlockOperation也是同步的,而block的執(zhí)行是并發(fā)的。至于串行隊(duì)列并發(fā)隊(duì)列與同步異步的概念可以參考我前面提到的那篇文章。

自定義NSOperation

自定義NSOperation分兩種,一種是自定義非并發(fā)的NSOperation,一種是定義并發(fā)的NSOperation的。下面分別介紹。

  • 定義非并發(fā)的NSOperation

如果是自定義非并發(fā)的NSOperation,只需要重寫main方法就夠了。

#import "SerialNSOperation.h"

@implementation SerialNSOperation

- (void)main
{
    NSLog(@"main begin");
    @try {
        //在這里我們要創(chuàng)建自己的釋放池,因?yàn)檫@里我們拿不到主線程的釋放池
        @autoreleasepool {
            // 提供一個變量標(biāo)識,來表示需要執(zhí)行的操作是否完成了,當(dāng)然,沒開始執(zhí)行之前,為NO
            BOOL taskIsFinished = NO;
            // while 保證:只有當(dāng)沒有執(zhí)行完成和沒有被取消,才執(zhí)行自定義的相應(yīng)操作
            while (taskIsFinished == NO && [self isCancelled] == NO){
                // 自定義的操作
                NSLog(@"currentThread = %@", [NSThread currentThread]);
                sleep(10);  // 模擬耗時操作
                // 這里相應(yīng)的操作都已經(jīng)完成,后面就是要通知KVO我們的操作完成了。
                taskIsFinished = YES;
            }
        }
    }
    @catch (NSException * e) {
        NSLog(@"Exception %@", e);
    }
    NSLog(@"main end");
}

然后直接使用

SerialNSOperation *op = [[SerialNSOperation alloc]init];
[op start];
2017-09-29 15:12:59.151481+0800 aegewgr[10524:3564080] main begin
2017-09-29 15:13:09.152082+0800 aegewgr[10524:3564080] currentThread = <NSThread: 0x60400006e1c0>{number = 1, name = main}
2017-09-29 15:13:09.152299+0800 aegewgr[10524:3564080] main end

其實(shí)我感覺這個實(shí)用性不大。

  • 定義并發(fā)的NSOperation

自定義并發(fā)的NSOperation需要以下步驟:
1.start方法:該方法必須實(shí)現(xiàn),
2.main:該方法可選,如果你在start方法中定義了你的任務(wù),則這個方法就可以不實(shí)現(xiàn),但通常為了代碼邏輯清晰,通常會在該方法中定義自己的任務(wù)
3.isExecuting isFinished 主要作用是在線程狀態(tài)改變時,產(chǎn)生適當(dāng)?shù)腒VO通知
4.isAsynchronous :必須覆蓋并返回YES;

//.h
#import <Foundation/Foundation.h>

@interface ConcurrentOperation : NSOperation{
    BOOL executing;
    BOOL finished;
}
//.m
#import "ConcurrentOperation.h"

@implementation ConcurrentOperation
- (id)init {
    if(self = [super init])
    {
        executing = NO;
        finished = NO;
    }
    return self;
}
- (BOOL)isAsynchronous {
    return YES;
}
- (BOOL)isExecuting {
    return executing;
}
- (BOOL)isFinished {
    return finished;
}
- (void)start {
    //第一步就要檢測是否被取消了,如果取消了,要實(shí)現(xiàn)相應(yīng)的KVO
    if ([self isCancelled]) {
        [self willChangeValueForKey:@"isFinished"];
        finished = YES;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }
    //如果沒被取消,開始執(zhí)行任務(wù)
    [self willChangeValueForKey:@"isExecuting"];
    [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
    executing = YES;
    [self didChangeValueForKey:@"isExecuting"];
}
- (void)main {
    NSLog(@"main begin");
    @try {
        @autoreleasepool {
            //在這里定義自己的并發(fā)任務(wù)
            NSLog(@"自定義并發(fā)操作NSOperation");
            NSThread *thread = [NSThread currentThread];
            NSLog(@"current Thread:%@",thread);
            //任務(wù)執(zhí)行完成后要實(shí)現(xiàn)相應(yīng)的KVO
            [self willChangeValueForKey:@"isFinished"];
            [self willChangeValueForKey:@"isExecuting"];
            executing = NO;
            finished = YES;
            [self didChangeValueForKey:@"isExecuting"];
            [self didChangeValueForKey:@"isFinished"];
        }
    }
    @catch (NSException * e) {
        NSLog(@"Exception %@", e);
    }
    NSLog(@"main end");
}
//調(diào)用
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    ConcurrentOperation *op1 = [[ConcurrentOperation alloc]init];
    ConcurrentOperation *op2 = [[ConcurrentOperation alloc]init];
    ConcurrentOperation *op3 = [[ConcurrentOperation alloc]init];
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];    

打印如下:

2017-09-29 15:34:15.158649+0800 aegewgr[10664:3638228] main begin
2017-09-29 15:34:15.158653+0800 aegewgr[10664:3638226] main begin
2017-09-29 15:34:15.158675+0800 aegewgr[10664:3638227] main begin
2017-09-29 15:34:15.158912+0800 aegewgr[10664:3638228] 自定義并發(fā)操作NSOperation
2017-09-29 15:34:15.159321+0800 aegewgr[10664:3638226] 自定義并發(fā)操作NSOperation
2017-09-29 15:34:15.159372+0800 aegewgr[10664:3638227] 自定義并發(fā)操作NSOperation
2017-09-29 15:34:15.159965+0800 aegewgr[10664:3638226] current Thread:<NSThread: 0x60400046b640>{number = 4, name = (null)}
2017-09-29 15:34:15.160014+0800 aegewgr[10664:3638228] current Thread:<NSThread: 0x60000026d140>{number = 5, name = (null)}
2017-09-29 15:34:15.160103+0800 aegewgr[10664:3638227] current Thread:<NSThread: 0x60400046b5c0>{number = 3, name = (null)}
2017-09-29 15:34:15.160799+0800 aegewgr[10664:3638226] main end
2017-09-29 15:34:15.160973+0800 aegewgr[10664:3638227] main end
2017-09-29 15:34:15.161154+0800 aegewgr[10664:3638228] main end

為了展示并發(fā)執(zhí)行,所以我這里使用了NSOperationQueue,后面我在繼續(xù)講這個。

使用NSOperationQueue

NSOperationQueue就是執(zhí)行NSOperation的隊(duì)列,我們可以將一個或多個NSOperation對象放到隊(duì)列中去執(zhí)行。NSOperationQueue有兩種不同類型的隊(duì)列:主隊(duì)列和自定義隊(duì)列。主隊(duì)列運(yùn)行在主線程之上,而自定義隊(duì)列在后臺執(zhí)行。

使用起來很簡單:

NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];  //主隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //自定義隊(duì)列
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        //任務(wù)執(zhí)行
}];
[queue addOperation:operation];

我們可以通過設(shè)置 maxConcurrentOperationCount 屬性來控制并發(fā)任務(wù)的數(shù)量,當(dāng)設(shè)置為 1時, 那么它就是一個串行隊(duì)列。主對列默認(rèn)是串行隊(duì)列,這一點(diǎn)和 dispatch_queue_t是相似的。前面也說過,NSOperation就是基于GCD開發(fā)的。
NSOperationQueue相對于GCD來說有以下優(yōu)點(diǎn):

  • 提供了在 GCD 中不那么容易復(fù)制的有用特性。
  • 可以很方便的取消一個NSOperation的執(zhí)行
  • 可以更容易的添加任務(wù)的依賴關(guān)系
  • 提供了任務(wù)的狀態(tài):isExecuteing, isFinished.

以上就是多線程相關(guān)的所有方法了,具體使用什么方法還是看你的需求。如果我講的有什么錯誤的地方希望大家指正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,835評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,676評論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,730評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,118評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,873評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,266評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,330評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,482評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,036評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,846評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,025評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,575評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,279評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,684評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,953評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,751評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,016評論 2 375

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

  • 歡迎大家指出文章中需要改正或者需要補(bǔ)充的地方,我會及時更新,非常感謝。 一. 多線程基礎(chǔ) 1. 進(jìn)程 進(jìn)程是指在系...
    xx_cc閱讀 7,225評論 11 70
  • 1、簡介 NSOperation是蘋果提供給我們的一套多線程解決方案。實(shí)際上NSOperation是基于GCD更高...
    WQ_UESTC閱讀 963評論 0 6
  • Object C中創(chuàng)建線程的方法是什么?如果在主線程中執(zhí)行代碼,方法是什么?如果想延時執(zhí)行代碼、方法又是什么? 1...
    AlanGe閱讀 1,765評論 0 17
  • 在了解GCD之前,我們首先要知道幾個概念。關(guān)于隊(duì)列和同/異步函數(shù)。為了讓讀者更簡單直觀的理解這些概念,我盡可能用最...
    fou7閱讀 896評論 1 2
  • 什么是進(jìn)程? 進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個應(yīng)用程序。 每個進(jìn)程之間是獨(dú)立的,每個進(jìn)程均運(yùn)行在其專用且受保護(hù)的內(nèi)存...
    珍此良辰閱讀 1,268評論 1 5