前言

之前,咱们在探求动画及烘托相关原理的时分,咱们输出了几篇文章,解答了iOS动画是怎么烘托,特效是怎么作业的疑问。咱们深感体系规划者在创作这些体系结构的时分,是如此脑洞大开,也 深深意识到了解一门技能的底层原理关于从事该方面作业的重要性。

因此咱们决议 进一步探求iOS底层原理的使命。继上一篇文章对GCD的 探求iOS底层原理: 栅门函数dispatch_barrier_asyncdispatch_barrier_sync、信号量dispatch_semaphore探求之后,本篇文章将持续对GCD多线程底层原理的探求

一、线程调度组dispatch_group

1.1 调度组介绍

调度组最直接的效果便是操控使命的履行次序

  • dispatch_group_create :创立调度组
  • dispatch_group_async :进组的使命 履行
  • dispatch_group_notify :进组使命履行结束的告诉
  • dispatch_group_wait : 进组使命履行等候时刻
  • dispatch_group_enter :使命进组
  • dispatch_group leave :使命出组

1.2 调度组举例

下面举个调度组的运用举例

给图片增加水印,有两张水印照片需求网络恳求,水印照片恳求,完结之后,再增加到本地图片上面显示!

//创立调度组
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 水印 1
dispatch_group_async(group , queue, ^{
        NSString *logoStr1 = @"https://thirdqq.qlogo.cn/g?b=sdk&k=zeIp1PmCE6jff6BGSbjicKQ&s=140&t=1556562300";
        NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr1]];
        UIImage *image1 = [UIImage imageWithData:data1];
        [self.mArray addObject:image1];
});
// 水印 1
dispatch_group_async(group , queue, ^{
        NSString *logoStr2 = @"https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTJKuHEuLLyYK0Rbw9s9G8jpcnMzQCNsuYJRIRjCvltH6NibibtP73EkxXPR9RaWGHvmHT5n69wpKV2w/132";
        NSData *data2 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr2]];
        UIImage *image2 = [UIImage imageWithData:data2];
        [self.mArray addObject:image2];
});
// 水印恳求完结
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        UIImage *newImage = nil;
        NSLog(@"恳求结束,增加水印");
        for (int i = 0; i<self.mArray.count; i++) {
                UIImage *waterImage = self.mArray[i];
                newImage =[JP_ImageTool jp_WaterImageWithWaterImage:waterImage backImage:newImage waterImageRect:CGRectMake(20, 100*(i+1), 120, 60)];
        }
        self.imageView.image = newImage;
});
  • 增加水印前

模拟器运转成果—增加水印前

  • 增加水印后

模拟器运转成果—增加水印后 当组内的使命全部履行完结了,dispatch_group_notify会告诉,使命现已完结了,内部增加水印的作业能够开端了!

上面的比如还能够运用dispatch_group_enterdispatch_group leave 调配运用,如下:

进组和出组调配运用

从上面的两个比如代码能够发现,dispatch_group_async 相当所以dispatch_group_enter + dispatch_group leave 的效果!

留意dispatch_group_enterdispatch_group leave 调配运用,可是次序不能反,否则会奔溃,如下:

奔溃截图dispatch_group_enterdispatch_group leave 调配运用,除了次序不发,个数也得保持一致,人家是收支成双成对,你不能把它们分开,否则也会罢工或许奔溃的!

  • dispatch_group_enter进组不出组状况

进组不出组状况

dispatch_group_enter进组不出组,那么dispatch_group_notify就不会收到使命履行完结的告诉,dispatch_group_notify内的使命就履行不了

  • 不进组就出组 dispatch_group leave 状况

不进组就出组

不进组就出组,程序会奔溃,都没有使命进去,你去出去,出个锤子哦!😢

  • dispatch_group_wait等候 举例

dispatch_group_wait举例dispatch_group_wait有点栅门的感觉,堵住了组里边前面的使命,可是并没有堵塞主线程。那么再看看下面这个比如

dispatch_group_wait举例

  • 这儿运用了dispatch_group_wait进行等候
  • dispatch_group_wait函数会一向等到前面group中的使命履行完,再履行下面代码,但会产生堵塞线程的问题,导致了主线程中的使命5不能正常运转,直到使命组的使命完结才干被调用。

考虑

  1. 那么调度组是怎么作业,为什么能够调度使命呢?
  2. dispatch_group_enter进组和dispatch_group_leave出组为什么能够起到与调度组dispatch_group_async相同的效果呢?

现在去看看源码寻找答案!

二、调度组源码剖析

2.1 dispatch_group_create

  • dispatch_group_create
dispatch_group_t
dispatch_group_create(void)
{
	return _dispatch_group_create_with_count(0);
}

创立调度组会调用_dispatch_group_create_with_count办法,并默许传入0

  • _dispatch_group_create_with_count
static inline dispatch_group_t
_dispatch_group_create_with_count(uint32_t n)
{
	dispatch_group_t dg = _dispatch_object_alloc(DISPATCH_VTABLE(group),
			sizeof(struct dispatch_group_s));
	dg->do_next = DISPATCH_OBJECT_LISTLESS;
	dg->do_targetq = _dispatch_get_default_queue(false);
	if (n) {
		os_atomic_store2o(dg, dg_bits,
				(uint32_t)-n * DISPATCH_GROUP_VALUE_INTERVAL, relaxed);
		os_atomic_store2o(dg, do_ref_cnt, 1, relaxed); // <rdar://22318411>
	}
	return dg;
}

_dispatch_group_create_with_count办法里边经过os_atomic_store2o来把传入的 n进行保存,这儿的写法和信号量很像(如下图),是模仿的信号量的写法自己写了一个,但并不是调度组底层是运用信号量完结的。

dispatch_semaphore_create

2.2 dispatch_group_enter

  • dispatch_group_enter

dispatch_group_enter 经过os_atomic_sub_orig2o会进行0的减减操作,此时的old_bits等于-1

2.3 dispatch_group_leave

  • dispatch_group_leave

dispatch_group_leave

这儿经过os_atomic_add_orig2o-1加加操作,old_state就等于00 & DISPATCH_GROUP_VALUE_MASK的成果依然等于0,也便是old_value等于0DISPATCH_GROUP_VALUE_1的界说如下代码: DISPATCH_GROUP_VALUE_1

从代码中能够看出old_value是不等于DISPATCH_GROUP_VALUE_MASK的,所以代码会履行到外面的if中去,并调用_dispatch_group_wake办法进行唤醒,唤醒的便是dispatch_group_notify办法。

也便是说,假如不调用dispatch_group_leave办法,也就不会唤醒dispatch_group_notify,下面的流程也就不会履行了。

2.4 dispatch_group_notify

  • dispatch_group_notify

dispatch_group_notifyold_state等于0的状况下,才会去唤醒相关的同步或许异步函数的履行,也便是 block里边的履行,便是调用同步、异步的那个callout履行。

  • dispatch_group_leave剖析中,咱们现已得到old_state成果等于0
  • 所以这儿也就解说了dispatch_group_enterdispatch_group_leave为什么要配合起来运用的原因,经过信号量的操控,避免异步的影响,能够及时唤醒并调用dispatch_group_notify办法
  • dispatch_group_leave里边也有调用_dispatch_group_wake办法,这是由于异步的履行,使命是履行耗时的,有或许dispatch_group_leave这行代码还没有走,就先走了dispatch_group_notify办法,但这时分dispatch_group_notify办法里边的使命并不会履行,只是把使命增加到 group
  • 它会等dispatch_group_leave履行了被唤醒才履行,这样就确保了异步时,dispatch_group_notify里边的使命不丢弃,能够正常履行。如下图所示:

示意图

  • 当履行使命 2的时分,是耗时使命(sleep(5)模拟耗时),异步不会堵塞,会履行后边的代码,便是图中①,dispatch_group_notify里边的使命会包装起来,进group
  • 包装完结,异步履行完,这时分就走 ②了,又回到dispatch_group_leave处去履行了,这时分就能够经过 group 拿到使命 4,直接去调用_dispatch_group_wake使命 4唤醒履行了。
  • 这一波是非常的细节,苹果工程师真是妙啊!

苹果工程师牛逼

2.5 dispatch_group_async

猜测dispatch_group_async里边应该是封装了dispatch_group_enterdispatch_group_leave,所以才干起到相同的作业效果!

  • dispatch_group_async

dispatch_group_async

dispatch_continuation_t的处理,也便是使命的包装处理,还做了一些符号处理,最终走_dispatch_continuation_group_async

  • _dispatch_continuation_group_async

_dispatch_continuation_group_async

靓仔!看到没有,和猜测的是相同的,内部公然封装了dispatch_group_enter办法,向组中增加使命时,就调用了dispatch_group_enter办法,将信号量0变成了-1。那么现在去找下dispatch_group_leave的在哪里!持续盯梢流程。。。

  • _dispatch_continuation_async

_dispatch_continuation_async

这一波又是非常的了解了,这个dx_push咱们都现已非常了解了,异步、同步的时分经常见这个办法,这儿就不再赘述了(传送门),会调用:

  • _dispatch_root_queue_push — >
  • _dispatch_root_queue_push_inline — >
  • _dispatch_root_queue_poke — >
  • _dispatch_root_queue_poke_slow — >
  • _dispatch_root_queues_init — >
  • _dispatch_root_queues_init_once — >
  • _dispatch_worker_thread2 — > _dispatch_root_queue_drain_dispatch_root_queue_drain 然后_dispatch_root_queue_drain -- > _dispatch_continuation_pop_inline -- > _dispatch_continuation_with_group_invoke

_dispatch_continuation_with_group_invoke

在最终_dispatch_continuation_with_group_invoke里边咱们找到了出组的办法dispatch_group_leave 在这儿完结_dispatch_client_callout函数调用,紧接着调用dispatch_group_leave办法,将信号量由-1变成了0

至此完结闭环,完整的剖析了调度组、进组、出组、告诉的底层原理和联系。

三、 Dispatch Source 介绍

3.1 Dispatch Source简介

Dispatch Source BSD体系内核惯有功能kqueue的包装,kqueue是在XNU内核中产生事情时在运用程序编程方履行处理的技能。

它的CPU负荷非常小,尽量不占用资源。当事情产生时,Dispatch Source 会在指定的Dispatch Queue中履行事情的处理。

  • dispatch_source_create :创立源
  • dispatch_source_set_event_handler: 设置源的回调
  • dispatch_source_merge_data: 源事情设置数据
  • dispatch_source_get_data: 获取源事情的数据
  • dispatch_resume:康复持续
  • dispatch_suspend:挂起

咱们在日常开发中,经常会运用计时器NSTimer,例如发送短信的倒计时,或许进度条的更新。可是NSTimer需求加入到NSRunloop中,还遭到mode的影响。收到其他事情源的影响,被打断,当滑动scrollView的时分,模式切换,定时器就会停止,然后导致timer的计时不精确。

GCD提供了一个处理计划dispatch_source来出来相似的这种需求场景。

  • 时刻较精确,CPU负荷小,占用资源少
  • 能够运用子线程,处理议时器跑在主线程上卡UI问题
  • 能够暂停,持续,不必像NSTimer相同需求从头创立

3.2 Dispatch Source 运用

创立事情源的代码:

// 办法声明
dispatch_source_t dispatch_source_create(
        dispatch_source_type_t type,
        uintptr_t handle,
        unsigned long mask,
        dispatch_queue_t _Nullable queue);
// 完结进程
dispatch_source_t source =  dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0,  dispatch_get_main_queue());

创立的时分,需求传入两个重要的参数:

  • dispatch_source_type_t 要创立的源类型
  • dispatch_queue_t 事情处理的调度行列

3.3 Dispatch Source 品种

  • Dispatch Source 品种:
  1. DISPATCH_SOURCE_TYPE_DATA_ADD 变量增加
  2. DISPATCH_SOURCE_TYPE_DATA_OR 变量 OR
  3. DISPATCH_SOURCE_TYPE_DATA_REPLACE 新取得的数据值替换现有的
  4. DISPATCH_SOURCE_TYPE_MACH_SEND MACH 端口发送
  5. DISPATCH_SOURCE_TYPE_MACH_RECV MACH 端口接收
  6. DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 内存压力 (注:iOS8后可用)
  7. DISPATCH_SOURCE_TYPE_PROC 检测到与进程相关的事情
  8. DISPATCH_SOURCE_TYPE_READ 可读取文件映像
  9. DISPATCH_SOURCE_TYPE_SIGNAL 接收信号
  10. DISPATCH_SOURCE_TYPE_TIMER 定时器
  11. DISPATCH_SOURCE_TYPE_VNODE 文件体系有变更
  12. DISPATCH_SOURCE_TYPE_WRITE 可写入文件映像

规划一个定时器举例: 创立定时器办法

  • 点击屏幕开端

定时器操控办法 运用dispatch_source的计时器,能够暂停、开端,一起不受主线程影响,不会受 UI事情的影响,所以它的计时是精确的。如下图所示:

运转成果

3.4 运用时留意事项

留意事项

  1. source 需求手动发动

Dispatch Source 运用最多的便是用来完结定时器,source创立后默许是暂停状态,需求手动调用 dispatch_resume发动定会器。 dispatch_after只是封装调用了dispatch source定时器,然后在回调函数中履行界说的block.

  1. 循环引证

由于 dispatch_source_set_event_handle回调是block,在增加到source的链表上时会履行copy并被source强引证,假如block里持有了selfself又持有了source的话,就会引起循环引证。所以正确的办法是运用weak+strong或许提早调dispatch_source_cancel取消timer

  1. resume、suspend 调用次数保持平衡

dispatch_resume dispatch_suspend 调用次数需求平衡,假如重复调用 dispatch_resume则会溃散,由于重复调用会让dispatch_resume 代码里if分支不成立,然后履行了 DISPATCH_CLIENT_CRASH(“Over-resume of an object”) 导致溃散。

  1. source 创立与释放时机

sourcesuspend状态下,假如直接设置 source = nil 或许从头创立 source 都会造成 crash。正确的办法是在resume状态下调用 dispatch_source_cancel(source)后再从头创立。

四、 Dispatch Source源码剖析

那么去底层源码看看,为什么会呈现上面的一些问题。

4.1 dispatch_resume

  • dispatch_resume
void
dispatch_resume(dispatch_object_t dou)
{
	DISPATCH_OBJECT_TFB(_dispatch_objc_resume, dou);
	if (unlikely(_dispatch_object_is_global(dou) ||
			_dispatch_object_is_root_or_base_queue(dou))) {
		return;
	}
	if (dx_cluster(dou._do) == _DISPATCH_QUEUE_CLUSTER) {
		_dispatch_lane_resume(dou._dl, DISPATCH_RESUME);
	}
}

dispatch_resume``会去履行_dispatch_lane_resume

  • _dispatch_lane_resume

_dispatch_lane_resume 这儿的办法是对事情源的相关状态进行判别,假如过度resume康复,则会goto走到over_resume流程,直接调起DISPATCH_CLIENT_CRASH溃散。

这儿还有对挂起计数的判别,挂起计数包含所有挂起和非活动位的挂起计数。underflow下溢意味着需求过度康复或暂停计数转移到边计数,也便是说假如当前计数器还没有到可运转的状态,需求接连康复。

4.2 dispatch_suspend

  • 挂起dispatch_suspend

dispatch_suspenddispatch_suspend的界说里边也能够发现,康复和挂起一定要保持平衡,挂起的目标不会调用与其相关的任何block。 在与目标相关的任何运转的 block完结后,目标将被挂起。

void
dispatch_suspend(dispatch_object_t dou)
{
	DISPATCH_OBJECT_TFB(_dispatch_objc_suspend, dou);
	if (unlikely(_dispatch_object_is_global(dou) ||
			_dispatch_object_is_root_or_base_queue(dou))) {
		return;
	}
	if (dx_cluster(dou._do) == _DISPATCH_QUEUE_CLUSTER) {
		return _dispatch_lane_suspend(dou._dl);
	}
}
  • _dispatch_lane_suspend

_dispatch_lane_suspend

  • _dispatch_lane_suspend_slow

_dispatch_lane_suspend_slow 同样这儿维护一个暂停挂起的计数器,假如接连调用dispatch_suspend挂起办法,减法的无符号下溢或许产生,由于其他线程或许在咱们尝试获取锁时触及了该值,或许由于另一个线程争先恐后地履行相同的操作并首要取得锁。

所以不能重复的挂起或许康复,一定要你一个我一个,你两个我也两个,保持一个balanced

五、总结

5.1 线程调度组

  • 调度组最直接的效果便是操控使命的履行次序
  • dispatch_group_notify :进组使命履行结束的告诉
  • dispatch_group_wait 函数会一向等到前面group中的使命履行完,后边的才干够履行
  • dispatch_group_enterdispatch_group leave 成对运用
  • dispatch_group_async 内部封装了dispatch_group_enterdispatch_group leave 的运用

5.2 事情源

  • 运用定时器NSTimer需求加入到NSRunloop,导致计数不精确,能够运用Dispatch Source来处理

  • Dispatch Source的运用,要留意康复挂起平衡

  • sourcesuspend状态下,假如直接设置 source = nil 或许从头创立 source 都会造成 crash。正确的办法是在resume状态下调用 dispatch_source_cancel(source)后再从头创立。

  • 由于 dispatch_source_set_event_handle回调是block,在增加到source的链表上时会履行copy并被source强引证,假如block里持有了selfself又持有了source的话,就会引起循环引证。所以正确的办法是运用weak+strong或许提早调dispatch_source_cancel取消timer

专题系列文章

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-小程序结构烘托原理