iOS 多線程 淺述

什么是進程瘾带?

  • 進程是指在系統(tǒng)中正在運行的一個應用程序匹颤。
  • 每個進程之間是獨立的偏塞,每個進程均運行在其專用且受保護的內(nèi)存空間內(nèi)利术。

什么是線程?


  • 1個進程要想執(zhí)行任務,必須得有線程(每1個進程至少要有1條線程)汉额。
  • 線程是進程的基本執(zhí)行單元曹仗,一個進程(程序)的所有任務都在線程中執(zhí)行。

小拓展

- 線程的串行(就像烤串一樣)
    - 1個線程中任務的執(zhí)行是串行的蠕搜。
    - 如果要在1個線程中執(zhí)行多個任務怎茫,那么只能一個一個地按順序執(zhí)行這些任務。
    - 在`同一時間內(nèi)`妓灌,1個線程只能執(zhí)行1個任務轨蛤。

什么是多線程?

  • 1個進程中可以開啟多條線程虫埂,每條線程可以并行(同時)執(zhí)行不同的任務祥山。

  • 線程的并行(同時執(zhí)行)

    • 比如同時開啟3條線程分別下載3個文件(分別是文件A、文件B掉伏、文件C缝呕。
  • 多線程并發(fā)執(zhí)行的原理:

    • 在同一時間里,CPU只能處理1條線程斧散,只有1條線程在工作(執(zhí)行)岳颇。
    • 多線程并發(fā)(同時)執(zhí)行,其實是CPU快速地在多條線程之間調(diào)度(切換)颅湘,如果CPU調(diào)度線程的時間足夠快话侧,就造成了多線程并發(fā)執(zhí)行的假象。(如下圖)
    CPU調(diào)用線程

多線程優(yōu)缺點:

  • 優(yōu)點
    • 能適當提高程序的執(zhí)行效率闯参。
    • 能適當提高資源利用率(CPU瞻鹏、內(nèi)存利用率)
  • 缺點
    • 開啟線程需要占用一定的內(nèi)存空間(默認情況下,主線程占用1M鹿寨,子線程占用512KB)新博,如果開啟大量的線程,會占用大量的內(nèi)存空間脚草,降低程序的性能赫悄。
    • 線程越多,CPU在調(diào)度線程上的開銷就越大馏慨。
    • 程序設計更加復雜:比如線程之間的通信埂淮、多線程的數(shù)據(jù)共享

多線程在iOS開發(fā)中的應用

- 主線程
    - 一個iOS程序運行后,默認會在自己的進程中開啟1條線程写隶,稱為“主線程”也叫“UI線程”倔撞。
    - 作用:刷新顯示UI,處理UI事件。
- 使用注意
    - 不要將耗時操作放到主線程中去處理慕趴,因為會卡住主線程痪蝇,造成UI卡頓(用戶體驗差)鄙陡。
    - 和UI相關的刷新操作`必須`放到主線程中進行處理。

線程的狀態(tài)

  • 線程的各種狀態(tài):新建-就緒-運行-阻塞-死亡
  • 常用的控制線程狀態(tài)的方法
        [NSThread exit];//退出當前線程
        [NSThread sleepForTimeInterval:7.0];//阻塞線程
        [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:7.0]];//阻塞線程
    
    

    注意:線程死亡后不能復生


線程安全:

  • 前提:多個線程同時訪問同一塊資源會發(fā)生數(shù)據(jù)安全問題 解決方案:加互斥鎖
  • 相關代碼:@synchronized(self){}
  • 專業(yè)術語-線程同步
  • 原子和非原子屬性(是否對setter方法加鎖)

IOS中多線程的實現(xiàn)方案

方案 簡介 語言 線程生命周期 使用頻率
pthread 一套通用的多線程API
(跨平臺\可移植)
C語言 程序員管理 幾乎不用
NSThread 使用更加面向?qū)ο?
(簡單易用躏啰,可直接操作線程對象)
OC語言 程序員管理 偶爾使用
GCD 為了替代NSThread為生
充分利用設備多核
C語言 系統(tǒng)自動管理 經(jīng)常使用
NSOperation 基于GCD
更加面向?qū)ο?更方便地設置線程之間的依賴 監(jiān)聽線程狀態(tài)KVO
OC語言 系統(tǒng)自動管理 經(jīng)常使用

pthread簡單使用

1.包含頭文件(必須)

#import <pthread.h>

2.創(chuàng)建線程

//  創(chuàng)建線程
/**
     *
     * 參數(shù)一:線程對象(傳地址)
     * 參數(shù)二:線程的屬性(名稱\優(yōu)先級)
     * 參數(shù)三:只想函數(shù)的指針
     * 參數(shù)四:函數(shù)需要接受的字符串參數(shù)趁矾,可以不傳遞(注:由于我們創(chuàng)建的是OC的字符串,所以在傳值的時候需要將其轉(zhuǎn)換成C的字符串)
     */
    pthread_t thread;
    NSString *num = @"123";
    pthread_create(&thread, NULL, task, (__bridge void *)(num));

3.定義參數(shù)所需要的函數(shù)指針


void *task(void *num)
{
    NSLog(@"當前線程 -- %@,傳入的參數(shù):-- %@", [NSThread currentThread], num);

    return NULL;
}

如果需要退出線程的話只需調(diào)用下面代碼

pthread_exit(NULL);

運行結(jié)果:


pthread線程使用截圖

NSThread簡單使用

這邊介紹NSThread創(chuàng)建線程的4種方式:

  • 第一種 (alloc nitWithTarget:selector:object:)
    • 特點:需要手動開啟線程给僵,可以拿到線程對象進行詳細設置
    • 優(yōu)缺點:
      • 缺點:需要手動開啟線程執(zhí)行任務
      • 優(yōu)點:可以拿到線程對象
//  創(chuàng)建線程
    /**
     * 參數(shù)一:目標對象
     * 參數(shù)二:方法選擇器(線程啟動后調(diào)用的方法)
     * 參數(shù)三:調(diào)用方法需要接受的參數(shù)
     */
    NSThread *thread = [[NSThread alloc] initWithTarget:self
                                               selector:@selector(task)
                                                 object:nil];

    //  開始執(zhí)行
    [thread start];
  • 第二種(分離出一條子線程)
    • 特點:自動啟動線程毫捣,無法對線程進行更詳細的設置
    • 優(yōu)缺點:
      • 缺點:無法拿到線程對象 進行更詳細設置
      • 優(yōu)點:代碼簡單且自動開啟線程執(zhí)行
//  創(chuàng)建線程
    /**
     * 參數(shù)一:要調(diào)用的方法
     * 參數(shù)二:目標對象 self
     * 參數(shù)三:調(diào)用方法需傳遞的參數(shù)
     */
    [NSThread detachNewThreadSelector:@selector(task)
                             toTarget:self
                           withObject:nil];
  • 第三種(后臺線程)
    • 特點:自動啟動線程,無法進行更詳細設置
    • 優(yōu)缺點:
      • 缺點:無法拿到線程對象 進行更詳細設置
      • 優(yōu)點:代碼簡單且自動開啟線程執(zhí)行

/**
 *  NSThread創(chuàng)建一條后臺線程
 */
- (void)nsthreadTest3
{
    //  創(chuàng)建線程
    /**
     * 參數(shù)一:要調(diào)用的方法
     * 參數(shù)二:調(diào)用方法需傳遞的參數(shù)
     */
    [self performSelectorInBackground:@selector(run:) withObject:@"后臺線程"];

}

- (void)run:(NSString *)str
{
    NSLog(@"當前線程:%@ -- 接收到的參數(shù):%@", [NSThread currentThread], str);
}

  • 第四種(自定義NSThread類并重寫內(nèi)部的方法實現(xiàn))
    • 特點:可以不暴露一些實現(xiàn)細節(jié)想际,使代碼增加隱蔽性培漏。(一般出現(xiàn)在第三方框架內(nèi))
    • 優(yōu)缺點:
      • 缺點:繁瑣溪厘,且需要手動開啟線程執(zhí)行
      • 優(yōu)點:增加代碼隱蔽性

1.創(chuàng)建自定義類繼承自NSThread
2.重寫NSThread類中的main方法

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

3.創(chuàng)建線程對象

/**
 *  NSThread創(chuàng)建一條后臺線程
 */
- (void)nsthreadTest4
{
    //  創(chuàng)建線程
    SJThread *thread = [[SJThread alloc] init];

    //  開啟執(zhí)行
    [thread start];
}

線程間通信

有時候我們會從服務器上下載圖片然后再展示出來胡本,下載的操作我們會放到子線程,而UI刷新的操作只能在主線程中執(zhí)行畸悬。這樣就涉及到線程間的通信侧甫。接下來我們分三種方式來簡單實現(xiàn)一下:

  • 方式一:
- (void)viewDidLoad {
    [super viewDidLoad];

    // 開啟一條線程下載圖片
    [NSThread detachNewThreadSelector:@selector(downloadImage) toTarget:self withObject:nil];

}


- (void)downloadImage
{
    //  網(wǎng)絡圖片url
    NSURL *url = [NSURL URLWithString:@"http://img3.imgtn.bdimg.com/it/u=3841157212,2135341815&fm=206&gp=0.jpg"];
    //  根據(jù)url下載圖片數(shù)據(jù)到本地
    NSData *imageData = [NSData dataWithContentsOfURL:url];
    //  把下載到本地的二進制數(shù)據(jù)轉(zhuǎn)成圖片
    UIImage *image = [UIImage imageWithData:imageData];
    //  回到主線程刷新UI
    //  第一種方式
    [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];

    //  第二種方式
    //  直接調(diào)用iconView里面的setImage:方法就可以實現(xiàn)刷新
//    [self.iconView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];

    //  第三種方式
    //  此方法可以方便自由在主線程和其它線程切換
//    [self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
}

- (void)showImage:(UIImage *)image
{
    self.iconView.image = image;
}


GCD簡單使用

什么是GCD

  • GCD全稱是Grand Central Dispatch(牛逼的中樞調(diào)度器)
  • 純C語言,提供了非常多強大的函數(shù)

GCD優(yōu)勢

  • GCD是蘋果公司為多核的并行運算提出的解決方案
  • GCD會自動利用更多的CPU內(nèi)核
  • GCD會自動關了線程生命周期(創(chuàng)建蹋宦、調(diào)度披粟、銷毀線程)
  • GCD性能很好(接近底層)

GCD的組合方式

  • 異步函數(shù)+并發(fā)隊列:開啟多條線程,并發(fā)執(zhí)行任務
  • 異步函數(shù)+串行隊列:開啟一條線程冷冗,串行執(zhí)行任務
  • 同步函數(shù)+并發(fā)隊列:不開線程守屉,串行執(zhí)行任務
  • 同步函數(shù)+串行隊列:不開線程,串行執(zhí)行任務
  • 異步函數(shù)+主隊列:不開線程蒿辙,在主線程中串行執(zhí)行任務
  • 同步函數(shù)+主隊列:不開線程拇泛,串行執(zhí)行任務(注意死鎖發(fā)生

注意同步函數(shù)和異步函數(shù)在執(zhí)行順序上面的差異

GCD的任務和隊列

  • 任務:執(zhí)行什么操作
  • 隊列:用來存放任務(GCD中提供了2種隊列)
    • 串行隊列
    • 并發(fā)隊列

GCD的使用

  • 定制任務 —— 確定需要做的操作
  • 將任務添加到隊列中
  • GCD會自動將隊列中的任務取出,存放到線程中執(zhí)行
  • 任務的取出遵循隊列的FIFO原則(先進先出思灌,后進后出)
FIFO原則圖片

GCD創(chuàng)建線程

  • 接下來看看同步函數(shù)和異步函數(shù)有什么區(qū)別:

1.先來看看異步并發(fā)隊列

- (void)test
{
    /**
     * 參數(shù)一:C語言的字符串俺叭,給隊列起一個名字或標識
     * 參數(shù)二:隊列類型
        DISPATCH_QUEUE_CONCURRENT   并發(fā)
        DISPATCH_QUEUE_SERIAL   串行
     */
    dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);

    /**
     *  使用函數(shù)封裝任務
     * 參數(shù)一:獲取隊列
     * 參數(shù)二:需要執(zhí)行的任務
     */
    dispatch_async(queue, ^{
        NSLog(@"在:%@線程執(zhí)行了任務",[NSThread currentThread]);
    });

    NSLog(@"結(jié)束");
}

執(zhí)行結(jié)果:


異步并發(fā)隊列截圖

2.再來看看同步并發(fā)隊列

- (void)test
{
    /**
     * 參數(shù)一:C語言的字符串,給隊列起一個名字或標識
     * 參數(shù)二:隊列類型
        DISPATCH_QUEUE_CONCURRENT   并發(fā)
        DISPATCH_QUEUE_SERIAL   串行(串行隊列可以用NULL表示)
     */
    dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);

    /**
     *  使用函數(shù)封裝任務
     * 參數(shù)一:獲取隊列
     * 參數(shù)二:需要執(zhí)行的任務
     */
    dispatch_sync(queue, ^{
        NSLog(@"在:%@線程執(zhí)行了任務",[NSThread currentThread]);
    });

    NSLog(@"結(jié)束");
}

執(zhí)行結(jié)果:


同步并發(fā)隊列截圖

結(jié)論:

從上面的2個運行結(jié)果的時間可以看出
1.異步并發(fā)隊列泰偿,會開啟一條子線程來處理任務熄守,以達到主線程和子線程同時執(zhí)行的并發(fā)效果。
2.同步并發(fā)隊列耗跛,不會開線程裕照,必須等block塊中的代碼先執(zhí)行完畢才會繼續(xù)執(zhí)行以外的任務,所以并發(fā)隊列對于同步函數(shù)來說等同于“無效”

  • 再看看并發(fā)隊列對異步函數(shù)和同步函數(shù)的影響:

1.同步函數(shù)+并發(fā)隊列

dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);

    dispatch_sync(queue, ^{
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });

執(zhí)行結(jié)果:同步函數(shù)+并發(fā)隊列沒有開啟子線程的能力

并發(fā)隊列對同步函數(shù)的影響

2.異步函數(shù)+并發(fā)隊列

- (void)test2
{
    dispatch_queue_t queue = dispatch_queue_create("并發(fā)隊列", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:異步函數(shù)+并發(fā)隊列會自動開啟3條子線程執(zhí)行任務

并發(fā)隊列對異步函數(shù)的影響

結(jié)論:

從上面可以看出调塌,異步函數(shù)擁有開啟子線程的能力牍氛,而同步函數(shù)沒有開啟子線程的能力。


  • GCD中烟阐,除了并發(fā)隊列外搬俊,還有串行隊列紊扬,我們來看看如果把并發(fā)隊列換成串行隊列會有怎樣的變化

1.同步函數(shù)+串行隊列

- (void)test2
{
    dispatch_queue_t queue = dispatch_queue_create("串行隊列", DISPATCH_QUEUE_SERIAL);

    dispatch_sync(queue, ^{
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:進一步證明同步函數(shù)沒有開啟子線程的能力,他的所有任務都在主線程中執(zhí)行

同步函數(shù)+串行隊列

2.異步函數(shù)+串行隊列

- (void)test2
{
    dispatch_queue_t queue = dispatch_queue_create("串行隊列", DISPATCH_QUEUE_SERIAL);

    dispatch_async(queue, ^{
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"當前線程:%@",[NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:開啟了一條子線程唉擂,在子線程中依次執(zhí)行任務

異步函數(shù)+串行隊列

結(jié)論

1.在同步函數(shù)+串行隊列中餐屎,任務依舊是在主線程中執(zhí)行。

2.在異步函數(shù)+串行隊列中玩祟,會自動開啟一條子線程腹缩,在子線程中依次執(zhí)行任務

3.再一次證明同步函數(shù)沒有開啟子線程的能力


系統(tǒng)提供的4個全局并發(fā)隊列

  • 在iOS中系統(tǒng)默認給我們提供了4個全局并發(fā)隊列
- (void)test3
{
    //  獲取全局并發(fā)隊列
    //  系統(tǒng)內(nèi)部默認提供4個全局并發(fā)隊列
    /**
     * 參數(shù)一:優(yōu)先級
     * 參數(shù)二:時間(傳0即可)
     */
//優(yōu)先級:DISPATCH_QUEUE_PRIORITY_HIGH 2
//      DISPATCH_QUEUE_PRIORITY_DEFAULT 0
//      DISPATCH_QUEUE_PRIORITY_LOW (-2)
//      DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN    級別最低

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_async(queue, ^{
        NSLog(@"1當前線程:%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"2當前線程:%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"3當前線程:%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"4當前線程:%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"5當前線程:%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"6當前線程:%@", [NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:在結(jié)果中我們看到GCD創(chuàng)建了6條線程,但是實際上GCD創(chuàng)建多少條線程完全由系統(tǒng)當前情況而定空扎,我們是無法控制的藏鹊。

獲取全局隊列

特殊的串行隊列 —— 主隊列(與主線程相關聯(lián)的隊列)

  • 主隊列是GCD自帶的一種特殊的串行隊列
  • 放在主隊列中的人物,都會放到主線程中執(zhí)行
  • 使用dispatch_get_main_queue()的方式可獲取主隊列
    • 特點
      • 1.放在主隊列中的任務转锈,必須在主線程中執(zhí)行
      • 2.主隊列執(zhí)行任務的時候盘寡,在調(diào)度任務的時候,會先調(diào)用主線程的狀態(tài)撮慨,如果當前有任務在做竿痰,則會等待主線程執(zhí)行完任務再執(zhí)行自己的任務

1.主隊列+異步函數(shù)

- (void)test4
{
    //  獲取主隊列
    dispatch_queue_t queue = dispatch_get_main_queue();

    //  添加任務
    dispatch_async(queue, ^{
        NSLog(@"當前線程:%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"當前線程:%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"當前線程:%@", [NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:任務都在主線程中執(zhí)行

主隊列+異步函數(shù)

2.同步函數(shù)+主隊列

- (void)test4
{
    //  獲取主隊列
    dispatch_queue_t queue = dispatch_get_main_queue();

    //  添加任務
    dispatch_sync(queue, ^{
        NSLog(@"當前線程:%@", [NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"當前線程:%@", [NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"當前線程:%@", [NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:
進入死鎖狀態(tài),因為主隊列執(zhí)行任務的時候萧朝,在調(diào)度任務的時候何址,會先調(diào)用主線程的狀態(tài)偎血,如果當前有任務在做谒亦,則會等待主線程執(zhí)行完任務再執(zhí)行自己的任務

如果要解決以上的情況廓旬,那么可以將任務添加到子線程中,這樣就不會出現(xiàn)死鎖的情況终畅,程序也就能夠正常執(zhí)行了


[self performSelectorInBackground:@selector(test4) withObject:nil];

- (void)test4
{
    //  獲取主隊列
    dispatch_queue_t queue = dispatch_get_main_queue();

    //  添加任務
    dispatch_sync(queue, ^{
        NSLog(@"當前線程:%@", [NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"當前線程:%@", [NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"當前線程:%@", [NSThread currentThread]);
    });
}

執(zhí)行結(jié)果:

死鎖解決方式

總結(jié)

函數(shù)類型 并發(fā)隊列 手動創(chuàng)建的串行隊列 主隊列
同步 (sync) 1.沒有開啟新線程
2.串行執(zhí)行任務
1.有開啟新線程
2.串行執(zhí)行任務
死鎖
異步(async) 1.有開啟新線程
2.并發(fā)執(zhí)行任務
1.有開啟新線程
2.串行執(zhí)行任務
1.沒有開啟新線程
2.串行執(zhí)行任務

注意

使用sync函數(shù)往當前串行隊列中添加任務絮识,會卡主當前的串行隊列挪圾。

GCD線程間的通信

  • 有時候我們需要在子線程進行一些耗時操作也殖,等耗時操作完成后再回到主線程進行相應的UI刷新闪湾,那么就可以使用下面的方式在子線程和主線程之間進行通信

- (void)test5
{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    dispatch_async(queue, ^{

        NSLog(@"在%@線程中執(zhí)行任務", [NSThread currentThread]);
        //  回到主線程
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"在%@線程中執(zhí)行任務", [NSThread currentThread]);
        });
    });
}

執(zhí)行結(jié)果:


線程間通信

GCD延遲執(zhí)行

  • 特點:可以選擇在哪個線程中執(zhí)行任務
- (void)test6
{
    NSLog(@"方法開始運行");
    /**
     *  GCD延遲執(zhí)行方法
     *
     *  參數(shù)一: 要延遲的時間 (以秒為單位)
     *  參數(shù)二: 在哪個線程中執(zhí)行
     */
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"GCD定時器");
    });
}

執(zhí)行結(jié)果:

GCD延遲執(zhí)行

一次性代碼

  • 特點:
    • 能保證整個程序運行過程中黔夭,block內(nèi)的代碼塊只會被執(zhí)行一次
    • 線程是安全的
    • 應用:簡單的單例模式(單例模式實現(xiàn)點我)
    • 注意點:不可放在懶加載中

- (void)test8
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"一次性代碼運行");
    });
}


柵欄函數(shù)

  • 作用:能夠控制并發(fā)隊列里面任務的執(zhí)行順序
  • 注意:不能使用全局并發(fā)隊列(會沒有任何區(qū)別,文檔中有注釋——只對自己創(chuàng)建的并發(fā)隊列有效)
- (void)test7
{
    //  創(chuàng)建隊列
    dispatch_queue_t queue = dispatch_queue_create(0, 0);

    dispatch_async(queue, ^{

        for (int i = 0; i<5; i++) {
            NSLog(@"1");
        }
    });
    dispatch_async(queue, ^{

        for (int i = 0; i<5; i++) {
            NSLog(@"2");
        }
    });

    dispatch_barrier_async(queue, ^{
        NSLog(@"進入柵欄函數(shù)");
    });

    dispatch_async(queue, ^{
        for (int i = 0; i<5; i++) {
            NSLog(@"3");
        }
    });

    dispatch_async(queue, ^{
        for (int i = 0; i<5; i++) {
            NSLog(@"4");
        }
    });
}

執(zhí)行結(jié)果:

柵欄函數(shù)執(zhí)行結(jié)果

GCD迭代開發(fā)(遍歷)

  • 一般我們傳統(tǒng)的遍歷方式如下浇衬,它的缺點就是在處理比較耗時的操作時效率較低餐济,因為只在一個線程中執(zhí)行任務耘擂。
    //  傳統(tǒng)的遍歷方式
    for (int i ; i< 10; i++) {
        NSLog(@"%d -- 當前線程%@", i, [NSThread currentThread]);
    }

執(zhí)行結(jié)果:


這里寫圖片描述
  • 在GCD中,為我們提供了一個迭代函數(shù)絮姆,可以開啟子線程快速進行遍歷醉冤,這樣就可以大大提高效率,而且使用非常簡單。接下來使用迭代函數(shù)來進行文件復制的操作:
- (void)test9
{
    //  獲得文件原始路徑(上層文件夾得路徑)
    NSString *fromPath = @"/Users/yeshaojian/Desktop/test";

    //  獲得文件的目標路徑
    NSString *toPath = @"/Users/yeshaojian/Desktop/test2";

    //  得到文件路徑下面的所有文件
    NSArray *subpaths =  [[NSFileManager defaultManager] subpathsAtPath:fromPath];
    NSLog(@"文件名:%@",subpaths);

    //  獲取數(shù)組中文件的個數(shù)
    NSInteger count = subpaths.count;

    //  將要迭代的操作放到迭代函數(shù)內(nèi)
    dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t index){
            //  拼接需要復制的文件的全路徑
            NSString *fromFullpath = [fromPath stringByAppendingPathComponent:subpaths[index]];
            //  拼接目標目錄的全路徑
            NSString *toFullpath = [toPath stringByAppendingPathComponent:subpaths[index]];
            //  執(zhí)行文件剪切操作
            /*
             * 參數(shù)一:文件在哪里的全路徑
             * 參數(shù)二:文件要被剪切到哪里的全路徑
             */
            [[NSFileManager defaultManager] moveItemAtPath:fromFullpath toPath:toFullpath error:nil];

           NSLog(@"拼接需要復制的文件的全路徑:%@ -- 拼接目標目錄的全路徑:%@ -- 當前線程:%@",fromFullpath,toFullpath,[NSThread currentThread]);
       });

}

執(zhí)行結(jié)果:


GCD迭代截圖

隊列組

  • 假如開發(fā)中有多個任務篙悯,要求在所有任務都在子線程中并發(fā)執(zhí)行蚁阳,且不能使用柵欄函數(shù),當所有任務都執(zhí)行完成后打印“完成”鸽照。這樣的需求就需要用到GCD中的隊列組螺捐。
  • 應用場合:
    • 對多個任務有強制依賴性,缺一不可時使用

1.隊列組的基本使用

- (void)test10
{
    // 獲取隊列組矮燎,用來管理隊列
    dispatch_group_t group = dispatch_group_create();

    //  獲取并發(fā)隊列
    dispatch_queue_t queue = dispatch_queue_create("cs", DISPATCH_QUEUE_CONCURRENT);

    //  添加任務
    dispatch_group_async(group, queue, ^{
        NSLog(@"cs1---%@", [NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"cs2---%@", [NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"cs3---%@", [NSThread currentThread]);
    });

    //  攔截通知:當隊列組中所有的任務都執(zhí)行完畢后定血,會調(diào)用下面方法的block塊
    dispatch_group_notify(group, queue, ^{
        NSLog(@"完成");
    });
}

執(zhí)行結(jié)果:


隊列組截圖

隊列組函數(shù)內(nèi)部操作簡要流程

處理流程:
1.封裝任務
2.把任務提交到隊列
3.把當前任務的執(zhí)行情況納入到隊列注的監(jiān)聽范圍

注意:下面方法本身是異步的
dispatch_group_notify(group, queue, ^{

    });

拓展:
在一些框架或者早期項目中,可能會見到下面2種隊列組的使用方法诞外,在這邊順帶提及一下澜沟,但不推薦使用,因為太過繁瑣浅乔。

第一種

- (void)test11
{
    //  獲得隊列組,管理隊列
    dispatch_group_t group = dispatch_group_create();

    //  獲得并發(fā)隊列
    dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);

    //  表示開始把后面的異步任務納入到監(jiān)聽范圍
    //dispatch_group_enter & dispatch_group_leave
    dispatch_group_enter(group);

    //  使用異步函數(shù)封裝任務
    dispatch_async(queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);

        //  通知隊列組該任務已經(jīng)執(zhí)行完畢
        dispatch_group_leave(group);
    });

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

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"3---%@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });

    //  攔截通知
    dispatch_group_notify(group, queue, ^{
        NSLog(@"--完成---");
    });
}

第二種


- (void)test11
{
    //  獲得隊列組,管理隊列
    dispatch_group_t group = dispatch_group_create();

    //  獲得并發(fā)隊列
    dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);

    //  表示開始把后面的異步任務納入到監(jiān)聽范圍
    //dispatch_group_enter & dispatch_group_leave
    dispatch_group_enter(group);

    //  使用異步函數(shù)封裝任務
    dispatch_async(queue, ^{
        NSLog(@"1---%@",[NSThread currentThread]);

        //  通知隊列組該任務已經(jīng)執(zhí)行完畢
        dispatch_group_leave(group);
    });

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

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"3---%@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });

    //  等待DISPATCH_TIME_FOREVER 死等,一直要等到所有的任務都執(zhí)行完畢之后才會繼續(xù)往下執(zhí)行
    //  同步執(zhí)行
    dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, 0.00001 * NSEC_PER_SEC);

    //  等待timer m的時間 不管隊列中的任務有沒有執(zhí)行完畢都繼續(xù)往下執(zhí)行,如果在該時間內(nèi)所有事任務都執(zhí)行完畢了那么會返回一個0,否則是非0值
    long n =  dispatch_group_wait(group, timer);
    NSLog(@"%ld",n);

    NSLog(@"--完成---");
}


補充:同步\異步函數(shù)另一種創(chuàng)建方式

  • 其實同步函數(shù)和異步函數(shù)還有另外的創(chuàng)建方式倔喂,但是使用起來比較不方便铝条,所以上面就沒提及靖苇,想想還是補充一下好了

1.異步函數(shù)(創(chuàng)建一個使用函數(shù)封裝代碼的異步函數(shù))

- (void)test12
{
    /**
     *  參數(shù)一:隊列
     *  參數(shù)二:要傳給函數(shù)的參數(shù)
     *  參數(shù)三:函數(shù)
     */
    dispatch_async_f(dispatch_get_global_queue(0, 0), NULL, testTask);
}

void testTask(void *param)
{
    NSLog(@"%@", [NSThread currentThread]);
}

2.同步函數(shù)(創(chuàng)建一個使用函數(shù)封裝代碼的同步函數(shù))

- (void)test12
{
    /**
     *  參數(shù)一:隊列
     *  參數(shù)二:要傳給函數(shù)的參數(shù)
     *  參數(shù)三:函數(shù)
     */
    dispatch_sync_f(dispatch_get_global_queue(0, 0), NULL, testTask);
}

void testTask(void *param)
{
    NSLog(@"%@", [NSThread currentThread]);
}

上面使用的是函數(shù)來封裝要處理的代碼,使用比較不方便班缰,且block是輕量級的數(shù)據(jù)結(jié)構(gòu)贤壁,更推薦使用block封裝代碼的形式創(chuàng)建同步\異步函數(shù)。

GCD一些需要注意的細節(jié)

  • 全局并發(fā)隊列是默認存在的(在我們程序運行的時候就存在)
  • 全局隊列根據(jù)隊列的優(yōu)先級分為 (高埠忘,默認脾拆,低,后臺優(yōu)先級)4個并發(fā)隊列
  • iOS 6之前莹妒,我們通過創(chuàng)建的線程名船,是要自己手動施放的
    • 施放的方式 —— dispatch_release()
  • 使用柵欄函數(shù),蘋果官方文檔明確規(guī)定柵欄函數(shù)只有在和使用create函數(shù)創(chuàng)建的筆法隊列一起使用才有效
  • 暫時就想到這么多O(∩_∩)O旨怠,因為GCD已經(jīng)開源渠驼,想研究的朋友可以到網(wǎng)上搜索一下,有哪里不對的可以聯(lián)系我鉴腻,謝謝迷扇!

NSOperation簡單使用

NSOperation作用

  • 配合使用NSOperation和NSOperationQueue也能實現(xiàn)多線程編程

NSOperation和NSOperationQueue實現(xiàn)多線程的具體步驟

  • 先將需要執(zhí)行的操作封裝到一個NSOperation對象中
  • 然后將NSOperation對象添加到NSOperation對象中
  • 系統(tǒng)會自動將NSOperationQueue中的NSOperation取出來
  • 將取出來的NSOperation封裝的操作放到一條新線程中執(zhí)行

NSOperation的子類

  • NSOperation是個抽象類百揭,并不具備封裝操作的能力,必須使用它的子類
  • 使用NSOperation子類的方式有3種
    • NSInvocationOperation
    • NSBlockOperation
    • 自定義子類繼承NSOperation,實現(xiàn)內(nèi)部相應的方法

NSOperation封裝操作

  • 第一種方式 —— NSInvocationOperation
- (void)invocationTest
{
    /**
     * 參數(shù)一:目標對象
     * 參數(shù)二:調(diào)用方法
     */
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil];
    
    //  開啟任務
    [op1 start];
}

- (void)download
{
    NSLog(@"下載:%@",[NSThread currentThread]);
}

執(zhí)行結(jié)果:需要和隊列并用才會開啟子線程執(zhí)行任務


NSInvocationOperation
  • 第二種方式 —— Block
- (void)blockTest
{
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下載:%@",[NSThread currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下載:%@",[NSThread currentThread]);
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下載:%@",[NSThread currentThread]);
    }];
    
    [op1 addExecutionBlock:^{
        NSLog(@"增加的下載:%@", [NSThread currentThread]);
    }];
    
    //  開啟任務
    [op1 start];
    [op2 start];
    [op3 start];
}

- (void)download
{
    NSLog(@"下載:%@",[NSThread currentThread]);
}

執(zhí)行結(jié)果:如果一條線程中執(zhí)行的操作大于1就會開啟新線程并發(fā)執(zhí)行


NSBlockOperation
  • 方式三 —— 自定義NSOperation

1.先創(chuàng)建一個繼承自NSOperation的類并重寫main方法


- (void)main
{
    NSLog(@"當前線程:%@", [NSThread currentThread]);
}

2.在需要使用的類中引用自定義的類,并創(chuàng)建開啟任務

- (void)custom
{
    SJOperation *op1 = [[SJOperation alloc] init];
    
    [op1 start];
}

執(zhí)行結(jié)果:需要手動開啟線程或者與隊列并用才會開啟子線程


自定義NSOperation

NSOperation中的隊列

  • 主隊列 (獲取方式:+mainQueue)
    • 所有在主隊列中的任務都在主線程中執(zhí)行
    • 本質(zhì)上是串行隊列
- (void)invocationQueue
{
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download1) object:nil];
    
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download2) object:nil];
    
    NSInvocationOperation *op3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download3) object:nil];
    
    //  獲取主隊列
    NSOperationQueue *queue = [NSOperationQueue mainQueue];
    
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    
}

執(zhí)行結(jié)果:所有任務都在主隊列中執(zhí)行蜓席,且是串行隊列

NSInvocationOperation在主隊列使用情況
  • 非主隊列(獲取方式:alloc init)
    • 同時具備并發(fā)和串行功能
    • 默認下是并發(fā)的
- (void)invocationQueue
{
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download1) object:nil];
    
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download2) object:nil];
    
    NSInvocationOperation *op3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download3) object:nil];
    
    //  獲取非主隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];
    
}

執(zhí)行結(jié)果:所有任務在子線程中并發(fā)執(zhí)行

NSInvocationOperation在非主隊列使用情況

注意:addOperation:內(nèi)部已經(jīng)幫我們執(zhí)行了開啟任務方法器一,所有不需要另外實現(xiàn)。


NSBlockOperation與隊列并用的簡單寫法

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    [queue addOperationWithBlock:^{
        NSLog(@"下載:%@",[NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        NSLog(@"下載:%@",[NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        NSLog(@"下載:%@",[NSThread currentThread]);
    }];

執(zhí)行結(jié)果:所有任務都在子線程中并發(fā)執(zhí)行

NSBlockOperation在隊列中的簡單寫法

設置最大并發(fā)數(shù)

  • 在NSOperation中厨内,我們要想控制串行隊列或者并發(fā)隊列祈秕,只需要設置maxConcurrentOperationCount屬性即可
    • 一般我們要使用串行隊列,只需設置值為1即可
    • 如果值大于1雏胃,則為并發(fā)隊列

1.串行隊列示例

- (void)blockQueue
{
    //  創(chuàng)建非主隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //  設置最大并發(fā)數(shù)為1踢步,則隊列為串行隊列
    queue.maxConcurrentOperationCount = 1;
    
    [queue addOperationWithBlock:^{
        NSLog(@"下載1:%@",[NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        NSLog(@"下載2:%@",[NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        NSLog(@"下載3:%@",[NSThread currentThread]);
    }];
    
}

執(zhí)行結(jié)果:按照任務添加順序執(zhí)行,所以是串行隊列


NSOperationQueue串行執(zhí)行

2.并發(fā)隊列示例

- (void)blockQueue
{
    //  創(chuàng)建非主隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //  設置最大并發(fā)數(shù)為6丑掺,一般子線程控制在6以內(nèi)获印,太多線程會使設備壓力過大
    queue.maxConcurrentOperationCount = 6;
    
    [queue addOperationWithBlock:^{
        NSLog(@"下載1:%@",[NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        NSLog(@"下載2:%@",[NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        NSLog(@"下載3:%@",[NSThread currentThread]);
    }];
    
}

執(zhí)行結(jié)果:程序并沒有按照添加順序完成任務,所以是并發(fā)執(zhí)行

NSOperationQueue并發(fā)執(zhí)行

注意:

  1. 一般子線程控制在6以內(nèi)街州,太多線程會使設備壓力過大
  2. maxConcurrentOperationCount默認值為-1(在計算機中兼丰,-1一般指最大值)
  3. 如果將maxConcurrentOperationCount設置為0,說明同一時間內(nèi)執(zhí)行0個任務唆缴,所以任務將不會執(zhí)行鳍征。
maxConcurrentOperationCount系統(tǒng)默認值

NSOperation暫停、恢復和取消功能

  • 在NSOperation中面徽,已經(jīng)為我們提供了暫停艳丛、恢復和取消的功能,我們只需調(diào)用相應的方法即可趟紊。

1.暫停

    //  暫停
    [queue setSuspended:YES];

2.恢復

    //  取消
    [queue setSuspended:NO];

3.取消

    //  取消隊列中所有操作,且取消后的任務不可恢復
    [queue cancelAllOperations];

注意:

1.隊列中的的任務是有狀態(tài)的氮双,分別是 —— 等待;執(zhí)行霎匈;完成三種狀態(tài)戴差,且暫停、恢復和取消操作并不能作用于當前正處于執(zhí)行狀態(tài)的任務铛嘱,只能作用于等待狀態(tài)的任務暖释。
2.如果是自定義的NSOperation,會發(fā)現(xiàn)暫停墨吓、恢復操作對其無效球匕,對于這種情況,可以用以下方式解決 —— 使用取消操作

- (void)main
{
    //  模擬耗時操作
    for (int i = 0; i< 200; i++) {
        NSLog(@"1當前線程:%@", [NSThread currentThread]);
    }
    //  判斷當前狀態(tài)帖烘,如果已經(jīng)取消亮曹,直接返回
    if (self.cancelled) return;
    
    //  模擬耗時操作
    for (int i = 0; i< 200; i++) {
        NSLog(@"2當前線程:%@", [NSThread currentThread]);
    }
    //  判斷當前狀態(tài),如果已經(jīng)取消,直接返回
    if (self.cancelled) return;
    
    //  模擬耗時操作
    for (int i = 0; i< 200; i++) {
        NSLog(@"3當前線程:%@", [NSThread currentThread]);
    }
    //  判斷當前狀態(tài)乾忱,如果已經(jīng)取消讥珍,直接返回
    if (self.cancelled) return;
}

解決問題思路:其實這是蘋果官方文檔中的建議 —— 因為,當我們調(diào)用cancelAllOperations:方法的時候窄瘟,他內(nèi)部的cancelled屬性就會為真衷佃,每執(zhí)行完一個耗時操作后都進行一次判斷,如果發(fā)現(xiàn)已經(jīng)取消蹄葱,則退出執(zhí)行氏义。如果想更精確操控的話,也可以將判斷操作放到耗時操作中图云,但是不建議這樣做惯悠,因為這樣性能極差。


NSOperation中的依賴操作

  • NSOperation提供了一套非常便捷好用的操作依賴方式竣况,比起GCD克婶,那種酸爽簡直不敢相信
- (void)blockQueue
{
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下載1:%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下載2:%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"下載3:%@",[NSThread currentThread]);
    }];
    
    //  設置依賴關系
    //  op1依賴op2,只有當op2執(zhí)行完畢后丹泉,才會執(zhí)行op1
    [op1 addDependency:op2];
    //  op2依賴op3情萤,只有當op3執(zhí)行完畢后,才會執(zhí)行op2
    [op2 addDependency:op3];
    
    //  獲取主隊列
    NSOperationQueue *queue = [NSOperationQueue mainQueue];
    
    [queue addOperation:op1];
    [queue addOperation:op2];
    [queue addOperation:op3];

執(zhí)行結(jié)果:先執(zhí)行完op3摹恨,等op3執(zhí)行完成后才執(zhí)行op2筋岛,當op2執(zhí)行完畢后,才執(zhí)行op1

NSOperation操作依賴

注意

  • NSOperation提供的操作依賴功能特別強大晒哄,可以設置不同隊列的依賴
  • 但是不能循環(huán)依賴睁宰,比如op1依賴op2,op2又依賴op1寝凌,而且并不會報錯柒傻,但會發(fā)生死鎖,且有關任務都不執(zhí)行硫兰。

NSOperation的監(jiān)聽

  • 我們經(jīng)常有這樣的需要:在某些任務執(zhí)行完成后诅愚,再執(zhí)行指定的某些操作,那么NSOperation中的監(jiān)聽功能就派上用場了,使用非常簡單
    NSOperation *op = [[NSOperation alloc] init];
    
    op.completionBlock = ^{
        NSLog(@"下載完成");
    };

    [op start];

NSOperation線程間通信

  • NSOperation線程間的通信類似于GCD劫映,所以就不多敘述了,直接上代碼
- (void)downloadPhoto
{
    //  獲取非主隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    //  創(chuàng)建下載任務
    [queue addOperationWithBlock:^{
       
        //  圖片地址
        NSURL *url = [NSURL URLWithString:@"http://cdn.duitang.com/uploads/item/201512/05/20151205092106_aksZU.jpeg"];
        //  下載圖片
        NSData *imageData = [NSData dataWithContentsOfURL:url];
        //  轉(zhuǎn)換圖片
        UIImage *image = [UIImage imageWithData:imageData];
        //  回到主線程刷新
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            
            NSLog(@"回%@線程刷新UI", [NSThread currentThread]);
            
            self.imageView.image = image;
        }];
        
    }];
}

執(zhí)行結(jié)果:

NSOperation線程間通信.gif

GCD和NSOperation區(qū)別

在開發(fā)中最常用的就是GCD和NSOperation來進行多線程開發(fā)刹前,NSThread更多是在測試時輔助使用泳赋,pthread則很少看見,這里為大家簡單整理一下他們之間的區(qū)別

  • GCD和NSOperation的對比

    • GCD是純C語言的API喇喉,而操作隊列(NSOperation)則是Object-C的對象
    • 在GCD中祖今,任務用Block塊來表示,而塊是輕量級的數(shù)據(jù)結(jié)構(gòu),相反千诬,操作隊列(NSOperation)中的操作NSOperation是比較重量級的Object-C對象
  • 那么在開發(fā)中如何選擇呢耍目?

    • 一般如果任務之間有依賴關系或者需要監(jiān)聽任務執(zhí)行的過程(KVO),首選NSOperation
    • 單純進行一些耗時操作則選用GCD徐绑,因為相比NSOperation,GCD效率更高邪驮,性能更好
  • NSOperation和NSOperationQueue好處

    • NSOperation可以方便設置操作優(yōu)先級(表示操作在隊列中與其它操作之間的優(yōu)先關系,級別越高越先執(zhí)行)
    • NSOperation可以通過KVO的方式對NSOperation對象進行監(jiān)聽控制(監(jiān)聽當前操作是處于完成傲茄,取消還是執(zhí)行狀態(tài))
    • NSOperation可以方便設置操作之間的依賴關系
    • 通過自定義NSOperation子類可以實現(xiàn)操作復用
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末毅访,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子盘榨,更是在濱河造成了極大的恐慌喻粹,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件草巡,死亡現(xiàn)場離奇詭異守呜,居然都是意外死亡,警方通過查閱死者的電腦和手機山憨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門弛饭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人萍歉,你說我怎么就攤上這事侣颂。” “怎么了枪孩?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵憔晒,是天一觀的道長。 經(jīng)常有香客問我蔑舞,道長拒担,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任攻询,我火速辦了婚禮从撼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘钧栖。我一直安慰自己低零,他們只是感情好,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布拯杠。 她就那樣靜靜地躺著掏婶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪潭陪。 梳的紋絲不亂的頭發(fā)上雄妥,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天最蕾,我揣著相機與錄音,去河邊找鬼老厌。 笑死瘟则,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的枝秤。 我是一名探鬼主播醋拧,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼宿百!你這毒婦竟也來了趁仙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤垦页,失蹤者是張志新(化名)和其女友劉穎雀费,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痊焊,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡盏袄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了薄啥。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辕羽。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖垄惧,靈堂內(nèi)的尸體忽然破棺而出刁愿,到底是詐尸還是另有隱情,我是刑警寧澤到逊,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布铣口,位于F島的核電站,受9級特大地震影響觉壶,放射性物質(zhì)發(fā)生泄漏脑题。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一铜靶、第九天 我趴在偏房一處隱蔽的房頂上張望叔遂。 院中可真熱鬧,春花似錦争剿、人聲如沸已艰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽旗芬。三九已至,卻和暖如春捆蜀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工辆它, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留誊薄,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓锰茉,卻偏偏與公主長得像呢蔫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子飒筑,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

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

  • 原文:http://www.cocoachina.com/ios/20170707/19769.html 本文主要...
    冬的天閱讀 2,238評論 0 12
  • 從哪說起呢补憾? 單純講多線程編程真的不知道從哪下嘴漫萄。。 不如我直接引用一個最簡單的問題盈匾,以這個作為切入點好了 在ma...
    Mr_Baymax閱讀 2,734評論 1 17
  • 多線程 在iOS開發(fā)中為提高程序的運行效率會將比較耗時的操作放在子線程中執(zhí)行腾务,iOS系統(tǒng)進程默認啟動一個主線程,用...
    郭豪豪閱讀 2,586評論 0 4
  • 歡迎大家指出文章中需要改正或者需要補充的地方削饵,我會及時更新岩瘦,非常感謝。 一. 多線程基礎 1. 進程 進程是指在系...
    xx_cc閱讀 7,171評論 11 70
  • Object C中創(chuàng)建線程的方法是什么葵孤?如果在主線程中執(zhí)行代碼担钮,方法是什么?如果想延時執(zhí)行代碼尤仍、方法又是什么箫津? 1...
    AlanGe閱讀 1,716評論 0 17