淺析 iOS 多線程

一、基本知識

1. 進(jìn)程

  • 系統(tǒng)中正在運(yùn)行的一個程序
  • 每個進(jìn)程之間相互獨(dú)立「運(yùn)行在其專用且受保護(hù)的內(nèi)存空間內(nèi)」
  • 進(jìn)程之間可以相互通訊

2. 線程

  • 1 個進(jìn)程想執(zhí)行任務(wù),必須得有 1 條線程「每個進(jìn)程至少有一條線程」
  • 1 個進(jìn)程所有的任務(wù)都在線程里執(zhí)行
  • 1 個線程的任務(wù)的執(zhí)行都是串行「按順序多個任務(wù)孙咪,同一時間內(nèi) 1個線程只執(zhí)行 1個任務(wù)」

I. 線程的狀態(tài)

  • 啟動線程 -(void)start;
  • 阻塞線程
// 讓線程阻塞多久不做任務(wù)
+ (void)sleepUntilDate:(NSDate *)date;
// 示例 1:
[NSThread sleepUntilDate: [NSDate distanceFuture]]; //永遠(yuǎn)阻塞「當(dāng)前語句所在的線程」
[NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow:2]]; //阻塞 2秒「單位是秒,當(dāng)前語句所在的線程」

+ (void)sleepForTimeInterval:(NSTimeInterval) ti;
// 示例2:
[NSThread sleepForTimeInterval:2]; //阻塞 2秒「單位是秒粪摘,當(dāng)前語句所在線程」
  • 強(qiáng)制停止線程 +(void)exit;
    注意:一旦線程死亡了击喂,不能在開啟任務(wù),只能重新創(chuàng)建新的線程來完成任務(wù)

II. 線程的通信

定義:

  • 1個線程傳遞數(shù)據(jù)給另 1個線程
  • 在 1個線程中執(zhí)行完成特定任務(wù)后论笔,轉(zhuǎn)到 另一個線程 繼續(xù) 執(zhí)行任務(wù)

線程間的通信方法:

// 跳轉(zhuǎn)回 主線程 的 哪個方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
// 跳轉(zhuǎn)到 哪個線程的 哪個方法
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;

// 利用 NSPort(端口對象) 通訊「了解」
// A線程操作 B線程采郎,需要 B將 1個Port對象 給A,A通過 Port對象 操作 B

3. 多線程「掌握」

定義:1個進(jìn)程 開啟多條線程狂魔,每條線程可以 并行「同時」執(zhí)行不同的任務(wù)
原理:

  • 同一時間 CPU只處理 1條線程
  • 多線程并發(fā)「同時」執(zhí)行蒜埋,其實(shí)是 CPU快速的在多條線程間調(diào)度「切換」

優(yōu)點(diǎn):適當(dāng)提高 程序執(zhí)行效率 和 資源利用率「CPU、內(nèi)存利用率」
缺點(diǎn):

  • 創(chuàng)建線程有開銷「創(chuàng)建線程大約需要90毫秒的創(chuàng)建時間」
    iOS的主要開銷
    • 內(nèi)核數(shù)據(jù)結(jié)構(gòu)「大約 1KB」
    • 椬羁空間「子線程 521KB整份,主線程 1MB」
  • 線程過多 會降低程序性能
  • 線程過多 CPU在線程上的開銷越大
  • 使用多線程,程序設(shè)計(jì)更加復(fù)雜「線程通信籽孙、多線程數(shù)據(jù)共享」
  • 多個線程訪問同一個資源的時候會引發(fā) 數(shù)據(jù)錯亂數(shù)據(jù)安全 問題

I. 主線程

定義:

  • 程序運(yùn)行后烈评,默認(rèn)開啟 1條線程叫主線程「UI線程」
  • 主要作用:顯示、刷新UI界面犯建、處理UI事件

注意:耗時操作放在主線程會卡住主線程讲冠,嚴(yán)重影響UI流暢度

II. 互斥鎖「使用線程同步技術(shù)」

線程同步 定義:多條線程在同一條線上執(zhí)行「按順序的執(zhí)行任務(wù)」
互斥鎖 格式:@synchronized ( 鎖對象 ){ 需要鎖定的代碼 }
注意:

  • 使用前提:多線程共同搶奪同一個線程
  • 鎖對象可以是任意對象「一般都是 self,即調(diào)用此方法的單例對象本身」
  • 鎖定 1份代碼适瓦,多個線程共用 1個鎖對象「多個鎖對象無效」

優(yōu)點(diǎn):防止多線程搶奪資源造成的數(shù)據(jù)安全問題
缺點(diǎn):需要消耗大量的 CPU資源「一般不提倡使用」

III. 原子和非原子屬性

在 @property 的屬性中有

  • 原子屬性 atomic:為 setter方法加同步鎖「@property 屬性 默認(rèn)是 加鎖」
    特點(diǎn):線程安全竿开,但需要消耗大量的資源
  • 非原子屬性 nonatomic:不加 同步鎖
    特點(diǎn):非線程安全,適合內(nèi)存小的移動設(shè)備

注:加鎖玻熙、資源搶奪一般交給服務(wù)器處理否彩,減少客戶端壓力

二、iOS中多線程的實(shí)現(xiàn)方案

1. pthread「了解」

// 創(chuàng)建線程
#import<pthread.h>
pthread_t thread;
pthread_create(pthread_t *restrict, const pthread_attr_t *restrict, void *(*)(void *), void *restrict);

2. NSThread 「掌握」

I. 基本用法

  • + (NSThread *)currentThread;獲得當(dāng)前線程
  • + (NSThread *)mainThread; 獲得主線程
  • - (BOOL)isMainThread; 查看 當(dāng)前方法所在的類 是否為主線程
  • + (BOOL)isMainThread; 查看 當(dāng)前方法所在的方法 是否為主線程

II. 創(chuàng)建和啟動線程

  • 1 個NSThread對象就是 1條線程
  • 雖然這里的線程是局部變量揭芍,可是當(dāng)線程開啟后胳搞,線程會直到任務(wù)完成后銷毀
  • 線程任務(wù)完成后會處于消亡狀態(tài),不能再次開啟称杨。只能重新創(chuàng)建新的線程在執(zhí)行任務(wù)肌毅。
// 方法1. 創(chuàng)建線程
NSThread *thread_1 = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"run方法的參數(shù),obj類型"];
// 以下這步可以省略
thread_1.name = @"這條線程的名字"; 
// 啟動線程「線程一啟動姑原,就會在線程thread中執(zhí)行 self的 run方法」
[thread start];

// 注:以下兩種方法 不返回線程對象
// 優(yōu)點(diǎn):簡單快捷
// 缺點(diǎn):無法對線程進(jìn)行詳細(xì)的設(shè)置

// 方法2. 創(chuàng)建后自動分離出線程悬而,并啟動
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"run方法的參數(shù), obj類型"];

// 方法3. 隱式創(chuàng)建,并啟動后臺線程
[self performSelectorInBackground:@selector(run) withObject:nil];

3. GCD「Grand Central Dispatch 宏大的中樞調(diào)度器」

優(yōu)勢

  • 純 C 語言有許多強(qiáng)大的函數(shù)
  • 進(jìn)行了 iOS 系統(tǒng)級的優(yōu)化
  • 針對多核并行運(yùn)算提出的解決方案「會自動利用更多的CPU內(nèi)核」
  • 自動管理線程的生命周期「創(chuàng)建線程锭汛、調(diào)度任務(wù)笨奠、銷毀線程」

使用步驟

  1. 創(chuàng)建隊(duì)列或使用系統(tǒng)提供的隊(duì)列
  2. 定制任務(wù)「線程操作」
  3. 將任務(wù)添加到 隊(duì)列「存放任務(wù)」

1) 任務(wù)

任務(wù)的執(zhí)行方式「GCD會自動從隊(duì)列中取出任務(wù)袭蝗,放到對應(yīng)線程中執(zhí)行」

I. 同步任務(wù)「synchronize」

簡介

  • 只能在當(dāng)前線程中執(zhí)行任務(wù),不能 擅自開啟新線程
  • 注:當(dāng)前任務(wù)中 添加了同步函數(shù)般婆,先執(zhí)行完 同步函數(shù)到腥,后執(zhí)行 同步函數(shù)后的當(dāng)前任務(wù)操作
    在當(dāng)前隊(duì)列添加 同步任務(wù),會阻塞當(dāng)前隊(duì)列

執(zhí)行步驟

  1. 阻塞當(dāng)前線程蔚袍,保持和當(dāng)前線程的同步
  2. 執(zhí)行同步任務(wù)中的任務(wù)
  3. 回到當(dāng)前隊(duì)列乡范,使當(dāng)前隊(duì)列不在阻塞,執(zhí)行當(dāng)前隊(duì)列的任務(wù)

示例結(jié)果:1啤咽、2晋辆、3

// block:需要執(zhí)行的任務(wù)
// dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

NSLog(@"1. %@", [NSThread currentThread]);
dispatch_queue_t newQueue = dispatch_queue_create("test.testName.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(newQueue, ^{
    NSLog(@"2. %@", [NSThread currentThread]);
});
NSLog(@"3. %@", [NSThread currentThread]);

II. 異步任務(wù)「asynchronous」

簡介

  • 可以在新的線程執(zhí)行任務(wù), 擅自開啟新線程
  • 注:當(dāng)前任務(wù)中 添加異步函數(shù)宇整,先執(zhí)行完 當(dāng)前任務(wù)瓶佳,后 執(zhí)行異步函數(shù)

執(zhí)行步驟

  1. 執(zhí)行當(dāng)前線程「不會阻塞當(dāng)前線程」
  2. 執(zhí)行異步任務(wù)

示例結(jié)果:1、3鳞青、2

// dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
// 每個有block的函數(shù)都有對應(yīng)的 function函數(shù)
// dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);

NSLog(@"1. %@", [NSThread currentThread]);
dispatch_queue_t newQueue = dispatch_queue_create("test.testName.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(newQueue, ^{
    NSLog(@"2. %@", [NSThread currentThread]);
});
NSLog(@"3. %@", [NSThread currentThread]);

2)隊(duì)列

I. 串行隊(duì)列「Serial Dispatch Queue」

  • 任務(wù)一個接著一個執(zhí)行
  • 代表:**主線程隊(duì)列 **「不受 retain 和 release 的影響」
    放到主隊(duì)列中的任務(wù)霸饲,會在主線程,UI界面的線程中執(zhí)行
// 1. 獲得主隊(duì)列
dispatch_queue_t qu = dispatch_get_main_queue();

// 2. 將任務(wù)加入隊(duì)列
// 2.1 主隊(duì)列+異步函數(shù):串行執(zhí)行盼玄,不能開啟新線程
dispatch_async(qu, ^{ /*任務(wù)*/ });

// 2.2 主隊(duì)列+同步函數(shù):串行執(zhí)行贴彼,不能開啟新線程「如果執(zhí)行的函數(shù)也在主隊(duì)列則,隊(duì)列阻塞埃儿,都無法執(zhí)行」
dispatch_sync(qu, ^{ /*任務(wù)*/ });
  • 自己創(chuàng)建 1個串行隊(duì)列,隊(duì)列優(yōu)先級默認(rèn)為 DISPATCH_QUEUE_PRIORITY_DEFAULT
// 1. 創(chuàng)建 1個串行隊(duì)列
// label: 隊(duì)列名稱「格式: 作用.名稱.queue」融涣,隊(duì)列名稱會在 CrashLog 里標(biāo)明崩潰的隊(duì)列名稱
// dispatch_queue_attr_t: 隊(duì)列類型「并發(fā)/同步」
// dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
// 屬性:DISPATCH_QUEUE_SERIAL 等價(jià)于 NULL
dispatch_queue_t qu = dispatch_queue_create("down.Miao.queue", DISPATCH_QUEUE_SERIAL);

// 2. 將任務(wù)加入隊(duì)列
// 2.1 串行隊(duì)列+異步函數(shù):不能并發(fā)童番,可以開啟多線程
// 異步函數(shù):任務(wù)執(zhí)行完成后,不等待 任務(wù)執(zhí)行的隊(duì)列結(jié)束就返回得出的結(jié)果
dispatch_async(qu, ^{ /*任務(wù)*/ });

// 2.2 串行隊(duì)列+同步函數(shù):能并發(fā)威鹿,不會開啟新線程
// 同步函數(shù):任務(wù)執(zhí)行完成后剃斧,等待 任務(wù)執(zhí)行的隊(duì)列結(jié)束就返回得出的結(jié)果
dispatch_sync(qu, ^{ /*任務(wù)*/ });

// 3. 自己創(chuàng)建的隊(duì)列需要手動釋放「MRC模式」
dispatch_release(qu); // 當(dāng)然 也有 dispatch_retain() 方法

II. 并發(fā)隊(duì)列「Concurrent Dispatch Queue」

  • 讓多個任務(wù) 并發(fā)(同時) 進(jìn)行「自動開啟多線程同時執(zhí)行任務(wù)」
    并發(fā)只有在 異步函數(shù) dispatch_async 下有效
  • 代表:全局并發(fā)隊(duì)列 「不受 retain 和 release 的影響」
  • 所有應(yīng)用程序都能使用并發(fā)隊(duì)列
// 有 4 個執(zhí)行優(yōu)先級:
// - 高  :DISPATCH_QUEUE_PRIORITY_HIGH
// - 默認(rèn):DISPATCH_QUEUE_PRIORITY_DEFAULT
// - 低  :DISPATCH_QUEUE_PRIORITY_LOW
// - 后臺:DISPATCH_QUEUE_PRIORITY_BACKGROUND
dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
// 不是自己創(chuàng)建的對象無法手動釋放
  • 自己創(chuàng)建 1個并發(fā)隊(duì)列
// 1. 創(chuàng)建 1個并發(fā)隊(duì)列
dispatch_queue_t qu = dispatch_queue_create("com.Miao.queue", DISPATCH_QUEUE_CONCURRENT);

// 2. 將任務(wù)加入并發(fā)隊(duì)列
// 2.1 并行隊(duì)列+異步函數(shù):可以并發(fā),可以開啟新線程
dispatch_async(qu, ^{ /*任務(wù)*/ });

// 2.2 并行隊(duì)列+同步函數(shù):不能并發(fā)忽你,不會開啟新線程
dispatch_sync(qu, ^{ /*任務(wù)*/ });

III. 變更線程的優(yōu)先級

  • 使用 dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);
    多個串行隊(duì)列的優(yōu)先級都變更到同一隊(duì)列的優(yōu)先級幼东,會導(dǎo)致
    原本互相可以并行的多個串行隊(duì)列,在目標(biāo)隊(duì)列上只能同時執(zhí)行一個處理
dispatch_set_target_queue(/*要設(shè)置優(yōu)先級的對列*/, /*與期望的優(yōu)先級相同的隊(duì)列*/);

IV. 線程通訊

示例:下載圖片

dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 1. 耗時的異步操作
    // 獲取圖片網(wǎng)絡(luò)路徑
    NSURL *url = [NSURL URLWithString:@"圖片網(wǎng)絡(luò)路徑/圖片名.圖片格式"];
    // 加載圖片
    NSData *data = [NSData dataWithContentsOfURL: url];
    // 生成圖片
    UIImage *image = [UIImage imageWithData:data];

    // 2. 回到主線程「最好用異步函數(shù)」
    dispatch_async( dispatch_get_main_queue(), ^{
        self.imageView.image = image;
    });
});

3)信號量

信號:多線程中的計(jì)數(shù)器科雳,計(jì)數(shù)為 0 時線程等待根蟹,計(jì)數(shù)為 1 或者大于 1 時,減去 1 而不等待
示例:由于信號量為 10 在這個并行隊(duì)列里糟秘,最多會有 10 個任務(wù)被執(zhí)行

// 創(chuàng)建信號量简逮,其值設(shè)為 10
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);

for (int i = 0; i < 100; i++) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 超時后,信號量會失效
        // 信號量為 0尿赚,線程被阻塞散庶;信號量 不為 0蕉堰,信號總量 -1
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);        
        NSLog(@"__%d__",i);
        // 休眠
        [NSThread sleepForTimeInterval:3];
        // 信號量 +1
        dispatch_semaphore_signal(semaphore);
    });
}

4) GCD 其他方法

I. 障礙函數(shù)

使用限制

// 此方法阻塞的是 傳入的 queue「不是當(dāng)前線程」
// 參數(shù) queue 不能是 全局的并發(fā)隊(duì)列
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

執(zhí)行步驟

  1. 阻塞傳入的線程 queue
  2. queue 前面的任務(wù)執(zhí)行完畢,執(zhí)行 障礙函數(shù)
  3. 取消對線程 queue 的阻塞
dispatch_async(queue, ^{ /* 讀 */ });
dispatch_async(queue, ^{ /* 讀 */ });
dispatch_barrier_async(queue, ^{ /* 并行讀入完成悲龟,開始并行寫入 */ });
dispatch_async(queue, ^{ /* 寫 */ });
dispatch_async(queue, ^{ /* 寫 */ });

II. 延時函數(shù)「不影響現(xiàn)有的線程繼續(xù)執(zhí)行」

  • NSObject 方法:
    [self performSelector:@selector(函數(shù)1:) withObject:函數(shù)1的參數(shù) afterDelay: 2.0];延遲 2秒
  • NSTimer 方法:
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(函數(shù)1:) userInfo:nil repeats:NO];
  • GCD 函數(shù)「非循環(huán)」:
dispatch_after( dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), 
    // 2秒后到主線程執(zhí)行任務(wù)
    dispatch_get_main_queue(), ^{
    // 2秒后異步執(zhí)行這里的代碼...
});
  • GCD 定時器「循環(huán)」:GCD 的定時器不受 RunLoop 的 Mode影響
int count = 0;
// 1. 獲得隊(duì)列
//    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue = dispatch_get_main_queue();
    
// 2. 創(chuàng)建一個定時器(dispatch_source_t本質(zhì)還是個OC對象)
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    
// 3. 設(shè)置定時器的各種屬性(幾時開始任務(wù)屋讶,每隔多長時間執(zhí)行一次)
//    GCD的時間參數(shù),一般是納秒(1秒 == 10的9次方納秒)
//    何時開始執(zhí)行第一個任務(wù)
//    dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC) 比當(dāng)前時間晚3秒
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
dispatch_source_set_timer(self.timer, start, interval, 0);
    
// 4. 設(shè)置回調(diào)
dispatch_source_set_event_handler(self.timer, ^{
    NSLog(@"------------%@", [NSThread currentThread]);
    count++;
    if (count == 4) {
        // 6. 取消定時器
        dispatch_cancel(self.timer);
        self.timer = nil;
     }
});    
// 5. 啟動定時器
dispatch_resume(self.timer);

III. 一次性代碼「保證代碼在程序運(yùn)行中须教,只執(zhí)行一次」

static dispatch_one_t onceToken; // 標(biāo)記是否執(zhí)行的過
dispatch_once(&onceToken, ^{ /*要只執(zhí)行一次的代碼塊丑婿,線程安全的已經(jīng)加鎖了*/ });

IV. 快讀迭代「可以多線程同時遍歷」

dispatch_queue_t qu = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 10為遍歷的數(shù)組長度
dispatch_apply(10, qu,^(size_t index){
    // 遍歷需要的操作
});

VI. 隊(duì)列組

作用

  • 首先,異步執(zhí)行多個耗時操作
  • 其次没卸,等之前的操作全部完畢后羹奉,再回到主線程執(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í)行2個耗時的異步操作
});

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

4. NSOperation「底層是GCD约计,做了面向?qū)ο蟮姆庋b」

作用

  • 配合使用 NSOperation 和 NSOperationQueue 也能實(shí)現(xiàn)多線程編程
  • NSOperation 是抽象類诀拭,并不具備封裝操作的能力,必須使用他的子類

1)NSOperation 的子類「了解」

I. NSInovcationOperation

注意:

  • 默認(rèn)煤蚌,調(diào)用 start方法后 不會開啟一條新線程執(zhí)行耕挨,在當(dāng)前線程同步執(zhí)行操作
  • 只有將 NSOperation 放到 NSOperationQueue 里,才會執(zhí)行異步操作
// 創(chuàng)建NSInvocationOperation對象
- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;

// 調(diào)用start方法開始執(zhí)行操作
// 一旦執(zhí)行操作尉桩,就會調(diào)用target的sel方法
- (void)start;

II. NSBlockOperation

注意:只要NSBlockOperation封裝的操作數(shù) > 1筒占,就會異步執(zhí)行操作

// 創(chuàng)建NSBlockOperation對象
// 默認(rèn)在當(dāng)前線程執(zhí)行
+ (id)blockOperationWithBlock:(void (^)(void))block;

// 通過addExecutionBlock:方法添加更多的操作
// 添加額外的任務(wù)會開啟新的線程異步執(zhí)行
- (void)addExecutionBlock:(void (^)(void))block;

III. 自定義類「繼承自NSOperation」

  • 要實(shí)現(xiàn) NSOperation中的 -(void)main
    將自定義類添加到 NSOperationQueue 中時會自動調(diào)用 實(shí)現(xiàn)的main函數(shù)
    其中,要自己創(chuàng)建自動釋放池(因?yàn)槿绻钱惒讲僮髦├纾瑹o法訪問主線程的自動釋放池)
  • -(BOOL)isConcurrent 是否能夠并發(fā)執(zhí)行
  • -(BOOL)isCancelled 檢測操作是否被取消翰苫,對取消做出響應(yīng)

還有很多等等

2)NSOperationQueue「掌握」

作用:將 NSOperation 添加到 NSOperationQueue 中,系統(tǒng)會自動異步執(zhí)行 NSOperation 中的操作

I. NSOperationQueue的隊(duì)列類型

  • 主隊(duì)列 [NSOperationQueue mainQueue]
    凡是添加到主隊(duì)列中的任務(wù)这橙,都會放到主線程中執(zhí)行

  • 非主隊(duì)列 [[NSOperationQueue alloc] init]
    同時包含了:串行奏窑、并發(fā)功能
    添加到這種隊(duì)列,就會自動放到子線程中執(zhí)行

// 1. 創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

// 設(shè)置最大并發(fā)數(shù)屈扎,如果為 1 則為串行
queue.maxConcurrentOperationCount = 1;

// 2. 創(chuàng)建操作(任務(wù))
// 2.1 創(chuàng)建NSInvocationOperation
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download1) object:nil];
   
// 2.2 創(chuàng)建NSBlockOperation
// 快速創(chuàng)建并添加方式
[queue addOperationWithBlock:^{
    NSLog(@"download5 --- %@", [NSThread currentThread]);
}];
// 普通方式
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"download2 --- %@", [NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
    NSLog(@"download4 --- %@", [NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
    NSLog(@"download5 --- %@", [NSThread currentThread]);
}];

// 2.3 創(chuàng)建自定義任務(wù)
Person *op3 = [[Person alloc] init];
  
// 3. 添加任務(wù)到隊(duì)列中
[queue addOperation:op1]; // 不用調(diào)用start方法埃唯,自動開啟線程
[queue addOperation:op2]; 
[queue addOperation:op3]; 

II. NSOperationQueue 的方法

  • - (void)cancelAllOperations;
    取消所有隊(duì)列的操作,會移除 所有隊(duì)列的操作
  • - (void)cancel;
    取消單個隊(duì)列操作
  • @property (getter=isSuspended) BOOL suspended;
    暫陀コ浚或恢復(fù)隊(duì)列墨叛,不會移除隊(duì)列

III. 線程的依賴和監(jiān)聽

  • 依賴的作用:保證執(zhí)行順序
    可以在 同一 / 不同隊(duì)列中 創(chuàng)建依賴關(guān)系
// 線程操作B 會在 線程操作A執(zhí)行完后 在執(zhí)行
[operationB addDependency:operationA]; // 操作B依賴于操作A
  • 線程操作的監(jiān)聽
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"download2 --- %@", [NSThread currentThread]);
}];

op2.completionBlock = ^{
    NSLog(@"監(jiān)聽線程操作 op2執(zhí)行完畢后要執(zhí)行的操作");
};

[queue addOperation:op2]; 

IV. 線程間的通訊

[[[NSOperationQueue alloc] init] addOperationWithBlock:^{
   // 圖片的網(wǎng)絡(luò)路徑
   NSURL *url = [NSURL URLWithString:@"圖片路徑/圖片名.格式"];
   // 加載圖片
   NSData *data = [NSData dataWithContentsOfURL:url];
   // 生成圖片
   UIImage *image = [UIImage imageWithData:data];
   // 回到主線程
   [[NSOperationQueue mainQueue] addOperationWithBlock:^{
       self.imageView.image = image;
   }];
}];

5. 多線程的應(yīng)用:單例設(shè)計(jì)模式「Singleton」

定義:

  • 類的對象成為系統(tǒng)中唯一的實(shí)例,提供一個訪問點(diǎn)模蜡,供客戶類 共享資源

使用情景:「操作頻繁的時候漠趁,提升效率」

  1. 類只能有一個實(shí)例,必需從一個為人熟知的訪問點(diǎn)訪問哩牍,比如工廠方法
  2. 整個應(yīng)用程序中棚潦,共享一份資源

注意:

  1. 某個類只能有一個實(shí)例
  2. 為保證實(shí)例唯一性,這個方法必須是靜態(tài)類方法
  3. 單例方法名稱一般以 share 或 default 開頭
  4. 這個類必須自行創(chuàng)建這個對象
  5. 必須自行向整個系統(tǒng)提供這個實(shí)例

示例代碼一膝昆、使用GCD

// 實(shí)現(xiàn)之外丸边,先定義一個實(shí)例叠必,為了防止野指針,賦空值
// 實(shí)現(xiàn)之外的靜態(tài)變量妹窖,一個類只有一個「不能用繼承來實(shí)現(xiàn)不同類的單例」
static Sample *_instance = nil;
@implement Sample
// alloc纬朝、allocWithZone方法都會調(diào)用 allocWithZone方法骄呼,所以只重寫 allocWithZone方法
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
    // 確保多線程的線程安全
    static dispach_one_t onceToken; 
    dispach_once(&onceToken, ^{
        _instance = [super allocWithZone:zone];
    });
    return _instance;
}

// 直接返回單例對象
+ (instancetype)shareInstance{
    // 確保多線程的線程安全
    static dispatch_one_t onceToken; 
    dispach_once(&onceToken, ^{
        _instance = [[self alloc] init];
    });
    return _instance;
}

// 防止 Copy方法的誤用
-(id)copyWithZone:(NSZone *)zone{
    return _instance; 
}
-(id)mutableCopyWithZone:(NSZone *)zone{
    return _instance;
}

// 若是MRC模式下共苛,需要添加一下代碼
- (oneway void)release{
    // 為保證單例,只重寫父類方法蜓萄,什么都不需要做
}
- (instancetype)retain{
    return _instance;
}
- (NSInteger)retainCount{
    return MAXFLOAT; //為了方便程序員交流隅茎,這里返回一個很大的數(shù)    
}
@end

示例代碼二、不使用GCD

static Sample _instance = nil;
@implement Sample

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    // 添加同步鎖嫉沽,注意加鎖的位置
    @synchronized(self) {
        if (_instance == nil) {
            _instance = [super allocWithZone:zone];
        }
    }
    return _instance;
}

+ (instancetype)sharedInstance {
    // 添加同步鎖
    @synchronized(self) {
        if (_instance == nil) {
            _instance = [[self alloc] init];
        }
    }
    return _instance;
}

// 以下代碼與使用GCD時重復(fù)辟犀,略
@end

將單例作為宏定義使用

  • 單例抽取為宏
    優(yōu)點(diǎn):每次定義單例的方式都是一樣的,為了提高編程效率
    缺點(diǎn):因?yàn)閱卫x中含有全局變量 static绸硕,在程序運(yùn)行的整個過程中只創(chuàng)建一份堂竟,所以在 使用宏時不能繼承單例

  • 宏方法定義

// 聲明方法,
// #define a(b) is_##b  的作用是 將 a(hehe) 自動替換為 is_hehe
#define interfaceSingleton(name) +(instancetype)share##name;

// 定義方法
// 系統(tǒng)自帶 宏定義 判斷是否為 arc模式
#if !__has_feature(obj_arc)

#define implememtationSingleton(name) \
static id _instance = nil;\
+ (instancetype)allocWithZone:(struct _NSZone *)zone{\
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{\
        _instance = [super allocWithZone:zone];\
    });\
    return _instance;\
}\
+ (instancetype)share##name{\
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{\
        _instance = [[self alloc] init];\
    });\
    return _instance;\
}\
- (id)copyWithZone:(NSZone *)zone{ return _instance; }\
- (id)mutableCopyWithZone:(NSZone *)zone{ return _instance; }\
- (oneway void)release{}\
- (instancetype)retain{ return _instance; }\
- (NSInteger)retainCount{ return (unsigned long)MAXFLOAT}

#else

#define implememtationSingleton(name) \
static id _instance = nil;\
+ (instancetype)allocWithZone:(struct _NSZone *)zone{\
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{\
        _instance = [super allocWithZone:zone];\
    });\
    return _instance;\
}\
+ (instancetype)share##name{\
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{\
        _instance = [[self alloc] init];\
    });\
    return _instance;\
}\
- (id)copyWithZone:(NSZone *)zone{ return _instance; }\
- (id)mutableCopyWithZone:(NSZone *)zone{ return _instance; }
#endif
  • 宏方法使用
interfaceSingleton(className)   // 這里沒有「;」因?yàn)槭呛甓x的使用
implementationSingleton(className) 
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市玻佩,隨后出現(xiàn)的幾起案子出嘹,更是在濱河造成了極大的恐慌,老刑警劉巖咬崔,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件税稼,死亡現(xiàn)場離奇詭異,居然都是意外死亡刁赦,警方通過查閱死者的電腦和手機(jī)娶聘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來甚脉,“玉大人,你說我怎么就攤上這事铆农∥保” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵墩剖,是天一觀的道長猴凹。 經(jīng)常有香客問我,道長岭皂,這世上最難降的妖魔是什么郊霎? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮爷绘,結(jié)果婚禮上书劝,老公的妹妹穿的比我還像新娘进倍。我一直安慰自己,他們只是感情好憨栽,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布艰额。 她就那樣靜靜地躺著态蒂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪垂蜗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天解幽,我揣著相機(jī)與錄音贴见,去河邊找鬼。 笑死躲株,一個胖子當(dāng)著我的面吹牛片部,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播徘溢,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼吞琐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了然爆?” 一聲冷哼從身側(cè)響起站粟,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎曾雕,沒想到半個月后奴烙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡剖张,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年切诀,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搔弄。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡幅虑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出顾犹,到底是詐尸還是另有隱情倒庵,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布炫刷,位于F島的核電站擎宝,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏浑玛。R本人自食惡果不足惜绍申,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧极阅,春花似錦胃碾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拆又,卻和暖如春儒旬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背帖族。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工栈源, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人竖般。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓甚垦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親涣雕。 傳聞我的和親對象是個殘疾皇子艰亮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

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