一次性執(zhí)行(dispatch_once_t)
實(shí)際開發(fā)中有時(shí)我們需要某個(gè)方法只執(zhí)行一次颈抚,而且要保證線程是安全的锯厢,那么dispatch_once_t無疑是很好的選擇枯冈。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self onceTest];
}
- (void)onceTest {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"%@", [NSThread currentThread]);
});
}
執(zhí)行在主線程上
<NSThread: 0x60800006b0c0>{number = 1, name = main}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self onceTest];
});
}
執(zhí)行在當(dāng)前線程上
<NSThread: 0x608000263080>{number = 3, name = (null)}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
for(int i = 0; i< 10; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self onceTest];
});
}
}
線程是安全的茵瀑,同時(shí)開啟多個(gè)任務(wù)執(zhí)行once仍然只執(zhí)行一次
<NSThread: 0x608000266040>{number = 3, name = (null)}
實(shí)際開發(fā)中once最常用在創(chuàng)建單例的對(duì)象上
#import <Foundation/Foundation.h>
@interface JYPerson : NSObject <NSCopying>
+ (instancetype)sharedPerson;
@end
#import "JYPerson.h"
@implementation JYPerson
// 定義一個(gè)靜態(tài)變量,保證內(nèi)存中只有一個(gè)副本惜犀,而且是保存在靜態(tài)區(qū)直到程序退出才銷毀
static id instance;
// 提供一個(gè)全局的訪問方法(補(bǔ)充:有一個(gè)約定铛碑,所有的單例都是以 shared + 類名 格式定義)
+ (instancetype)sharedPerson {
// 用dispatch_once的目的:保證對(duì)象只會(huì)被初始化(init)一次,切線程是安全的
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
// 重寫allocWihtZone方法保證虽界,對(duì)象只會(huì)被實(shí)例化一次汽烦,只分配一次內(nèi)存空間
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
// 用dispatch_once的目的:在多線程運(yùn)行在保證allocWithZone只調(diào)用一次,即內(nèi)存只分配一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [super allocWithZone:zone];
});
return instance;
}
- (id)copyWithZone:(NSZone *)zone {
// return [[self.class allocWithZone:zone] init];
// 對(duì)象方法莉御,說明拷貝的源已經(jīng)存在 => instance 就一定已經(jīng)被實(shí)例化過撇吞,所以這里直接返回實(shí)例instance
return instance;
}
@end
順便介紹下Swift中單利寫法(dispatch_once_t在swift中被廢棄了)
import UIKit
class JYPerson: NSObject {
static let shareInstance: JYPerson = {
return JYPerson()
}()
}
延時(shí)操作
// OC
/**
參數(shù)1: dispatch_time_t when
多少納秒之后執(zhí)行
參數(shù)2: dispatch_queue_t queue
執(zhí)行任務(wù)的隊(duì)列
參數(shù)3: dispatch_block_t block
要執(zhí)行的任務(wù)
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<delayInSeconds> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 要執(zhí)行的任務(wù)
});
// Swift
func test2() -> () {
// 原先的dispatch_time_t現(xiàn)在由DispatchTime對(duì)象表示〗甘澹可以用靜態(tài)方法now獲得當(dāng)前時(shí)間梢夯,然后再通過加上一個(gè)。DispatchTimeInterval枚舉來獲得一個(gè)需要延遲的時(shí)間
print("操作一")
// let delay = DispatchTime.now() + DispatchTimeInterval.seconds(3)
// 這里也可以直接加上一個(gè)秒數(shù)
let delay = DispatchTime.now() + 3
DispatchQueue.main.asyncAfter(deadline: delay) {
print("操作二:延遲了3s執(zhí)行")
}
}
調(diào)度組(Dispatch_groups)
Dispatch Group 會(huì)在整個(gè)組的任務(wù)都完成時(shí)發(fā)出通知晴圾,這些任務(wù)可以是同步的,也可以是異步的噪奄,即便在不同的隊(duì)列也行死姚。對(duì)多個(gè)異步任務(wù)的完成進(jìn)行監(jiān)控的問題,這無疑是一個(gè)非常好的選擇勤篮。
因?yàn)楸槐O(jiān)控的任務(wù)可能在不同的隊(duì)列都毒,因此用一個(gè) dispatch_group_t 的實(shí)例來記下這些不同的任務(wù)。
當(dāng)組中所有的事件都完成時(shí)碰缔,GCD 的 API 提供了兩種通知方式账劲。
用法一
利用 dispatch_group_wait ,它會(huì)阻塞當(dāng)前線程金抡,直到組里面所有的任務(wù)都完成或者等到某個(gè)超時(shí)發(fā)生瀑焦。
因?yàn)槟阍谑褂玫氖峭降?dispatch_group_wait ,它會(huì)阻塞當(dāng)前線程梗肝,所以你要用 dispatch_async 將整個(gè)方法放入后臺(tái)隊(duì)列以避免阻塞主線程榛瓮。
// OC
- (void)groupTest {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 創(chuàng)建一個(gè)dispatch_group
dispatch_group_t group = dispatch_group_create();
// 手動(dòng)通知進(jìn)入dispatch_group_t(你必須保證 dispatch_group_enter 和 dispatch_group_leave 成對(duì)出現(xiàn),否則你可能會(huì)遇到詭異的崩潰問題)
dispatch_group_enter(group);
NSLog(@"任務(wù)1");
[NSThread sleepForTimeInterval:3.0];
NSLog(@"任務(wù)1");
NSLog(@"任務(wù)1");
// 手動(dòng)通知結(jié)束dispatch_group_t
dispatch_group_leave(group);
// 一直等待直到dispatch_group_t中所有任務(wù)執(zhí)行完畢
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"拉回主線程刷新UI");
});
});
}
// Swift
func groupTest() -> () {
DispatchQueue.global().async {
// 創(chuàng)建一個(gè)dispatch_group
let group = DispatchGroup();
// 手動(dòng)通知進(jìn)入dispatch_group_t(你必須保證 dispatch_group_enter 和 dispatch_group_leave 成對(duì)出現(xiàn)巫击,否則你可能會(huì)遇到詭異的崩潰問題)
group.enter()
print("任務(wù)1")
print("任務(wù)2")
print("任務(wù)3")
group.leave()
// 一直等待直到dispatch_group_t中所有任務(wù)執(zhí)行完畢
group.wait()
DispatchQueue.main.async(execute: {
print("拉回主線程刷新UI")
})
}
}
控制臺(tái)輸出結(jié)果
[1670:108365] 任務(wù)1
[1670:108365] 任務(wù)2
[1670:108365] 任務(wù)3
[1670:108304] 拉回主線程刷新UI
基本實(shí)現(xiàn)過程:
創(chuàng)建一個(gè)group 禀晓,執(zhí)行g(shù)roupenter 和 leave 中間的代碼 精续,然后再利用dispatch_group_wait 讓線程阻塞在這里 一直等待,最后再去執(zhí)行刷新UI或者處理group結(jié)果的部分
用法二
然而粹懒,用阻塞線程這種方法顯得不是那么友好 重付,我們來看看第二種 ,不阻塞線程的方法:dispatch_group_notify
// OC
- (void)groupTest {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 創(chuàng)建一個(gè)dispatch_group
dispatch_group_t group = dispatch_group_create();
// 手動(dòng)通知進(jìn)入dispatch_group_t(你必須保證 dispatch_group_enter 和 dispatch_group_leave 成對(duì)出現(xiàn)凫乖,否則你可能會(huì)遇到詭異的崩潰問題)
dispatch_group_enter(group);
NSLog(@"任務(wù)1");
[NSThread sleepForTimeInterval:3.0];
NSLog(@"任務(wù)2");
NSLog(@"任務(wù)3");
// 手動(dòng)通知結(jié)束dispatch_group_t
dispatch_group_leave(group);
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"拉回主線程刷新UI");
});
});
}
// Swift
func groupTest() -> () {
DispatchQueue.global().async {
// 創(chuàng)建一個(gè)dispatch_group
let group = DispatchGroup();
// 手動(dòng)通知進(jìn)入dispatch_group_t(你必須保證 dispatch_group_enter 和 dispatch_group_leave 成對(duì)出現(xiàn)确垫,否則你可能會(huì)遇到詭異的崩潰問題)
group.enter()
print("任務(wù)1")
print("任務(wù)2")
print("任務(wù)3")
group.leave()
let workItem = DispatchWorkItem(block: {
print("拉回主線程刷新UI")
})
group.notify(queue: DispatchQueue.main, work: workItem)
}
}
控制臺(tái)輸出結(jié)果
[1670:108365] 任務(wù)1
[1670:108365] 任務(wù)2
[1670:108365] 任務(wù)3
[1670:108304] 拉回主線程刷新UI
用法三(平時(shí)開發(fā)最常用)
// OC
- (void)groupTest {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 創(chuàng)建一個(gè)dispatch_group
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務(wù)1");
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務(wù)2");
});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務(wù)3");
});
});
}
// Swift
func groupTest() -> () {
DispatchQueue.global().async {
// 創(chuàng)建一個(gè)dispatch_group
let group = DispatchGroup()
let queueOne = DispatchQueue(label: "queueOne")
queueOne.async(group: group) {
print("任務(wù)1")
}
let queueTwo = DispatchQueue(label: "queueTwo")
queueTwo.async(group: group) {
print("任務(wù)2")
}
group.notify(queue: DispatchQueue.main) {
print("任務(wù)3")
}
}
}
控制臺(tái)輸出結(jié)果
任務(wù)1
任務(wù)2
任務(wù)3
或者
任務(wù)2
任務(wù)1
任務(wù)3
任務(wù)1和任務(wù)2是異步的所以先后順序不可控,任務(wù)3只有在group中所有任務(wù)執(zhí)行完畢調(diào)用
dispatch_barrier
在并行隊(duì)列中拣凹,為了保持某些任務(wù)的順序森爽,需要等待一些任務(wù)完成后才能繼續(xù)進(jìn)行,使用 barrier 來等待之前任務(wù)完成嚣镜,避免數(shù)據(jù)競(jìng)爭(zhēng)等問題爬迟。假設(shè)我們有一個(gè)并發(fā)的隊(duì)列用來讀寫一個(gè)數(shù)據(jù)對(duì)象。如果這個(gè)隊(duì)列里的操作是讀的菊匿,那么可以多個(gè)同時(shí)進(jìn)行付呕。如果有寫的操作,則必須保證在執(zhí)行寫入操作時(shí)跌捆,不會(huì)有讀取操作在執(zhí)行徽职,必須等待寫入完成后才能讀取,否則就可能會(huì)出現(xiàn)讀到的數(shù)據(jù)不對(duì)佩厚。OC用dipatch_barrier實(shí)現(xiàn)姆钉。Swift中用DispatchWorkItemFlags實(shí)現(xiàn)。
// OC
- (void)barrierTest {
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"任務(wù)1");
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)2");
[NSThread sleepForTimeInterval:2.0];
});
dispatch_barrier_async(queue, ^{
NSLog(@"barrier測(cè)試");
});
dispatch_async(queue, ^{
NSLog(@"任務(wù)3");
});
}
// Swift
func barrierTest() -> () {
let queue = DispatchQueue(label: "queue", attributes: .concurrent)
queue.async {
print("任務(wù)1")
}
queue.async {
print("任務(wù)2")
Thread.sleep(forTimeInterval: 2.0)
}
let barrierWorkItm = DispatchWorkItem(flags: .barrier) {
print("barrier測(cè)試")
}
queue.async(execute: barrierWorkItm)
queue.async {
print("任務(wù)3")
}
}
控制臺(tái)輸出結(jié)果
任務(wù)1
任務(wù)2
barrier測(cè)試
任務(wù)3
或
任務(wù)2
任務(wù)1
barrier測(cè)試
任務(wù)3
任務(wù)1和任務(wù)2是異步的所以先后順序不可控抄瓦,barrier會(huì)等待當(dāng)前隊(duì)列中先于自己添加的任務(wù)完成再執(zhí)行潮瓶,并阻塞當(dāng)前線程,等待barrier函數(shù)執(zhí)行完畢當(dāng)前線程才恢復(fù)之前的動(dòng)作繼續(xù)執(zhí)行钙姊,任務(wù)3只barrier函數(shù)執(zhí)行完畢后才能調(diào)用毯辅。
注意:使用 dispatch_barrier_async ,該函數(shù)只能搭配自定義并行隊(duì)列 dispatch_queue_t 使用煞额。不能使用: dispatch_get_global_queue 思恐,否則 dispatch_barrier_async 的作用會(huì)和 dispatch_async 的作用一模一樣。
dispatch_apply
作用:按指定的次數(shù)將指定的Block追加到指定的Dispatch Queue中,并等到全部的處理執(zhí)行結(jié)束(類似for循環(huán))
并行隊(duì)列
- (void)apply {
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(5, queue, ^(size_t index) {
NSLog(@"當(dāng)前線程:%@, index: %zu",[NSThread currentThread], (index + 1));
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"done");
});
}
控制臺(tái)輸出結(jié)果:并行隊(duì)列上是并發(fā)執(zhí)行的膊毁,但并非所有任務(wù)都開辟新線程胀莹,也有在主線程中完成的。dispatch_apply會(huì)阻塞線程媚媒,直到dispatch_apply函數(shù)執(zhí)行完畢才繼續(xù)往下走嗜逻,所有done最后打印
當(dāng)前線程:<NSThread: 0x600000075ac0>{number = 4, name = (null)}, index: 3
當(dāng)前線程:<NSThread: 0x60800006a9c0>{number = 1, name = main}, index: 1
當(dāng)前線程:<NSThread: 0x608000078440>{number = 5, name = (null)}, index: 4
當(dāng)前線程:<NSThread: 0x608000078640>{number = 3, name = (null)}, index: 2
當(dāng)前線程:<NSThread: 0x600000075ac0>{number = 4, name = (null)}, index: 5
done
串行隊(duì)列
- (void)applyTest {
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_apply(5, queue, ^(size_t index) {
NSLog(@"當(dāng)前線程:%@, index: %zu",[NSThread currentThread], (index + 1));
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"done");
});
}
控制臺(tái)輸出結(jié)果:
當(dāng)前線程:<NSThread: 0x600000075ac0>{number = 4, name = (null)}, index: 1
當(dāng)前線程:<NSThread: 0x60800006a9c0>{number = 1, name = main}, index: 2
當(dāng)前線程:<NSThread: 0x608000078440>{number = 5, name = (null)}, index: 3
當(dāng)前線程:<NSThread: 0x608000078640>{number = 3, name = (null)}, index: 4
當(dāng)前線程:<NSThread: 0x600000075ac0>{number = 4, name = (null)}, index: 5
done