前言
之前,咱们在探求动画及烘托相关原理的时分,咱们输出了几篇文章,解答了
iOS动画是怎么烘托,特效是怎么作业的疑问
。咱们深感体系规划者在创作这些体系结构的时分,是如此脑洞大开,也深深意识到了解一门技能的底层原理关于从事该方面作业的重要性。
因此咱们决议
进一步探求iOS底层原理的使命
。继上一篇文章对GCD
的 探求iOS底层原理: 栅门函数dispatch_barrier_async
、dispatch_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_enter
和dispatch_group leave
调配运用,如下:
从上面的两个比如代码能够发现,
dispatch_group_async
相当所以dispatch_group_enter
+dispatch_group leave
的效果!
留意
:dispatch_group_enter
和dispatch_group leave
调配运用,可是次序不能反,否则会奔溃,如下:
dispatch_group_enter
和dispatch_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
函数会一向等到前面group
中的使命履行完,再履行下面代码,但会产生堵塞线程的问题,导致了主线程中的使命5
不能正常运转,直到使命组的使命完结才干被调用。
考虑
:
- 那么调度组是怎么作业,为什么能够调度使命呢?
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
进行保存,这儿的写法和信号量很像(如下图),是模仿的信号量的写法自己写了一个,但并不是调度组底层是运用信号量完结的。
2.2 dispatch_group_enter
dispatch_group_enter
经过os_atomic_sub_orig2o
会进行0
的减减操作,此时的old_bits
等于-1
。
2.3 dispatch_group_leave
dispatch_group_leave
这儿经过os_atomic_add_orig2o
把-1
加加操作,old_state
就等于0
,0 & DISPATCH_GROUP_VALUE_MASK
的成果依然等于0
,也便是old_value
等于0
。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
在old_state
等于0
的状况下,才会去唤醒相关的同步或许异步函数的履行,也便是 block
里边的履行,便是调用同步、异步的那个callout履行。
- 在
dispatch_group_leave
剖析中,咱们现已得到old_state
成果等于0
- 所以这儿也就解说了
dispatch_group_enter
和dispatch_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_enter
和dispatch_group_leave
,所以才干起到相同的作业效果!
dispatch_group_async
dispatch_continuation_t
的处理,也便是使命的包装处理,还做了一些符号处理,最终走_dispatch_continuation_group_async
_dispatch_continuation_group_async
靓仔!看到没有,和猜测的是相同的,内部公然封装了dispatch_group_enter
办法,向组中增加使命时,就调用了dispatch_group_enter办法,将信号量0
变成了-1
。那么现在去找下dispatch_group_leave
的在哪里!持续盯梢流程。。。
_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_continuation_pop_inline -- > _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 品种:
-
DISPATCH_SOURCE_TYPE_DATA_ADD
变量增加 -
DISPATCH_SOURCE_TYPE_DATA_OR
变量OR
-
DISPATCH_SOURCE_TYPE_DATA_REPLACE
新取得的数据值替换现有的 -
DISPATCH_SOURCE_TYPE_MACH_SEND MACH
端口发送 -
DISPATCH_SOURCE_TYPE_MACH_RECV MACH
端口接收 -
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE
内存压力 (注:iOS8后可用) -
DISPATCH_SOURCE_TYPE_PROC
检测到与进程相关的事情 -
DISPATCH_SOURCE_TYPE_READ
可读取文件映像 -
DISPATCH_SOURCE_TYPE_SIGNAL
接收信号 -
DISPATCH_SOURCE_TYPE_TIMER
定时器 -
DISPATCH_SOURCE_TYPE_VNODE
文件体系有变更 -
DISPATCH_SOURCE_TYPE_WRITE
可写入文件映像
规划一个定时器举例:
- 点击屏幕开端
运用dispatch_source
的计时器,能够暂停、开端,一起不受主线程影响,不会受 UI事情
的影响,所以它的计时是精确的。如下图所示:
3.4 运用时留意事项
留意事项
- source 需求手动发动
Dispatch Source
运用最多的便是用来完结定时器,source
创立后默许是暂停状态,需求手动调用 dispatch_resume
发动定会器。 dispatch_after
只是封装调用了dispatch source
定时器,然后在回调函数中履行界说的block
.
- 循环引证
由于 dispatch_source_set_event_handle
回调是block
,在增加到source
的链表上时会履行copy
并被source
强引证,假如block
里持有了self
,self
又持有了source
的话,就会引起循环引证。所以正确的办法是运用weak+strong
或许提早调dispatch_source_cancel
取消timer
。
- resume、suspend 调用次数保持平衡
dispatch_resume
和 dispatch_suspend
调用次数需求平衡,假如重复调用 dispatch_resume
则会溃散,由于重复调用会让dispatch_resume
代码里if
分支不成立,然后履行了 DISPATCH_CLIENT_CRASH(“Over-resume of an object”)
导致溃散。
- source 创立与释放时机
source
在suspend
状态下,假如直接设置 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
这儿的办法是对事情源的相关状态进行判别,假如过度resume
康复,则会goto
走到over_resume
流程,直接调起DISPATCH_CLIENT_CRASH
溃散。
这儿还有对挂起计数的判别,挂起计数包含所有挂起和非活动位的挂起计数。underflow
下溢意味着需求过度康复或暂停计数转移到边计数,也便是说假如当前计数器还没有到可运转的状态,需求接连康复。
4.2 dispatch_suspend
- 挂起
dispatch_suspend
在dispatch_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_slow
同样这儿维护一个暂停挂起的计数器,假如接连调用dispatch_suspend
挂起办法,减法的无符号下溢或许产生,由于其他线程或许在咱们尝试获取锁时触及了该值,或许由于另一个线程争先恐后地履行相同的操作并首要取得锁。
所以不能重复的挂起或许康复,一定要你一个我一个,你两个我也两个,保持一个balanced
。
五、总结
5.1 线程调度组
- 调度组最直接的效果便是操控使命的履行次序
-
dispatch_group_notify
:进组使命履行结束的告诉 -
dispatch_group_wait
函数会一向等到前面group
中的使命履行完,后边的才干够履行 -
dispatch_group_enter
和dispatch_group leave
成对运用 -
dispatch_group_async
内部封装了dispatch_group_enter
和dispatch_group leave
的运用
5.2 事情源
-
运用定时器
NSTimer
需求加入到NSRunloop
,导致计数不精确,能够运用Dispatch Source
来处理 -
Dispatch Source
的运用,要留意康复
和挂起
要平衡
-
source
在suspend
状态下,假如直接设置source = nil
或许从头创立source
都会造成crash
。正确的办法是在resume
状态下调用dispatch_source_cancel(source)
后再从头创立。 -
由于
dispatch_source_set_event_handle
回调是block
,在增加到source
的链表上时会履行copy
并被source
强引证,假如block
里持有了self
,self
又持有了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多态原理
、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-小程序结构烘托原理