線程基礎(chǔ)及NSThread,GCD,NSOperation簡單使用

一.多線程

  • 進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個應(yīng)用程序。每個進(jìn)程之間是獨(dú)立的演熟,每個進(jìn)程均運(yùn)行在其專用且受保護(hù)的內(nèi)存空間內(nèi)鞭执。(比如同時打開迅雷、Xcode芒粹,系統(tǒng)就會分別啟動2個進(jìn)程兄纺。)

  • 線程是進(jìn)程的基本執(zhí)行單元,1個進(jìn)程要想執(zhí)行任務(wù)化漆,必須得有線程(每1個進(jìn)程至少要有1條線程,并且1個線程中任務(wù)的執(zhí)行是串行的)估脆。

  • 多線程是指1個進(jìn)程中可以開啟多條線程,多條線程可以并行(同時)執(zhí)行不同的任務(wù)座云。

1.線程的并行:
 并行即同時執(zhí)行疙赠。比如同時開啟3條線程分別下載3個文件(分別是文件A付材、文件B、文件C)
2.多線程并發(fā)執(zhí)行的原理:
 a. 在同一時間里圃阳,CPU只能處理 1 條線程厌衔,只有 1 條線程在工作(執(zhí)行)
 b. 多線程并發(fā)(同時)執(zhí)行,其實是CPU快速地在多條線程之間調(diào)度(切換)捍岳,如果CPU調(diào)度線程的時間足夠快富寿,就造成了多線程并發(fā)執(zhí)行的假象
3.優(yōu)缺點(diǎn)

3.1優(yōu)點(diǎn)

 a. 能適當(dāng)提高程序的執(zhí)行效率
 b. 能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率)

3.2缺點(diǎn)

 a. 開啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下锣夹,主線程占用1M页徐,子線程占用512KB),如果開啟大量的線程银萍,會占用大量的內(nèi)存空間变勇,降低程序的性能
 b. 線程越多,CPU在調(diào)度線程上的開銷就越大
 c. 程序設(shè)計更加復(fù)雜:比如線程之間的通信贴唇、多線程的數(shù)據(jù)共享

4.多線程在ios中的應(yīng)用

4.1主線程

 a. 一個iOS程序運(yùn)行后搀绣,默認(rèn)會開啟 1 條線程,稱為“主線程”或“UI 線程”
 b. 作用:刷新顯示 UI滤蝠,處理 UI 事件

4.2子線程

 a. 除了主線程之外的所有線程豌熄,也叫做后臺線程
 b. 子線程不能用來刷新 UI

注意:

 a. 不要將耗時操作放到主線程中去處理,因為會卡住線程,造成畫面卡頓的現(xiàn)象
 b. 和UI相關(guān)的刷新操作必須放到主線程中進(jìn)行處理
5.多線程安全隱患
  • 資源共享
1塊資源可能會被多個線程共享物咳,也就是多個線程可能會訪問同一塊資源
比如多個線程訪問同一個對象、同一個變量蹄皱、同一個文件
  • 當(dāng)多個線程訪問同一塊資源時览闰,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題
安全隱患解決– 互斥鎖
  • 互斥鎖使用格式
// 注意:鎖定1份代碼只用1把鎖,用多把鎖是無效的
@synchronized(鎖對象){ //需要鎖定的代碼  }
  • 互斥鎖的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問題
缺點(diǎn):需要消耗大量的CPU資源
  • 互斥鎖的使用前提:
多條線程搶奪同一塊資源
  • 線程同步: 多條線程在同一條線上執(zhí)行(按順序地執(zhí)行任務(wù))
互斥鎖巷折,就是使用了線程同步技術(shù)
6.線程間通信
1個線程傳遞數(shù)據(jù)給另1個線程
在1個線程中執(zhí)行完特定任務(wù)后压鉴,轉(zhuǎn)到另1個線程繼續(xù)執(zhí)行任務(wù)

二.ios中常用多線程方案

  • NSThread

1.創(chuàng)建方式

//1.實例方法
 NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
[thread start];
//2.類方法
 [NSThread detachNewThreadSelector:@selector(loadImage) toTarget:self withObject:nil];
//3.//隱式創(chuàng)建線程
 [self performSelectorInBackground:@selector(loadImage) withObject:self];

2.當(dāng)前線程獲取

NSThread *current = [NSThread currentThread];

3.獲取主線程

NSThread *main = [NSThread mainThread];
//判斷是否為主線程
-(BOOL)isMainThread;
+(BOOL)isMainThread;

4.暫停當(dāng)前線程

[NSThread sleepForTimeInterval:2];

5.強(qiáng)制停止

+(void)exit;

6.線程之間通信

//在指定線程上執(zhí)行操作
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES]; 
//在主線程上執(zhí)行操作
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES]; 
//在當(dāng)前線程執(zhí)行操作
[self performSelector:@selector(run) withObject:nil];

7.簡單使用

//創(chuàng)建線程
-(void)implicitCreateThread{

[self performSelectorInBackground:@selector(loadImageSource:) withObject:imgUrl];

}
//下載圖片數(shù)據(jù)

-(void)loadImageSource:(NSString *)url{

    NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
    UIImage *image = [UIImage imageWithData:imgData];
    
    if (imgData!= nil) {
        //主線程中跟新界面
        [self performSelectorOnMainThread:@selector(refreshImageView:) withObject:image waitUntilDone:YES];
    }else{
        NSLog(@"NoData");
    }
}
//跟新界面
-(void)refreshImageView:(UIImage *)image{

[self.imageView setImage:image];

}
  • GCD

一.GCD中主要核心概念為任務(wù)隊列

  • 任務(wù):你在線程中執(zhí)行的那段代碼。分為以下兩種.
同步執(zhí)行(sync):只能在當(dāng)前線程中執(zhí)行任務(wù)锻拘,不具備開啟新線程的能力
異步執(zhí)行(async):可以在新的線程中執(zhí)行任務(wù)油吭,具備開啟新線程的能力
  • 隊列:用來存放任務(wù)的隊。采用FIFO(先進(jìn)先出)的原則署拟,即新任務(wù)總是被插入到隊列的末尾婉宰,而讀取任務(wù)的時候總是從隊列的頭部開始讀取。分為以下兩種.
并發(fā)隊列(Concurrent Dispatch Queue):可以讓多個任務(wù)并發(fā)(同時)執(zhí)行(自動開啟多個線程同時執(zhí)行任務(wù))
并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效
串行隊列(Serial Dispatch Queue):讓任務(wù)一個接著一個地執(zhí)行(一個任務(wù)執(zhí)行完畢后推穷,再執(zhí)行下一個任務(wù))

二.GCD創(chuàng)建

GCD創(chuàng)建分為兩步

1.創(chuàng)建一個隊列(串行隊列或并發(fā)隊列)
2.將任務(wù)添加到隊列中心包,然后系統(tǒng)就會根據(jù)任務(wù)類型執(zhí)行任務(wù)(同步執(zhí)行或異步執(zhí)行)
下邊來看看隊列的創(chuàng)建方法和任務(wù)的創(chuàng)建方法。
1.隊列的創(chuàng)建方法
// 串行隊列的創(chuàng)建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
// 并發(fā)隊列的創(chuàng)建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
//主隊列,GCD自帶的一種特殊的串行隊列
dispatch_queue_t queue = dispatch_get_main_queue();
對于并發(fā)隊列馒铃,還可以使用dispatch_get_global_queue來創(chuàng)建全局并發(fā)隊列蟹腾。GCD默認(rèn)提供了全局的并發(fā)隊列痕惋,需要傳入兩個參數(shù)。第一個參數(shù)表示隊列優(yōu)先級娃殖,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT值戳。第二個參數(shù)暫時沒用,用0即可炉爆。
2.任務(wù)的創(chuàng)建方法
// 同步執(zhí)行任務(wù)創(chuàng)建方法
dispatch_sync(queue, ^{
    NSLog(@"%@",[NSThread currentThread]);    // 這里放任務(wù)代碼
});
// 異步執(zhí)行任務(wù)創(chuàng)建方法
dispatch_async(queue, ^{
    NSLog(@"%@",[NSThread currentThread]);    // 這里放任務(wù)代碼
});



這里根據(jù)隊列和任務(wù)的不同共有6種組合方式堕虹。

并發(fā)隊列 串行隊列 主隊列
同步(sync) 沒有開啟新線程,串行執(zhí)行任務(wù) 沒有開啟新線程叶洞,串行執(zhí)行任務(wù) 沒有開啟新線程鲫凶,串行執(zhí)行任務(wù)
異步(async) 有開啟新線程,并發(fā)執(zhí)行任務(wù) 有開啟新線程(1條)衩辟,串行執(zhí)行任務(wù) 沒有開啟新線程螟炫,串行執(zhí)行任務(wù)
  • 并發(fā)隊列 + 同步執(zhí)行

不會開啟新線程,執(zhí)行完一個任務(wù)艺晴,再執(zhí)行下一個任務(wù)

- (void) syncConcurrent
{
    NSLog(@"syncConcurrent---begin");

    dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });

    NSLog(@"syncConcurrent---end");
}
輸出結(jié)果:
2016-09-03 19:22:27.577 GCD[11557:1897538] syncConcurrent---begin
2016-09-03 19:22:27.578 GCD[11557:1897538] 1------<NSThread: 0x7f82a1d058b0>{number = 1, name = main}
2016-09-03 19:22:27.578 GCD[11557:1897538] 1------<NSThread: 0x7f82a1d058b0>{number = 1, name = main}
2016-09-03 19:22:27.578 GCD[11557:1897538] 2------<NSThread: 0x7f82a1d058b0>{number = 1, name = main}
2016-09-03 19:22:27.579 GCD[11557:1897538] 2------<NSThread: 0x7f82a1d058b0>{number = 1, name = main}
2016-09-03 19:22:27.579 GCD[11557:1897538] 3------<NSThread: 0x7f82a1d058b0>{number = 1, name = main}
2016-09-03 19:22:27.579 GCD[11557:1897538] 3------<NSThread: 0x7f82a1d058b0>{number = 1, name = main}
2016-09-03 19:22:27.579 GCD[11557:1897538] syncConcurrent---end
1.從并發(fā)隊列 + 同步執(zhí)行中可以看到昼钻,所有任務(wù)都是在主線程中執(zhí)行的。由于只有一個線程封寞,所以任務(wù)只能一個一個執(zhí)行然评。
2.同時我們還可以看到,所有任務(wù)都在打印的syncConcurrent-begin和syncConcurrent---end之間狈究,這說明任務(wù)是添加到隊列中馬上執(zhí)行的碗淌。
  • 并發(fā)隊列 + 異步執(zhí)行

可同時開啟多線程,任務(wù)交替執(zhí)行

- (void) asyncConcurrent
{
    NSLog(@"asyncConcurrent---begin");

    dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });

    NSLog(@"asyncConcurrent---end");
}
輸出結(jié)果:
2016-09-03 19:27:31.503 GCD[11595:1901548] asyncConcurrent---begin
2016-09-03 19:27:31.504 GCD[11595:1901548] asyncConcurrent---end
2016-09-03 19:27:31.504 GCD[11595:1901626] 1------<NSThread: 0x7f8309c22080>{number = 2, name = (null)}
2016-09-03 19:27:31.504 GCD[11595:1901625] 2------<NSThread: 0x7f8309f0b790>{number = 4, name = (null)}
2016-09-03 19:27:31.504 GCD[11595:1901855] 3------<NSThread: 0x7f8309e1a950>{number = 3, name = (null)}
2016-09-03 19:27:31.504 GCD[11595:1901626] 1------<NSThread: 0x7f8309c22080>{number = 2, name = (null)}
2016-09-03 19:27:31.504 GCD[11595:1901625] 2------<NSThread: 0x7f8309f0b790>{number = 4, name = (null)}
2016-09-03 19:27:31.505 GCD[11595:1901855] 3------<NSThread: 0x7f8309e1a950>{number = 3, name = (null)}
1.在并發(fā)隊列 + 異步執(zhí)行中可以看出抖锥,除了主線程亿眠,又開啟了3個線程,并且任務(wù)是交替著同時執(zhí)行的磅废。
2.另一方面可以看出纳像,所有任務(wù)是在打印的syncConcurrent---begin和syncConcurrent---end之后才開始執(zhí)行的。說明任務(wù)不是馬上執(zhí)行拯勉,而是將所有任務(wù)添加到隊列之后才開始異步執(zhí)行竟趾。
  • 串行隊列 + 同步執(zhí)行

不會開啟新線程,在當(dāng)前線程執(zhí)行任務(wù)宫峦。任務(wù)是串行的岔帽,執(zhí)行完一個任務(wù),再執(zhí)行下一個任務(wù)

- (void) syncSerial
{
    NSLog(@"syncSerial---begin");

    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);

    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });

    NSLog(@"syncSerial---end");
}
輸出結(jié)果為:
2016-09-03 19:29:00.066 GCD[11622:1903904] syncSerial---begin
2016-09-03 19:29:00.067 GCD[11622:1903904] 1------<NSThread: 0x7fa2e9f00980>{number = 1, name = main}
2016-09-03 19:29:00.067 GCD[11622:1903904] 1------<NSThread: 0x7fa2e9f00980>{number = 1, name = main}
2016-09-03 19:29:00.067 GCD[11622:1903904] 2------<NSThread: 0x7fa2e9f00980>{number = 1, name = main}
2016-09-03 19:29:00.067 GCD[11622:1903904] 2------<NSThread: 0x7fa2e9f00980>{number = 1, name = main}
2016-09-03 19:29:00.067 GCD[11622:1903904] 3------<NSThread: 0x7fa2e9f00980>{number = 1, name = main}
2016-09-03 19:29:00.068 GCD[11622:1903904] 3------<NSThread: 0x7fa2e9f00980>{number = 1, name = main}
2016-09-03 19:29:00.068 GCD[11622:1903904] syncSerial---end
1.在串行隊列 + 同步執(zhí)行可以看到斗遏,所有任務(wù)都是在主線程中執(zhí)行的山卦,并沒有開啟新的線程。而且由于串行隊列,所以按順序一個一個執(zhí)行账蓉。
2.同時我們還可以看到枚碗,所有任務(wù)都在打印的syncConcurrent---begin和syncConcurrent---end之間,這說明任務(wù)是添加到隊列中馬上執(zhí)行的铸本。
  • 串行隊列 + 異步執(zhí)行

會開啟新線程肮雨,但是因為任務(wù)是串行的,執(zhí)行完一個任務(wù)箱玷,再執(zhí)行下一個任務(wù)

- (void) asyncSerial
{
    NSLog(@"asyncSerial---begin");

    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);

    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });

    NSLog(@"asyncSerial---end");
}
輸出結(jié)果為:
2016-09-03 19:30:08.363 GCD[11648:1905817] asyncSerial---begin
2016-09-03 19:30:08.364 GCD[11648:1905817] asyncSerial---end
2016-09-03 19:30:08.364 GCD[11648:1905895] 1------<NSThread: 0x7fb548c0e390>{number = 2, name = (null)}
2016-09-03 19:30:08.364 GCD[11648:1905895] 1------<NSThread: 0x7fb548c0e390>{number = 2, name = (null)}
2016-09-03 19:30:08.364 GCD[11648:1905895] 2------<NSThread: 0x7fb548c0e390>{number = 2, name = (null)}
2016-09-03 19:30:08.364 GCD[11648:1905895] 2------<NSThread: 0x7fb548c0e390>{number = 2, name = (null)}
2016-09-03 19:30:08.365 GCD[11648:1905895] 3------<NSThread: 0x7fb548c0e390>{number = 2, name = (null)}
2016-09-03 19:30:08.365 GCD[11648:1905895] 3------<NSThread: 0x7fb548c0e390>{number = 2, name = (null)}
1.在串行隊列 + 異步執(zhí)行可以看到怨规,開啟了一條新線程,但是任務(wù)還是串行锡足,所以任務(wù)是一個一個執(zhí)行波丰。
2.另一方面可以看出,所有任務(wù)是在打印的syncConcurrent---begin和syncConcurrent---end之后才開始執(zhí)行的舶得。說明任務(wù)不是馬上執(zhí)行掰烟,而是將所有任務(wù)添加到隊列之后才開始同步執(zhí)行。

三.GCD之間的通訊

在iOS開發(fā)過程中沐批,我們一般在主線程里邊進(jìn)行UI刷新纫骑,例如:點(diǎn)擊、滾動九孩、拖拽等事件先馆。我們通常把一些耗時的操作放在其他線程,比如說圖片下載躺彬、文件上傳等耗時操作煤墙。而當(dāng)我們有時候在其他線程完成了耗時操作時,需要回到主線程宪拥,那么就用到了線程之間的通訊番捂。

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSString * url = @"";
        NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
        UIImage *image = [UIImage imageWithData:imgData];
        
        // 回到主線程
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.imageView setImage:image];
        });
    });

4.GCD 其他用法

  • GCD的柵欄方法 dispatch_barrier_async

我們有時需要異步執(zhí)行兩組操作,而且第一組操作執(zhí)行完之后江解,才能開始執(zhí)行第二組操作。這樣我們就需要一個相當(dāng)于柵欄一樣的一個方法將兩組異步執(zhí)行的操作組給分割起來徙歼,當(dāng)然這里的操作組里可以包含一個或多個任務(wù)犁河。這就需要用到dispatch_barrier_async方法在兩個操作組間形成柵欄。

- (void)barrier
{
    dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        NSLog(@"----1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----2-----%@", [NSThread currentThread]);
    });

    dispatch_barrier_async(queue, ^{
        NSLog(@"----barrier-----%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"----3-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----4-----%@", [NSThread currentThread]);
    });
}
輸出結(jié)果:
2016-09-03 19:35:51.271 GCD[11750:1914724] ----1-----<NSThread: 0x7fb1826047b0>{number = 2, name = (null)}
2016-09-03 19:35:51.272 GCD[11750:1914722] ----2-----<NSThread: 0x7fb182423fd0>{number = 3, name = (null)}
2016-09-03 19:35:51.272 GCD[11750:1914722] ----barrier-----<NSThread: 0x7fb182423fd0>{number = 3, name = (null)}
2016-09-03 19:35:51.273 GCD[11750:1914722] ----3-----<NSThread: 0x7fb182423fd0>{number = 3, name = (null)}
2016-09-03 19:35:51.273 GCD[11750:1914724] ----4-----<NSThread: 0x7fb1826047b0>{number = 2, name = (null)}

可以看出在執(zhí)行完柵欄前面的操作之后魄梯,才執(zhí)行柵欄操作桨螺,最后再執(zhí)行柵欄后邊的操作。

  • GCD的延時執(zhí)行方法 dispatch_after

當(dāng)我們需要延遲執(zhí)行一段代碼時酿秸,就需要用到GCD的dispatch_after方法灭翔。

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 2秒后異步執(zhí)行這里的代碼...
   NSLog(@"run-----");
});
  • GCD的一次性代碼(只執(zhí)行一次) dispatch_once

我們在創(chuàng)建單例、或者有整個程序運(yùn)行過程中只執(zhí)行一次的代碼時辣苏,我們就用到了GCD的dispatch_once方法肝箱。使用dispatch_once函數(shù)能保證某段代碼在程序運(yùn)行過程中只被執(zhí)行1次哄褒。

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只執(zhí)行1次的代碼(這里面默認(rèn)是線程安全的)
});
  • GCD的隊列組 dispatch_group

有時候我們會有這樣的需求:分別異步執(zhí)行2個耗時操作,然后當(dāng)2個耗時操作都執(zhí)行完畢后再回到主線程執(zhí)行操作煌张。這時候我們可以用到GCD的隊列組呐赡。

我們可以先把任務(wù)放到隊列中,然后將隊列放入隊列組中骏融。
調(diào)用隊列組的dispatch_group_notify回到主線程執(zhí)行操作链嘀。

dispatch_group_t group =  dispatch_group_create();

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 執(zhí)行1個耗時的異步操作
});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 執(zhí)行1個耗時的異步操作
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 等前面的異步操作都執(zhí)行完畢后,回到主線程...
});
  • NSOperation

1.簡介

  • NSOperation 是對 GCD 的一層封裝档玻,更加面向?qū)ο蠡巢础V饕蒒SOperation(封裝操作) 和 NSOperationQueue (隊列)實現(xiàn)多線程。

2 .NSOperation

  • NSOperation是一個抽象類误趴,不能夠封裝操作霹琼,只能通過以下三種方式來實現(xiàn)封裝操作:
  • NSInvocationOperation
  • NSBlockOperation
  • 自定義子類繼承 NSOperation ,實現(xiàn)內(nèi)部相應(yīng)的方法
  • NSInvocationOperation
//創(chuàng)建NSInvocationOperation對象
  NSInvocationOperation *invoOperation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(doSomeThing) object:nil];
//調(diào)用start方法來執(zhí)行操作
  [invoOperation start];

NSInvocationOperation比較簡單冤留,繼承了NSOperation碧囊,它是基于一個對象和selector來創(chuàng)建操作,可以直接使用而不需繼承來實現(xiàn)自己的操作處理纤怒。并且默認(rèn)情況下糯而,調(diào)用了start方法之后不會開一條新線程去操作,是在當(dāng)前線程下同步執(zhí)行操作泊窘,只有將其放到NSOperationQueue中才會執(zhí)行異步操作熄驼。

  • NSBlockOperation
//創(chuàng)建NSBlockOperation對象
 NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
     NSLog(@"create1----%@",[NSThread currentThread]);
 }];

 //通過addExecutionBlock:方法添加更多的操作
 [blockOperation1 addExecutionBlock:^{
     NSLog(@"add1-----%@",[NSThread currentThread]);
 }];

 [blockOperation1 addExecutionBlock:^{
     NSLog(@"add2-----%@",[NSThread currentThread]);
 }];
[blockOperation1 start];
8975FE9D-D470-4860-B22C-E77601139C52.png

可以看出NSBlockOperation如果封裝了多個操作,那么除了第一個操作外烘豹,其他的操作會在子線程中進(jìn)行瓜贾。如果只封裝了一個操作,默認(rèn)在主線程中進(jìn)行携悯,并且只要NSBlockOperation封裝的操作數(shù)大于一個祭芦,就會異步執(zhí)行操作。

  • 定義繼承自NSOperation的子類
//繼承NSOpetaion

#import "BHOperation.h"

@implementation BHOperation

- (void)main{
    NSLog(@"%@",[NSThread currentThread]);
}


@end

 BHOperation *operation = [[BHOperation alloc]init];     
 [operation start];

3.NSOperationQueue

  • 主隊列
  • 凡是添加到主隊列中的任務(wù)(NSOperation)憔鬼,都會放到主線程中執(zhí)行
NSOperationQueue *queue = [NSOperationQueue mainQueue];
  • 其他隊列
  • 添加到這種隊列中的任務(wù)(NSOperation)龟劲,就會自動放到子線程中執(zhí)行
  • 同時包含了:串行、并發(fā)功能
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

4.將任務(wù)將入到隊列中

1.需要先創(chuàng)建任務(wù)轴或,再將創(chuàng)建好的任務(wù)加入到創(chuàng)建好的隊列中去

- (void)addOperationToQueue
{
    // 1.創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2. 創(chuàng)建操作  
    // 創(chuàng)建NSInvocationOperation    
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];    
    // 創(chuàng)建NSBlockOperation    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^
            NSLog(@"op1-----%@", [NSThread currentThread]);
    }];

    // 3. 添加操作到隊列中:addOperation:   
    [queue addOperation:op1]; // [op1 start]    
    [queue addOperation:op2]; // [op2 start]
}

- (void)run
{
    NSLog(@"op2-----%@", [NSThread currentThread]);
}

2.無需先創(chuàng)建任務(wù)昌跌,在block中添加任務(wù),直接將任務(wù)block加入到隊列中照雁。

- (void)addOperationWithBlockToQueue
{
    // 1. 創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2. 添加操作到隊列中:addOperationWithBlock:
    [queue addOperationWithBlock:^{
            NSLog(@"-----%@", [NSThread currentThread]);
    }];
}

5. 控制串行執(zhí)行和并行執(zhí)行的關(guān)鍵

這里有個關(guān)鍵參數(shù)maxConcurrentOperationCount蚕愤,叫做最大并發(fā)數(shù)。

最大并發(fā)數(shù):maxConcurrentOperationCount

  • maxConcurrentOperationCount默認(rèn)情況下為-1,表示不進(jìn)行限制萍诱,默認(rèn)為并發(fā)執(zhí)行悬嗓。
  • 當(dāng)maxConcurrentOperationCount為1時,進(jìn)行串行執(zhí)行砂沛。
  • 當(dāng)maxConcurrentOperationCount大于1時烫扼,進(jìn)行并發(fā)執(zhí)行,當(dāng)然這個值不應(yīng)超過系統(tǒng)限制碍庵,即使自己設(shè)置一個很大的值映企,系統(tǒng)也會自動調(diào)整。

6.執(zhí)行順序

對于添加到queue中的操作静浴,它的執(zhí)行順序取決于兩點(diǎn):

1.首先是NSOperation是否準(zhǔn)備好堰氓,是否準(zhǔn)備好是由對象的依賴關(guān)系來決定。
  • 操作依賴

NSOperation之間可以設(shè)置依賴來保證執(zhí)行順序苹享,?如一定要讓操作A執(zhí)行完后,才能執(zhí)行操作B

- (void)addDependency
{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1-----%@", [NSThread  currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2-----%@", [NSThread  currentThread]);
    }];

    [op2 addDependency:op1];    // 讓op2 依賴于 op1双絮,則先執(zhí)行op1,在執(zhí)行op2

    [queue addOperation:op1];
    [queue addOperation:op2];
}
2.根據(jù)NSOperation的相對優(yōu)先級來決定得问,默認(rèn)都是普通的優(yōu)先級囤攀,可以通過setQueuePriority來設(shè)置優(yōu)先級。優(yōu)先級不能替代依賴關(guān)系宫纬,優(yōu)先級是針對于已經(jīng)準(zhǔn)備好的NSOperation來確定執(zhí)行順序焚挠,先滿足依賴關(guān)系,再根據(jù)優(yōu)先級從所有準(zhǔn)備好的操作中選擇執(zhí)行優(yōu)先級最高的那個漓骚。
  • 設(shè)置優(yōu)先級
//創(chuàng)建任務(wù)
NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"create1----%@",[NSThread currentThread]);
  }];
//設(shè)置優(yōu)先級
[blockOperation4 setQueuePriority:NSOperationQueuePriorityVeryHigh];
//加入隊列
  [myQueue addOperation:blockOperation1];
//優(yōu)先級:
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};
  • (注意)先滿足依賴關(guān)系蝌衔,才根據(jù)優(yōu)先級從所有準(zhǔn)備好的操作中選擇優(yōu)先級高的執(zhí)行

7.其他

  • -(void)cancel;NSOperation提供的方法,可取消單個操作

  • -(void)cancelAllOperations;NSOperationQueue提供的方法蝌蹂,可以取消隊列的所有操作

  • -(void)setSuspended:(BOOL)b;可設(shè)置任務(wù)的暫停和恢復(fù)噩斟,YES代表暫停隊列,NO代表恢復(fù)隊列

  • -(BOOL)isSuspended;判斷暫停狀態(tài)

GCD,NSOpeation,NSThread優(yōu)缺點(diǎn)

  • NSThread
    每個NSThread對象對應(yīng)一個線程孤个,真正最原始的線程剃允。

  • 優(yōu)點(diǎn):NSThread 輕量級最低,相對簡單齐鲤。

  • 缺點(diǎn):手動管理所有的線程活動硅急,如生命周期、線程同步佳遂、睡眠等。

  • NSOperation
    自帶線程管理的抽象類撒顿。

  • 優(yōu)點(diǎn):自帶線程周期管理丑罪,操作上可更注重自己邏輯。

  • 缺點(diǎn):面向?qū)ο蟮某橄箢悾荒軐崿F(xiàn)它或者使用它定義好的兩個子類:NSInvocationOperation 和 NSBlockOperation吩屹。

  • GCD
    Grand Central Dispatch (GCD)是Apple開發(fā)的一個多核編程的解決方法跪另。

  • 優(yōu)點(diǎn):最高效,避開并發(fā)陷阱煤搜。

  • 缺點(diǎn):基于C實現(xiàn)免绿。

  • 選擇小結(jié)

  • 簡單而安全的選擇NSOperation實現(xiàn)多線程即可。

  • 處理大量并發(fā)數(shù)據(jù)擦盾,又追求性能效率的選擇GCD嘲驾。

  • NSThread本人選擇基本上是在做些小測試上使用,當(dāng)然也可以基于此造個輪子迹卢。

參考文獻(xiàn)

iOS 多線程基礎(chǔ)知識淺析

iOS多線程--徹底學(xué)會多線程之『GCD』

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辽故,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子腐碱,更是在濱河造成了極大的恐慌誊垢,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件症见,死亡現(xiàn)場離奇詭異喂走,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)谋作,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門芋肠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瓷们,你說我怎么就攤上這事业栅。” “怎么了谬晕?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵碘裕,是天一觀的道長。 經(jīng)常有香客問我攒钳,道長帮孔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任不撑,我火速辦了婚禮文兢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘焕檬。我一直安慰自己姆坚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布实愚。 她就那樣靜靜地躺著兼呵,像睡著了一般兔辅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上击喂,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天维苔,我揣著相機(jī)與錄音,去河邊找鬼懂昂。 笑死介时,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的凌彬。 我是一名探鬼主播沸柔,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼饿序!你這毒婦竟也來了勉失?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤原探,失蹤者是張志新(化名)和其女友劉穎乱凿,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體咽弦,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡徒蟆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了型型。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片段审。...
    茶點(diǎn)故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖闹蒜,靈堂內(nèi)的尸體忽然破棺而出寺枉,到底是詐尸還是另有隱情,我是刑警寧澤绷落,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布衙耕,位于F島的核電站虑稼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜彭雾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一箕速、第九天 我趴在偏房一處隱蔽的房頂上張望零截。 院中可真熱鬧锭汛,春花似錦、人聲如沸管呵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捐下。三九已至顿天,卻和暖如春堂氯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背牌废。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留啤握,地道東北人鸟缕。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像排抬,于是被迫代替她去往敵國和親懂从。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評論 2 348

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