iOS中的讀寫安全
atomic
atomic用于保證屬性setter、getter的原子性操作,相當于在getter和setter內部加了執行緒同步的鎖原子性:原子即為最小的物理單位,意味不可再分割;即代碼都為一個整體在同一執行緒進行操作
atomic只是保證setter、getter是執行緒安全的,并不能保證使用屬性的程序是執行緒安全的
從原始碼分析getter和setter對于atomic的使用
我們在objc4中的objc-accessors.mm中找到對應的getter和setter的實作
getter的實作
// getter
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = https://www.cnblogs.com/funkyRay/archive/2021/04/08/objc_retain(*slot);
slotlock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
setter的實作
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = https://www.cnblogs.com/funkyRay/archive/2021/04/08/[newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
從原始碼可以看出只有automic的屬性才會進行加鎖操作
iOS中的讀寫安全方案
思考以下場景,怎么做最合適
- 同一時間,只能有1個執行緒進行寫的操作
- 同一時間,允許有多個執行緒進行讀的操作
- 同一時間,不允許既有寫的操作,又有讀的操作
上面的場景就是典型的“多讀單寫”,經常用于檔案等資料的讀寫操作,iOS中的實作方案有以下兩個
- pthread_rwlock:讀寫鎖
- dispatch_barrier_async:異步柵欄呼叫
pthread_rwlock
pthread_rwlock是專用于讀寫檔案的鎖,其本質也是互斥鎖,等待鎖的執行緒會進入休眠
使用代碼如下
@interface ViewController ()
@property (assign, nonatomic) pthread_rwlock_t lock;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化鎖
pthread_rwlock_init(&_lock, NULL);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
[self read];
});
dispatch_async(queue, ^{
[self write];
});
}
}
- (void)read {
// 加上讀取資料的鎖
pthread_rwlock_rdlock(&_lock);
sleep(1);
NSLog(@"%s", __func__);
pthread_rwlock_unlock(&_lock);
}
- (void)write
{
// 加上寫入資料的鎖
pthread_rwlock_wrlock(&_lock);
sleep(1);
NSLog(@"%s", __func__);
pthread_rwlock_unlock(&_lock);
}
- (void)dealloc
{
// 釋放時要銷毀鎖
pthread_rwlock_destroy(&_lock);
}
@end
dispatch_barrier_async
dispatch_barrier_async也叫柵欄函式,意在用于攔截多執行緒異步并發操作,只保證同時有一條執行緒在操作
用柵欄函式也可以保證多讀單寫的操作
使用代碼如下
@interface ViewController ()
@property (strong, nonatomic) dispatch_queue_t queue;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_async(self.queue, ^{
[self read];
});
dispatch_async(self.queue, ^{
[self read];
});
dispatch_async(self.queue, ^{
[self read];
});
// 這個函式傳入的并發佇列必須是自己通過dispatch_queue_cretate創建的
dispatch_barrier_async(self.queue, ^{
[self write];
});
}
}
- (void)read {
sleep(1);
NSLog(@"read");
}
- (void)write
{
sleep(1);
NSLog(@"write");
}
@end
定時器
我們日常使用的定時器有以下幾個
- CADisplayLink
- NSTimer
- GCD定時器
CADisplayLink
CADisplayLink是用于同步螢屏重繪頻率的定時器
CADisplayLink和NSTimer的區別
- iOS設備的螢屏重繪頻率是固定的,
CADisplayLink在正常情況下會在每次重繪結束都被呼叫,精確度相當高 NSTimer的精確度就顯得低了點,比如NSTimer的觸發時間到的時候,runloop如果在阻塞狀態,觸發時間就會推遲到下一個runloop周期,并且NSTimer新增了tolerance屬性,讓用戶可以設定可以容忍的觸發的時間的延遲范圍CADisplayLink使用場合相對專一,適合做UI的不停重繪,比如自定義影片引擎或者視頻播放的渲染,NSTimer的使用范圍要廣泛的多,各種需要單次或者回圈定時處理的任務都可以使用,在UI相關的影片或者顯示內容使用CADisplayLink比起用NSTimer的好處就是我們不需要在格外關心螢屏的重繪頻率了,因為它本身就是跟螢屏重繪同步的,
CADisplayLink在使用中會出現的回圈參考問題
CADisplayLink在日常使用中,可能會出現回圈參考問題,見示例代碼
@interface ViewController ()
@property (strong, nonatomic) CADisplayLink *link;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)linkTest
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self.link invalidate];
}
@end
由于ViewController里有個link屬性指向這CADisplayLink物件,CADisplayLink物件里的target又指向著ViewController里的linkTest,都是強參考,所以會造成回圈參考,無法釋放

解決方案
增加第三個物件,通過第三個物件將target呼叫的方法轉發出去,具體如下圖所示

實作代碼如下
@interface LLProxy : NSObject
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
@implementation LLProxy
+ (instancetype)proxyWithTarget:(id)target
{
LLProxy *proxy = [[LLProxy alloc] init];
proxy.target = target;
return proxy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return self.target;
}
@end
// ViewController.m檔案中
#import "ViewController.h"
#import "LLProxy.h"
@interface ViewController ()
@property (strong, nonatomic) CADisplayLink *link;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.link = [CADisplayLink displayLinkWithTarget:[LLProxy proxyWithTarget:self] selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)linkTest
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self.link invalidate];
}
@end
NSTimer
NSTimer也是定時器,相比CADisplayLink使用范圍更廣,更靈活,但精確度會低一些
NSTimer在使用中會出現的回圈參考問題
NSTimer在使用時也會存在回圈參考問題,同CADisplayLink
@interface ViewController ()
@property (strong, nonatomic) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
}
- (void)timerTest
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self.timer invalidate];
}
@end
解決方案
【第一種】借助第三物件并將方法轉發,同CADisplayLink
@interface ViewController ()
@property (strong, nonatomic) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[LLProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
}
- (void)timerTest
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self.timer invalidate];
}
@end
【第二種】使用NSTimer的block回呼來呼叫方法,并將self改為弱指標
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf timerTest];
}];
統一優化方案
NSProxy
NSProxy是唯一一個沒有繼承自NSObject的類,它是專門用來做訊息轉發的

特點
- 不繼承
NSObject,也是基型別別 - 沒有
init方法,直接用alloc方法來初始化 - 沒有
forwardingTargetForSelector方法,只支持訊息轉發
優化方案
將LLProxy繼承自NSProxy,然后在訊息轉發里替換target
這么做的好處在于NSProxy相比NSObject少了訊息發送先從父類查找的程序,以及不經過forwardingTargetForSelector,相比之下性能會高
替換代碼如下
@interface LLProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
@implementation LLProxy
+ (instancetype)proxyWithTarget:(id)target
{
// NSProxy物件不需要呼叫init,因為它本來就沒有init方法
LLProxy *proxy = [LLProxy alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:self.target];
}
@end
從原始碼實作來分析
我們先查看下面這句代碼列印什么
ViewController *vc = [[ViewController alloc] init];
LLProxy *proxy = [LLProxy proxyWithTarget:vc];
NSLog(@"%d, [proxy isKindOfClass:[ViewController class]]);
列印結果為1,可以看出NSProxy的isKindOfClass和NSObject的isKindOfClass有所差別
我們可以通過GNUstep來查看NSProxy的原始碼實作,發現其內部會直接呼叫訊息轉發的方法,才會有我們將target替換成了ViewController物件,所以最后呼叫isKindOfClass的是ViewController物件,那么結果也就知曉了

從該方法可以反觀NSProxy的其他方法內部實作,都會主動觸發訊息轉發的實作
GCD定時器
GCD定時器相比其他兩個定時器是最準時的,因為和系統內核直接掛鉤
使用代碼如下
我們將GCD定時器封裝到自定義的LLTimer檔案來使用
// LLTimer.h檔案
@interface LLTimer : NSObject
+ (NSString *)execTask:(void(^)(void))task
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async;
+ (NSString *)execTask:(id)target
selector:(SEL)selector
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async;
+ (void)cancelTask:(NSString *)name;
// LLTimer.m檔案
#import "LLTimer.h"
@implementation LLTimer
static NSMutableDictionary *timers_;
static dispatch_semaphore_t semaphore_;
+ (void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
timers_ = [NSMutableDictionary dictionary];
// 加鎖來保證多執行緒創建定時器和取消定時器同時只能有一個操作
semaphore_ = dispatch_semaphore_create(1);
});
}
+ (NSString *)execTask:(void (^)(void))task start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async
{
if (!task || start < 0 || (interval <= 0 && repeats)) return nil;
// 佇列
dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
// 創建定時器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 設定時間
// dispatch_time_t start:幾秒后開始執行
// uint64_t interval:執行間隔
dispatch_source_set_timer(timer,
dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
interval * NSEC_PER_SEC, 0);
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
// 定時器的唯一標識
NSString *name = [NSString stringWithFormat:@"%zd", timers_.count];
// 存放到字典中
timers_[name] = timer;
dispatch_semaphore_signal(semaphore_);
// 設定回呼
dispatch_source_set_event_handler(timer, ^{
task();
if (!repeats) { // 不重復的任務
[self cancelTask:name];
}
});
// 啟動定時器
dispatch_resume(timer);
return name;
}
+ (NSString *)execTask:(id)target selector:(SEL)selector start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async
{
if (!target || !selector) return nil;
return [self execTask:^{
if ([target respondsToSelector:selector]) {
// 去掉警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[target performSelector:selector];
#pragma clang diagnostic pop
}
} start:start interval:interval repeats:repeats async:async];
}
+ (void)cancelTask:(NSString *)name
{
if (name.length == 0) return;
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
dispatch_source_t timer = timers_[name];
if (timer) {
dispatch_source_cancel(timer);
[timers_ removeObjectForKey:name];
}
dispatch_semaphore_signal(semaphore_);
}
@end
然后在控制器里呼叫
#import "ViewController.h"
#import "LLTimer.h"
@interface ViewController ()
@property (copy, nonatomic) NSString *task;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"begin");
// selector方式
self.task = [LLTimer execTask:self
selector:@selector(doTask)
start:2.0
interval:1.0
repeats:YES
async:YES];
// block方式
// self.task = [LLTimer execTask:^{
// NSLog(@"111111 - %@", [NSThread currentThread]);
// } start:2.0 interval:-10 repeats:NO async:NO];
}
- (void)doTask
{
NSLog(@"doTask - %@", [NSThread currentThread]);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[LLTimer cancelTask:self.task];
}
LLTimer封裝細節
- 在
initialize方法里只執行一次字典的創建和鎖的創建(只有用到該類時才創建,并且避免多次呼叫) - 內部創建一個全域的字典用來保存多個定時器的創建(
定時器的個數遞增作為key,timer為value) - 外部支持多個引數來控制定時器在哪個執行緒創建,以及是否只呼叫一次
- 注意細節的優化,對于傳入的時間、是否有任務,以及定時器的標識都對應做校驗
- 在多執行緒環境下,保證創建定時器和取消洗掉定時器同一時間只能有一個執行緒在執行
面試題
1.看下面兩段代碼,會不會造成死鎖
// 段1
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"執行任務1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"執行任務2");
});
NSLog(@"執行任務3");
}
// 段2
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"執行任務1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"執行任務2");
});
NSLog(@"執行任務3");
}
第一段會死鎖,第二段不會
因為整個函式viewDidLoad的執行都是在主佇列中串行執行的,所以要等函式執行完才會執行任務2,但是dispatch_sync又是同步的,在主執行緒中是要執行dispatch_sync之后才會執行任務3的代碼,所以互相之前都要等待,就造成了死鎖
而dispatch_async不會,因為需要等待一會才會執行任務2的代碼,所以會先執行任務再執行任務2,不需要馬上執行;但是不會開啟新的執行緒

2.看下面這段代碼,會不會造成死鎖?將佇列改為并發佇列,會不會死鎖
NSLog(@"執行任務1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // block0
NSLog(@"執行任務2");
dispatch_sync(queue, ^{ // block1
NSLog(@"執行任務3");
});
NSLog(@"執行任務4");
});
NSLog(@"執行任務5");
會的,
原因是由于是dispatch_async,所以會先執行任務1和任務5,然后由于是串行佇列,那么先會執行block0,再執行block1;但是任務2執行完,后面的是dispatch_sync,就表示要馬上執行任務3,可任務3的執行又是要等block0執行完才可以,于是就會造成死鎖
改為并發佇列后不會死鎖,雖然都是同一個并發佇列,但是可以同時執行多個任務,不需要等待
3.看下面這段代碼,會不會造成死鎖?將佇列2改為并發佇列,會不會死鎖
NSLog(@"執行任務1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // block0
NSLog(@"執行任務2");
dispatch_sync(queue2, ^{ // block1
NSLog(@"執行任務3");
});
NSLog(@"執行任務4");
});
NSLog(@"執行任務5");
都不會,因為兩個任務都是在兩個佇列里,所以不會有等待情況
4.看下面代碼列印結果是什么,為什么,怎么改
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"3");
});
- (void)test
{
NSLog(@"2");
}
列印1、3,
因為performSelector: withObject: afterDelay:這個方法是屬于RunLoop的庫的,有afterDelay: 引數的本質都是往RunLoop中添加定時器的,由于當前是在子執行緒中,不會創建RunLoop,所以創建RunLoop后就可以執行該呼叫,并列印1、3、2
由于該方法的實作RunLoop是沒有開源的,我們要想了解方法實作的本質,可以通過GNUstep開源專案來查看,這個專案將Cocoa的OC庫重新開源實作了一遍,雖然不是官網原始碼,但也有一定的參考價值
原始碼地址:http://www.gnustep.org/resources/downloads.php
我們在官網上找到GNUstep Base進行下載

然后找到RunLoop.m中performSelector: withObject: afterDelay:的實作
- (void) performSelector: (SEL)aSelector
withObject: (id)argument
afterDelay: (NSTimeInterval)seconds {
NSRunLoop *loop = [NSRunLoop currentRunLoop];
GSTimedPerformer *item;
item = [[GSTimedPerformer alloc] initWithSelector: aSelector
target: self
argument: argument
delay: seconds];
[[loop _timedPerformers] addObject: item];
RELEASE(item);
[loop addTimer: item->timer forMode: NSDefaultRunLoopMode];
}
找到GSTimedPerformer的構造方法里面可以看到,會創建一個timer的定時器,然后將它加到RunLoop中
- (id) initWithSelector: (SEL)aSelector
target: (id)aTarget
argument: (id)anArgument
delay: (NSTimeInterval)delay
{
self = [super init];
if (self != nil)
{
selector = aSelector;
target = RETAIN(aTarget);
argument = RETAIN(anArgument);
timer = [[NSTimer allocWithZone: NSDefaultMallocZone()]
initWithFireDate: nil
interval: delay
target: self
selector: @selector(fire)
userInfo: nil
repeats: NO];
}
return self;
}
如此一來就印證了我們的分析,下面我們就手動在子執行緒創建RunLoop來查看
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
// 這句代碼的本質是往Runloop中添加定時器
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"3");
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
- (void)test
{
NSLog(@"2");
}
創建空的RunLoop之前因為已經通過performSelector: withObject: afterDelay:創建了一個定時器加了進去,所以RunLoop就不為空了,不需要我們再添加一個Source1了,這樣也保證RunLoop不會退出
運行程式,列印結果為1、3、2
最后列印2是因為RunLoop被喚醒處理事件有時間延遲,所以會晚一些列印
5.看下面代碼列印結果是什么,為什么,怎么改
- (void)test
{
NSLog(@"2");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"1");
}];
[thread start];
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
列印結果為1,并且崩潰了,
因為執行執行緒的block和performSelector幾乎是同時的,所以先執行了block后的執行緒就被銷毀了,這時再在該執行緒上發訊息就是會報錯
解決辦法也是創建RunLoop并讓子執行緒不被銷毀
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"1");
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}];
[thread start];
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
6.dispatch_once是怎么做到只創建一次的,內部是怎么實作的
我們知道GCD中的dispatch_once的使用如下代碼,可以做的只執行一次,一般用來創建單例
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"單例應用");
});
我們可以通過原始碼來分析內部實作,在once.c中找到dispatch_once的實作
void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
// val是onceToken靜態變數
dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}
其中的_dispatch_Block_invoke是一個宏定義,用來包裝block
#define _dispatch_Block_invoke(bb) \
((dispatch_function_t)((struct Block_layout *)bb)->invoke)
找到其底層是通過dispatch_once_f實作的
void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
// 將外界傳入的靜態變數val轉變為dispatch_once_gate_t型別的變數l
dispatch_once_gate_t l = (dispatch_once_gate_t)val;
#if !DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
// 獲取任務識別符號v
uintptr_t v = os_atomic_load(&l->dgo_once, acquire);
// 如果v == DLOCK_ONCE_DONE,表示任務已經執行過了,直接return
if (likely(v == DLOCK_ONCE_DONE)) {
return;
}
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
// 如果加鎖失敗走到這里,再次進行存盤,并標記為DLOCK_ONCE_DONE
if (likely(DISPATCH_ONCE_IS_GEN(v))) {
return _dispatch_once_mark_done_if_quiesced(l, v);
}
#endif
#endif
if (_dispatch_once_gate_tryenter(l)) { // 嘗試進入任務
return _dispatch_once_callout(l, ctxt, func);
}
// 此時已有任務,則進入無限等待
return _dispatch_once_wait(l);
}
dispatch_once_f函式的詳細呼叫分析
1.os_atomic_load這個宏用來獲取任務標識
#define os_atomic_load(p, m) \
atomic_load_explicit(_os_atomic_c11_atomic(p), memory_order_##m)
2.通過_dispatch_once_mark_done_if_quiesced進行再次存盤和標記
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_once_mark_done_if_quiesced(dispatch_once_gate_t dgo, uintptr_t gen)
{
if (_dispatch_once_generation() - gen >= DISPATCH_ONCE_GEN_SAFE_DELTA) {
/*
* See explanation above, when the quiescing counter approach is taken
* then this store needs only to be relaxed as it is used as a witness
* that the required barriers have happened.
*/
// 再次存盤,并標記為DLOCK_ONCE_DONE
os_atomic_store(&dgo->dgo_once, DLOCK_ONCE_DONE, relaxed);
}
}
3.通過_dispatch_once_gate_tryenter內部進行比較并加鎖
DISPATCH_ALWAYS_INLINE
static inline bool
_dispatch_once_gate_tryenter(dispatch_once_gate_t l)
{
// 進行比較,如果沒問題,則進行加鎖,并標記為DLOCK_ONCE_UNLOCKED
return os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED,
(uintptr_t)_dispatch_lock_value_for_self(), relaxed);
}
4.通過_dispatch_once_callout來執行回呼
DISPATCH_NOINLINE
static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
dispatch_function_t func)
{
// 回呼執行
_dispatch_client_callout(ctxt, func);
// 進行廣播
_dispatch_once_gate_broadcast(l);
}
_dispatch_client_callout內部就是執行block回呼
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
return f(ctxt);
}
_dispatch_once_gate_broadcast內部會呼叫_dispatch_once_mark_done
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_once_gate_broadcast(dispatch_once_gate_t l)
{
dispatch_lock value_self = _dispatch_lock_value_for_self();
uintptr_t v;
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
v = _dispatch_once_mark_quiescing(l);
#else
v = _dispatch_once_mark_done(l);
#endif
if (likely((dispatch_lock)v == value_self)) return;
_dispatch_gate_broadcast_slow(&l->dgo_gate, (dispatch_lock)v);
}
_dispatch_once_mark_done內部就是賦值并標記,即解鎖
DISPATCH_ALWAYS_INLINE
static inline uintptr_t
_dispatch_once_mark_done(dispatch_once_gate_t dgo)
{
// 如果不相同,則改成相同,并標記為DLOCK_ONCE_DONE
return os_atomic_xchg(&dgo->dgo_once, DLOCK_ONCE_DONE, release);
}
總結:
GCD單例中,有兩個重要引數,onceToken和block,其中onceToken是靜態變數,具有唯一性,在底層被封裝成了dispatch_once_gate_t型別的變數l,l主要是用來獲取底層原子封裝性的關聯,即變數v,通過v來查詢任務的狀態,如果此時v等于DLOCK_ONCE_DONE,說明任務已經處理過一次了,直接return
如果此時任務沒有執行過,則會在底層通過C++函式的比較,將任務進行加鎖,即任務狀態置為DLOCK_ONCE_UNLOCK,目的是為了保證當前任務執行的唯一性,防止在其他地方有多次定義,加鎖之后進行block回呼函式的執行,執行完成后,將當前任務解鎖,將當前的任務狀態置為DLOCK_ONCE_DONE,在下次進來時,就不會在執行,會直接回傳

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/274029.html
標籤:其他
