前言

之前,咱们在探求动画及烘托相关原理的时分,咱们输出了几篇文章,回答了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

【第二种】运用NSTimerblock回调来调用办法,并将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,能够看出NSProxyisKindOfClassNSObjectisKindOfClass有所差别

咱们能够经过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.mperformSelector: 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,而且崩溃了。

由于履行线程的blockperformSelector几乎是一起的,所以先履行了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单例中,有两个重要参数,onceTokenblock,其间onceToken是静态变量,具有唯一性,在底层被封装成了dispatch_once_gate_t类型的变量ll主要是用来获取底层原子封装性的相关,即变量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多态原理StringArrayDictionary引证计数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-小程序结构烘托原理