前言
之前,咱们在探求动画及烘托相关原理的时分,咱们输出了几篇文章,回答了
iOS动画是如何烘托,特效是如何作业的疑惑
。咱们深感体系设计者在创作这些体系结构的时分,是如此脑洞大开,也深深意识到了解一门技能的底层原理关于从事该方面作业的重要性。
因此咱们决定
进一步探求iOS底层原理的使命
。继上一篇文章了解了【iOS中的10个线程锁,与线程锁类型:自旋锁、互斥锁、递归锁】
探求之后,本篇文章将继续对GCD多线程底层原理的探求
一、原子锁atomic
1. atomic
atomic
用于确保特色setter、getter
的原子性操作,适当于在getter和setter
内部加了线程同步的锁
原子性:原子即为最小的物理单位,意味不可再切割;即代码都为一个全体在同一线程进行操作
atomic
仅仅确保setter、getter
是线程安全的,并不能确保运用特色的过程是线程安全的
2. 从源码剖析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 = 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 = [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
:异步栅门调用
1. 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
2. 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定时器
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
,在下次进来时,就不会在履行,会直接回来
专题系列文章
1.前常识
- 01-探求iOS底层原理|总述
- 02-探求iOS底层原理|编译器LLVM项目【Clang、SwiftC、优化器、LLVM】
- 03-探求iOS底层原理|LLDB
- 04-探求iOS底层原理|ARM64汇编
2. 根据OC语言探求iOS底层原理
- 05-探求iOS底层原理|OC的实质
- 06-探求iOS底层原理|OC目标的实质
- 07-探求iOS底层原理|几种OC目标【实例目标、类目标、元类】、目标的isa指针、superclass、目标的办法调用、Class的底层实质
- 08-探求iOS底层原理|Category底层结构、App启动时Class与Category装载过程、load 和 initialize 履行、相关目标
- 09-探求iOS底层原理|KVO
- 10-探求iOS底层原理|KVC
- 11-探求iOS底层原理|探求Block的实质|【Block的数据类型(实质)与内存布局、变量捕获、Block的品种、内存办理、Block的修饰符、循环引证】
- 12-探求iOS底层原理|Runtime1【isa详解、class的结构、办法缓存cache_t】
- 13-探求iOS底层原理|Runtime2【音讯处理(发送、转发)&&动态办法解析、super的实质】
- 14-探求iOS底层原理|Runtime3【Runtime的相关使用】
- 15-探求iOS底层原理|RunLoop【两种RunloopMode、RunLoopMode中的Source0、Source1、Timer、Observer】
- 16-探求iOS底层原理|RunLoop的使用
- 17-探求iOS底层原理|多线程技能的底层原理【GCD源码剖析1:主行列、串行行列&&并行行列、大局并发行列】
- 18-探求iOS底层原理|多线程技能【GCD源码剖析1:dispatch_get_global_queue与dispatch_(a)sync、单例、线程死锁】
- 19-探求iOS底层原理|多线程技能【GCD源码剖析2:栅门函数dispatch_barrier_(a)sync、信号量dispatch_semaphore】
- 20-探求iOS底层原理|多线程技能【GCD源码剖析3:线程调度组dispatch_group、事情源dispatch Source】
- 21-探求iOS底层原理|多线程技能【线程锁:自旋锁、互斥锁、递归锁】
- 22-探求iOS底层原理|多线程技能【原子锁atomic、gcd Timer、NSTimer、CADisplayLink】
- 23-探求iOS底层原理|内存办理【Mach-O文件、Tagged Pointer、目标的内存办理、copy、引证计数、weak指针、autorelease
3. 根据Swift语言探求iOS底层原理
关于函数
、枚举
、可选项
、结构体
、类
、闭包
、特色
、办法
、swift多态原理
、String
、Array
、Dictionary
、引证计数
、MetaData
等Swift基本语法和相关的底层原理文章有如下几篇:
- Swift5中心语法1-根底语法
- Swift5中心语法2-面向目标语法1
- Swift5中心语法2-面向目标语法2
- Swift5常用中心语法3-其它常用语法
- Swift5使用实践常用技能点
其它底层原理专题
1.底层原理相关专题
- 01-计算机原理|计算机图形烘托原理这篇文章
- 02-计算机原理|移动终端屏幕成像与卡顿
2.iOS相关专题
- 01-iOS底层原理|iOS的各个烘托结构以及iOS图层烘托原理
- 02-iOS底层原理|iOS动画烘托原理
- 03-iOS底层原理|iOS OffScreen Rendering 离屏烘托原理
- 04-iOS底层原理|因CPU、GPU资源消耗导致卡顿的原因和解决计划
3.webApp相关专题
- 01-Web和类RN大前端的烘托原理
4.跨平台开发计划相关专题
- 01-Flutter页面烘托原理
5.阶段性总结:Native、WebApp、跨平台开发三种计划功能比较
- 01-Native、WebApp、跨平台开发三种计划功能比较
6.Android、HarmonyOS页面烘托专题
- 01-Android页面烘托原理
-
02-HarmonyOS页面烘托原理 (
待输出
)
7.小程序页面烘托专题
- 01-小程序结构烘托原理