iOS-底层原理28-Block底层原理

《iOS底层原理文章汇总》
上一篇文章《iOS-底层原理27-锁和Block》介绍了NSLock,NSCondition,NSConditionLock,条件变量和条件锁的底层原理及三种类型Block,本文接着介绍Block底层原理

1.Block循环引用

I.循环引用无法释放的原因

image.png

image.png

self中持有block,block中持有self,pop时无法进行release,从而无法进入VC的dealloc方法从而无法给block发送release消息来进行block的retainCount减一到0释放


image.png

II.循环引用解决办法

思维误区:我们经常会想到一个解决办法就是,通过中间变量WeakSelf解除相互强引用,中间变量WeakSelf储存在弱引用表中,不会对self的引用计数retainCount进行加1,为什么呢?后面分析。


image.png

此种解除循环引用的方式会有漏洞,若延时执行block中的内容将会无法释放,self的生命周期无法保全,于是就有下面的解决办法


image.png
A.强弱共舞:weakSelf在block内是临时变量,block内部语句执行完后strongSelf释放,weakSelf从弱引用表里面置为nil,self也可以进行释放了,属于自动释放
image.png
B.中介者模式:手动释放,vc使用完后就置为nil,vc使用__block修饰使block内部能对外界变量进行修改,vc虽为临时变量在viewDidLoad中,但vc在函数viewDidLoad执行完后并未释放,vc被block捕获到内存里面进行持有,底层进行了三层拷贝
image.png
C.传值模式:self作为临时变量传入block中压栈,block中不再持有self,不构成持有引用关系
image.png
D.proxy和NSObject平行的类,后续展开分析

2.Block底层原理

image.png

I.Block的本质:能进行%@打印,是对象结构体,匿名函数,能存放代码也叫代码块,Blcok的本质是结构体,里面有结构体同名的构造函数__main_block_impl_0在初始化是传入两个参数__main_block_func_0和&__main_block_desc_0_DATA,也称为函数,函数没有名字引申为匿名函数

image.png

__main_block_func_0作为参数传入构造函数中impl.FuncPtr = fp,调用时传入block相当于((block)->FuncPtr)(block),相当于执行__main_block_func_0函数来执行block中的内容printf("LG_Cooci")


image.png

II.Block捕获外部变量:编译时就在结构体__main_block_impl_0中生成变量int a,值拷贝,a++无法改变a的值,编译时就存在和调不调用没有关系,编译时默认给的isa为NSConcreteStackBlock

image.png

编译时就在结构体__main_block_impl_0中生成变量int a
编译时默认给的isa为NSConcreteStackBlock
两个a并不是同一个a,值拷贝,a++无法改变__cself->a的值
a只读不能写

III.Block中的捕获外部变量加__block修饰,在block中对a进行++,会生成结构体__Block_byref_a_0并初始化赋值,&a = *__forwarding,传入结构体__Block_byref_a_0 a的地址&a到block中,传入a的指针地址_a->__forwarding赋值给block中的结构体__Block_byref_a_0指针变量a,(a->__forwarding->a)++就是生成的外部变量结构体__Block_byref_a_0中的变量a++,是指针拷贝,指针地址中指向的值进行++

image.png

3.__block在底层做了什么,__main_block_copy_0,__main_block_dispose_0,__main_block_desc_0,__main_block_dispose_0,_Block_object_assign底层做了什么

I.Block的copy:在ViewDidLoad中对block下断点查看汇编,发现进入objc_retainBlock中,下符号断点往下执行_Block_copy,找到源码libsystem_blocks.dylib

image.png

image.png

在通过clang编译的block.cpp文件中也能发下Block_private.h文件,Block在底层的形式是Block_layout类型


image.png

image.png

探索Block内存变化-调用情况-签名
开始的Block为全局Block类型NSGlobalBlock,若捕获外界变量,则会由NSStackBlock变为NSMallocBlock,在NSStackBlock中进行Block_copy操作变为NSMallocBlock
通过汇编分析捕获外界变量的block类型为NSStackBlock经过拷贝类型为NSMallocBlock


image.png

由上文知道,捕获外界变量的Block本来为NSStackBlock,经过objc_retainBlock-->Block_copy之后变为NSMallocBlock
image.png

4.Block的invoke

进入汇编查看block_invoke,经过平移操作后得到x8直接调用NSLog打印,开始输出


image.png

为什么会平移16个字节得到block_invoke呢?通过查看Block的源码结构,Block_layout中isa8字节,flags4字节,reserved4字节,8+4+4=16字节,平移16字节得到invoke,invoke为传入的Block中的代码块


image.png

5.Block的签名signature

flags用来做辨识码,根据flags知道是否存在可选变量Block_descriptor_2和Block_descriptor_3,Block_descriptor_1一定会有,flags为BLOCK_HAS_COPY_DISPOSE,有Block_descriptor_2,flags为BLOCK_HAS_SIGNATURE有Block_descriptor_3


image.png

image.png

flags是否为BLOCK_HAS_COPY_DISPOSE决定是否有Block_descriptor_2
flags是否为BLOCK_HAS_SIGNATURE决定是否有Block_descriptor_3


image.png

若存在Block_descriptor_2和Block_descriptor_3则采取
内存平移的方式获取
存在Block_descriptor_2则从Block_descriptor_1起

始地址平移Block_descriptor_1的size
存在Block_descriptor_3则从Block_descriptor_1起
始地址平移Block_descriptor_1的size,判断是否存在
Block_descriptor_2,若存在Block_descriptor_2则再从
当前地址平移Block_descriptor_2的size


image.png

通过Block的内存分布情况验证Block的结构
flags和reserved共同组成8字节,reserved为0则在后面补0可忽略,Block_descriptor_1中的reserved为0,uintptr_t reserved占用8字节,uintptr_t size占8字节
0x0000000102f9f153为Block_descriptor_3中的第一个元素char *signature
1 << 25为BLOCK_HAS_COPY_DISPOSE的值,与上flags的值为0,则没有Block_descriptor_2
1 << 30为BLOCK_HAS_SIGNATURE的值,与上flags的值不为0,则有Block_descriptor_3
image.png

底层继续深入block_hook biffi

6.Block_copy怎么将栈区对象拷贝到堆区的,Block_copy源码

  • I.进行强制类型转换 Block_layout是block底层类型
  • II.判断是否需要释放
  • III.若是全局Block,不需要拷贝
  • IV.不是全局Block,只能是栈区或堆区Block,而堆区Block无法在编译时期编译出来,只能是栈区Block
  • V.申请堆区空间
  • VI.进行内存拷贝,原对象的Block拷贝到新的对象Block中
  • VII.原对象的invoke拷贝到新对象的invoke中,这儿就能执行之前例子中的内容NSLog
  • VIII.是否正在进行析构
  • IX.isa标识为NSMallocBlock


    image.png

7.Block的三层拷贝:怎么对外界变量操作

A.Block_discriptor_2中有copy变量和dispose变量进行copy和dispose操作,对外界变量进行copy处理底层调用Block_object_assign,对外界变量进行dispose操作,底层调用Block_object_dispose

image.png

Block结构体中flags是否为BLOCK_HAS_COPY_DISPOSE
决定是否有Block_descriptor_2,若有会有copy和dispose
函数赋值,copy对外界变量进行copy处理,底层调用_Block_object_assign((void*)&dst->lg_name,dispose对外界变量进行dispose处理,底层调用_Block_object_dispose((void*)src->lg_name
image.png

image.png

B.对外界变量操作调用Block_object_assign,根据Block中flags的值与BLOCK_ALL_COPY_DISPOSE_FLAGS运算后的类型进行不同的操作,用的最多的是第一种纯对象类型BLOCK_FIELD_IS_OBJECT,在第三层拷贝,如NSString的值拷贝时会应用,第三种__block修饰的外界变量类型BLOCK_FIELD_IS_BYREF,编译时__block修饰的类型会变为结构体类型Block_byref如__Block_byref_lg_name_0,在第二层拷贝时会应用

image.png

C.byref_keep在什么时候赋值的?int a=10,不是对象类型,clang编译不会有byref_keep,可以进行第二层拷贝不会有第三层拷贝,先进入第二层拷贝*dest = _Block_byref_copy(object)Block里面的结构体指针指向的内存地址就是外部的结构体地址,故结构体内部修改a=11,能改变外部的变量的值

image.png

D.满足第三种情况BLOCK_FIELD_IS_BYREF,需要捕获的外部变量为对象类型即编译生成的结构体类型中有对象类型如NSString进行clang编译,NSString是对象类型,Block_byref结构体中包含对象类型NSString *lg_name,才满足进行第三层拷贝的条件,将byref_keep保存对象后在合适的时候进行调用,byref_keep在什么时候赋值的呢?及是否还有Block_byref_3?

image.png

image.png

image.png

image.png

E.是否有Block_byref_2和Block_byref_3由flags的值BLOCK_BYREF_HAS_COPY_DISPOSE和BLOCK_BYREF_LAYOUT_EXTENDED决定,编译器拷贝,下层进行识别有Block_byref_2,进行拷贝时调用(*src2->byref_keep)(copy, src),即调用编译时传入的__Block_byref_id_object_copy,调用__Block_byref_id_object_copy,又一次调用_Block_object_assign,此时出入的为纯对象类型BLOCK_FIELD_IS_OBJECT,进行指针地址拷贝*dest = object,结构体__Block_byref_lg_name_0内存平移40得到NSString lg_name的内存地址的值,指向外部变量存放字符串常量LG_Cooci的地址,故Block内部对lg_name进行改变,外部也会跟着变,属于同一片地址空间,任何对象都是平移58吗?则由Block对象中flags的值是否为BLOCK_BYREF_LAYOUT_EXTENDED决定,即是否有Block_byref_3,若有则会增加一个变量char *layout,则去平移6 * 8 = 48个字节

image.png

image.png

image.png

针对捕获外界变量__block修饰的Block的三层拷贝

  • I.第一层拷贝:整个Block的拷贝从栈区NSStackBlock拷贝到堆区NSMallocBlock,前文在汇编代码中已经分析
  • II.__block修饰的捕获的外部变量结构体__Block_byref_lg_name_0的拷贝,调用*dest = _Block_byref_copy(object)第二层拷贝,对捕获的外部对象变量编译为结构体__Block_byref_lg_name_0后的拷贝,因为要在block内部去修改外部__block修饰的对象(编译为__Block_byref_lg_name_0类型的结构体了)的值,所以拷贝整个结构体到block中
  • III.捕获的外部变量为对象类型如NSString,作为成员NSString lg_name存在于结构体__Block_byref_lg_name_0中,通过内存平移58 = 40个字节得到值,要修改则进行指针拷贝,第三层拷贝,指针指向同一片内存地址*dest=object,内存地址中存放着字符串常量的值如LG_Cooci
    image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容