ios多線程操作— GCD延遲操作和相關使用方法
0x01.iOS版本
使用GCD函數可以進行延時操作,該函數爲
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
現在我們來分解一下參數
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)) : NSEC_PER_SEC
在頭文件中的定義如下:
#define NSEC_PER_SEC 1000000000ull /* nanoseconds per second */
該參數表示從現在開始經過多少納秒
dispatch_get_main_queue():
表示主隊列. ^{ }:
表示一個block任務。
我們可以來測試一下經過多少納秒之後,由主隊列調度任務是異步執行還是同步執行,代碼如下:
// when 時間 從現在開始經過多少納秒
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
void (^task)() = ^ {
// 延遲操作執行的代碼
NSLog(@"%@", [NSThread currentThread]);
};
// 經過多少納秒,由主隊列調度任務異步執行
dispatch_after(when, dispatch_get_main_queue(), task);
// 先執行就是異步,後執行就是同步
NSLog(@"come here");
由此可見主隊列中調度任務是異步執行的 再將執行隊列改爲全局隊列和串行隊列,得到的結果完全是一樣的,由此可知該函數執行的是異步操作。
GCD中有個函數能夠保證某段代碼在程序運行過程中只被執行1次!該函數如下:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
})
dispatch_once_t
在頭文件中得定義如下:typedef long dispatch_once_t
; 由此可知該類型是個long
類型。當onceToken
等於0時就會執行block
代碼。dispatch_once
是線程安全的,只要涉及到線程安全就會涉及到鎖,dispatch_once
內部也有一把鎖,性能比互斥鎖高! 利用該函數我們可以來寫一個單例模式 單例模式可以保證在程序運行過程,一個類只有一個實例且該實例易於供外界訪問,從而方便控制實例個數,並節約系統資源,當應用程序需要共享一份資源時就可以用單例模式來實現。單例模式分ARC與MRC兩種情況,我們可以用宏判斷是否爲ARC環境
#if __has_feature(objc_arc)
// ARC
#else
// MRC
#endif
ARC環境下簡單地單例模式:
@implementation SoundTools
// 定義一個靜態成員,保存唯一的實例
static id instance;
// 保證對象只被分配一次內存空間,通過dispatch_once能夠保證單例的分配和初始化是線程安全的
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [super allocWithZone:zone];
});
return instance;
}
// 保證對象只被初始化一次
+ (instancetype)sharedSoundTools {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
- (id)copyWithZone:(NSZone *)zone {
return instance;
}
@end
測試代碼如下:
- (void)viewDidLoad {
[super viewDidLoad];
SoundTools *s1 = [SoundTools sharedSoundTools];
NSLog(@"%p", s1);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
SoundTools *s2 = [SoundTools sharedSoundTools];
NSLog(@"%p", s2);
}
兩個方法打印出來的地址完全一樣!
在MRC環境下有如下代碼:
// 定義一個靜態成員,保存唯一的實例
static id instance;
// 保證對象只被分配一次內存空間,通過dispatch_once能夠保證單例的分配和初始化是線程安全的
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [super allocWithZone:zone];
});
return instance;
}
// 保證對象只被初始化一次
+ (instancetype)sharedSoundTools {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
- (id)copyWithZone:(NSZone *)zone {
return instance;
}
#pragma mark - MRC內存管理方法
/**
因爲單例的對象是保存在靜態區的,因此需要重寫 內存管理方法,取消默認的引用計數操作!
*/
// 默認會將引用計數-1
- (oneway void)release {
// 什麼也不做,跟highlight類似
}
// 默認引用計數+1,同時返回一個對象
- (instancetype)retain {
return instance;
}
// 默認添加自動釋放標記,延遲釋放!
- (instancetype)autorelease {
return instance;
}
// 返回有多少個對象對當前對象引用的數值
- (NSUInteger)retainCount {
// 出處:limits.h 會根據CPU的架構自行調整整數的長度
return ULONG_MAX;
}
0x02.swift 3.0版本
1.延遲執行:
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+3.0, execute: {
[unowned self] () -> Void in
//延遲操作
})
PS. DispatchTime
對象用now()
獲取當前時間,加上秒數即可
2.全局隊列執行耗時操作後切換到主線程刷新UI
DispatchQueue.global().async {
// 耗時操作
DispatchQueue.main.async {
// 主線程刷新UI
}
}
3.同步執行操作
DispatchQueue.global().sync {
// 同步執行
}
4.創建隊列
DispatchQueue
的默認初始化方法創建的是同步隊列,如果要創建併發的隊列,在attributes中聲明.concurrent。
// 同步隊列
let serialQueue = DispatchQueue(label: "name")
// 併發隊列
let concurrentQueue = DispatchQueue(label: "name", attributes: .concurrent)
5.執行多個任務後再做某種操作
使用DispatchGroup
,所有操作都完成後執行notify
。
let group = DispatchGroup()
let queue1 = DispatchQueue(label: "queue1")
queue1.async(group: group) {
// 執行任務1
}
let queue2 = DispatchQueue(label: "queue2")
queue1.async(group: group) {
// 執行任務2
}
group.notify(queue: DispatchQueue.main) {
// 執行完成
}
如果要在某一任務或某幾個任務後後執行其他任務,可在任務間加上等待:
//等待組內任務全部完成
group.wait(timeout: DispatchTime.distantFuture)
6.DispatchWorkItem
的使用
- DispatchWorkItem可理解爲任務條目,可初始化傳入優先級等參數,因其有默認值,也可只傳入一個閉包。同樣,它也有wait方法,使用和上面差不多。
let queue = DispatchQueue(label: "queue", attributes: .concurrent)
let workItem = DispatchWorkItem {
// 任務
}
queue.async(execute: workItem)
print("before waiting")
workItem.wait()
print("after waiting")
7.barrier
柵欄
- barrier的加入會等到在它加入隊列之前的“任務”執行完畢後,纔開始執行。在它之後加入隊列的“任務”,則等到這個“任務”執行完畢後纔開始執行。這裏的“任務”用DispatchWorkItem創建。
let barrierWorkItem = DispatchWorkItem(flags: .barrier) {
// 柵欄操作,比如之前有若干“讀”操作,這裏有“寫”操作
}
let queue = DispatchQueue(label: "queue", attributes: .concurrent)
queue.async(execute: barrierWorkItem)
8.信號量
爲了線程安全的統計數量,會使用信號量作計數。初始化方法只有一個,傳入一個Int類型的數。
let semaphore = DispatchSemaphore(value: 10)
// 信號量減一
semaphore.wait()
// 信號量加一
semaphore.signal()