prefacio
Antes, cuando estábamos explorando los principios de la animación y el renderizado, publicamos varios artículos y les respondimos
iOS动画是如何渲染,特效是如何工作的疑惑
. Sentimos profundamente que los diseñadores de sistemas tienen una mente tan abierta al crear estos marcos de sistemas, y también深深意识到了解一门技术的底层原理对于从事该方面工作的重要性。
Así que decidimos
进一步探究iOS底层原理的任务
. Siguiendo el artículo anterior para comprender la exploración de [10 bloqueos de subprocesos en iOS y tipos de bloqueos de subprocesos: bloqueos giratorios, bloqueos mutex, bloqueos recursivos], este artículo continuará explorando los principios subyacentes de subprocesamiento múltiple GCD
1. Bloqueo atómico
1. atómico
atomic
setter、getter
La operación atómica utilizada para garantizar que el atributo sea equivalente a getter和setter
agregar un bloqueo de sincronización de subprocesos internamente.
Atomicidad: Un átomo es la unidad física más pequeña, lo que significa que no se puede dividir, es decir, el código se opera como un todo en un mismo hilo.
atomic
Solo se garantiza que setter、getter
sea seguro para subprocesos, no garantiza que el proceso de uso de la propiedad sea seguro para subprocesos
2. Desde el análisis del código fuente getter和setter
para atomic
el uso de
Encontramos la objc4
implementación objc-accessors.mm
correspondiente getter和setter
en
getter
realización
// 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 = objc_retain(*slot);
slotlock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
setter
realización
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 = [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);
}
Se puede ver en el código fuente que solo automic
se bloquearán los atributos.
En segundo lugar, la elección de bloqueos de subprocesos en el esquema de seguridad de lectura y escritura en iOS
Considere los siguientes escenarios, cómo hacer lo más apropiado
- Al mismo tiempo, solo un subproceso puede realizar la operación de escritura.
- Al mismo tiempo, varios subprocesos pueden leer
- Al mismo tiempo, no se permiten operaciones de escritura y lectura.
El escenario anterior es un típico "lectura múltiple y escritura única", que a menudo se usa para leer y escribir datos como archivos. Los esquemas de implementación en iOS incluyen los siguientes dos
pthread_rwlock
: bloqueo de lectura y escrituradispatch_barrier_async
: llamada de valla asíncrona
1. pthread_rwlock
pthread_rwlock
Es un bloqueo dedicado a leer y escribir archivos, y su esencia también es un bloqueo de exclusión mutua. El hilo que espera el bloqueo se irá a dormir.
Utilice el código de la siguiente manera
@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
2. dispatch_barrier_async
dispatch_barrier_async
También llamada función de valla, está diseñada para interceptar operaciones concurrentes asincrónicas de subprocesos múltiples, solo para garantizar que haya un subproceso funcionando al mismo tiempo.
El uso de la función de valla también puede garantizar la operación de lecturas múltiples y escrituras individuales
Utilice el código de la siguiente manera
@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
3. Temporizador
我们日常使用的定时器有以下几个:
- CADisplayLink
- NSTimer
- GCD定时器
1. CADisplayLink
CADisplayLink
是用于同步屏幕刷新频率的定时器
1.1 CADisplayLink和NSTimer的区别
- iOS设备的屏幕刷新频率是固定的,
CADisplayLink
在正常情况下会在每次刷新结束都被调用,精确度相当高 NSTimer
的精确度就显得低了点,比如NSTimer
的触发时间到的时候,runloop
如果在阻塞状态,触发时间就会推迟到下一个runloop
周期。并且NSTimer
新增了tolerance
属性,让用户可以设置可以容忍的触发的时间的延迟范围CADisplayLink
使用场合相对专一,适合做UI的不停重绘,比如自定义动画引擎或者视频播放的渲染。NSTimer
的使用范围要广泛的多,各种需要单次或者循环定时处理的任务都可以使用。在UI相关的动画或者显示内容使用CADisplayLink
比起用NSTimer
的好处就是我们不需要在格外关心屏幕的刷新频率了,因为它本身就是跟屏幕刷新同步的。
1.2 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
,都是强引用,所以会造成循环引用,无法释放
1.3 解决方案
增加第三个对象,通过第三个对象将target调用的方法转发出去,具体如下图所示
实现代码如下
@interface HPProxy : NSObject
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
@implementation HPProxy
+ (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 "HPProxy.h"
@interface ViewController ()
@property (strong, nonatomic) CADisplayLink *link;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.link = [CADisplayLink displayLinkWithTarget:[HPProxy 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
2. NSTimer
NSTimer
也是定时器,相比CADisplayLink
使用范围更广,更灵活,但精确度会低一些
2.1 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
2.2 解决方案
【第一种】借助第三对象并将方法转发,同CADisplayLink
@interface ViewController ()
@property (strong, nonatomic) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[HPProxy 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];
}];
3. 统一优化方案
3.1 NSProxy
NSProxy
是唯一一个没有继承自NSObject
的类,它是专门用来做消息转发的
3.2 特点
- 不继承
NSObject
,也是基类类型 - 没有
init方法
,直接用alloc方法
来初始化 - 没有
forwardingTargetForSelector方法
,只支持消息转发
3.3 优化方案
将HPProxy
继承自NSProxy
,然后在消息转发里替换target
这么做的好处在于NSProxy
相比NSObject
少了消息发送先从父类查找的过程,以及不经过forwardingTargetForSelector
,相比之下性能会高
替换代码如下
@interface HPProxy : 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
3.4 从源码实现来分析
我们先查看下面这句代码打印什么
ViewController *vc = [[ViewController alloc] init];
HPProxy *proxy = [HPProxy proxyWithTarget:vc];
NSLog(@"%d, [proxy isKindOfClass:[ViewController class]]);
打印结果为1,可以看出NSProxy
的isKindOfClass
和NSObject
的isKindOfClass
有所差别
我们可以通过GNUstep
来查看NSProxy
的源码实现,发现其内部会直接调用消息转发的方法,才会有我们将target替换成了ViewController
对象,所以最后调用isKindOfClass
的是ViewController对象
,那么结果也就知晓了
从该方法可以反观NSProxy
的其他方法内部实现,都会主动触发消息转发的实现
4. GCD定时器
GCD定时器
相比其他两个定时器是最准时的,因为和系统内核直接挂钩
使用代码如下
我们将GCD定时器
封装到自定义的LLTimer
文件来使用
// HPTimer.h文件
@interface HPTimer : 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;
// HPTimer.m文件
#import "HPTimer.h"
@implementation HPTimer
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 "HPTimer.h"
@interface ViewController ()
@property (copy, nonatomic) NSString *task;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"begin");
// selector方式
self.task = [HPTimer execTask:self
selector:@selector(doTask)
start:2.0
interval:1.0
repeats:YES
async:YES];
// block方式
// self.task = [HPTimer 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
{
[HPTimer cancelTask:self.task];
}
4.1 HPTimer
- 在
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库
重新开源实现了一遍,虽然不是官网源码,但也有一定的参考价值
源码地址:www.gnustep.org/resources/d…
我们在官网上找到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
,在下次进来时,就不会在执行,会直接返回