block 源码分析 底层原理

block底层原理是什么?

封装了函数调用以及调用环境的OC对象

block

将main.m文件转换成C++文件,当前文件夹下

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp

通过分析main.cpp 我们可以看到编译后的block 。

block编译

我们可以看出block进过编译后生成一个__main_block_impl_0 的结构体,内部有一个__block_impl结构体变量,而且__block_impl内部有一个isa指针,说明block其实也是属于OC对象的,而且我们看到block内部使用的age变量,也存在于block内部,说明当block内部使用变量的时候,会将变量也传入block内部进行使用。下面我们就具体来分析下block内部的本质。

block的调用流程:

通过编译文件我们可以看到几个结构体

__block_impl  :isa指针、FuncPtr 指针:指向的block需要执行的代码地址
__main_block_impl_0 :结构体内部存在一个__main_block_impl_0函数,这是属于C++的构造器函数,返回是当前的一个结构体,相当于OC里面的init函数
__main_block_func_0 : 执行block内部的需要执行的代码
__main_block_desc_0 : block的描述信,第一个参数是0,第二个是block的 sizeof 内存大小。

block定义

void(*block)(void) = ((void(*)())&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA, age));
//去除 强制转换类型代码 伪代码 :
void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age))
//我们可以看出 block内部 是通过调用__main_block_impl_0函数 返回来的结构体,并取其地址,__main_block_func_0 和 &__main_block_desc_0_DATA, age是传递的三个参数

block对变量的捕获(可以根据上述方法,查看编译文件)

类型 是否捕获 原因
局部变量 出了作用域会销毁,需要捕获保留,值传递
局部(全局)常量static static创建在程序退出之前始终存在内存中,所以采用指针传递
全局变量 在当前作用域是不会销毁的,即使销毁了,block也会一起销毁,所以不需要捕获
self 每个函数都有隐式参数(Class *self,SEL _cmd)所以self属于局部变量,需要捕获
成员变量 成员变量的本质就是 self->name访问,所以也是需要捕获self变量来进行访问,注意捕获的是self,而并非是_name
函数调用 A函数调用B函数 ([self b])会进行消息转发机制 objc_msgSend(self,SEL b,参数),所以也是捕获的self
int global = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
static int height = 10;
void (^block)(void) = ^{
NSLog(@"age :----%d",age);
NSLog(@"height :----%d",height);
NSLog(@"global :----%d",global);
};
age = 20;
height =20;
global = 20;
block();
}
return 0;}

#interface Person :NSobject
@property (nomotic,assign)int age;
#end
#implentation Person
- (void)test {      --》隐式参数- (void)test:(Person * self,SEL _cmd)
}
#end
//从上一部分我们可以看出,block内部存在外部的变量,block内部会创建相应的变量来接受外部变量,此时block内部的变量已经不是外部变量了
  区别是,
  auto属性(默认属性) 属于值传递 所以输出是10
  static 属性是指针传递 输出是20
  全局变量 不会捕获到block内部,直接调用 输出是20
  局部变量因为作用域问题,aoto局部变量出了作用域会自动销毁,所以block需要及时捕获值
  static局部变量 是一直储存在内存中的,所以采用指针访问。
  全局变量,可以直接访问,所以不需要捕获也能访问。
  self是否会捕获? 隐式参数(每个函数都会有2个默认参数 就是当前 调用者self,SEL _cmd(方法名)),所以self是属于局部变量,所以会捕获
  成员变量(_name),本质是调用self->name   所以也会捕获.

block分类

block分类以及继承关系

不同的block分布在内存中的位置不同

类型 内存中位置 特点
NSGlobalBlock data段 没有访问auto变量,跟全局变量在一块,由系统管理
NSMallocBlock 需要手动释放,NSStackBlock 调用copy生成
NSStackBlock 访问了auto变量, 系统管理释放,超过作用域就释放

block-copy 操作

在ARC环境下,系统默认会对block进行copy操作的几种情况:
1.block作为函数的返回值的时候。
2.将block赋值给__strong指针时。
3.block作为cocoa API中方法名含有usingBlock的方法参数时。
4.block作为GCD API的方法参数时。
copy内部原理:当block从栈copy到堆上之后,如果存在__block、__weak、__strong修饰的对象,在__main_block_desc_0函数内部会增加copy跟dispose函数,copy函数内部会根据修饰类型对对象进行强引用还是弱引用,当block释放之后会进行dispose函数,release掉修饰对象的引用,如果都没有引用对象,将对象释放

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
block-copy操作

block 对象类型的auto变量捕获

1.如果block在栈上,将不会对auto变量产生强引用。
2.如果block被copy到堆上,会调用block内部的copy函数,copy内部函数会调用_Block_object_assign 函数,_Block_object_assign函数会根据auto变量的修饰符(__strong, __weak,__unsafe_unretaineaod)做出相应的操作,类似于tain(形成强引用,弱引用)


对象类型的auto变量

block引用对象类型的auto变量的时候,ARC会对当前对象进行内存管理操作,如果用__weak修饰的对象,不会增加其引用计数,出了作用域对象就会被释放,当用__strong修饰对象,会增加其引用计数,block执行之后会进行一次release操作。

__block 详解

我们知道 __block的修饰变量之后是就可以修改其值了,但是原理是什么呢?我们先看下代码

typedef void(^JWBlock)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int age = 10;
        JWBlock block = ^{
            age = 20;
            NSLog(@"ageage---------%d",age);
        };
        block();
        
    }
    return 0;
}

我们转换成 C++代码之后(xcrun -sdk -iphoneos clang -arch arm64 -rewrite-objc main.m)


block修改值过程

__block修饰对象类型

总结:__block修饰的auto变量,编译器会包装成一个__Block_byref_age_0(根据变量名可变)的对象类型的结构体,将指向自己的指针传递给__forwarding指针(这样做的目的是为了当多个block都使用__block修饰的变量的时候,能够始终指向堆中的变量),__Block_byref_age_0结构体内部存在的变量age才是真正__block修饰的变量,通过__Block_byref_age_0 -->__forwarding-->age改变变量的值。如果变量是NSObject对象,还会处理内存管理的问题,如上图,对象类型会生成__Block_byref_id_object_copy 跟__Block_byref_id_object_dispose这两个函数,这两个函数会对当前对象进行内存内存管理工作,下面会细讲这两个函数的作用

block循环引用问题

Person * person = [[Person alloc]init];
        person.block = ^{
            NSLog(@"%d",person.age);
        };

原因:block内部用到了外部的auto对象,block内部实现会对person进行强引用,person的block成员变量也会对block进行强引用,当person超出作用域之后,被回收,但是此时block强引用着Person,Person强引用着block 导致无法释放,造成循环引用,内存泄漏。

循环引用问题

一般我们希望block跟person的周期是一致的,所以最好将block内部引用person的指针换成__weak弱引用是最好的。这样就不会造成互相引用,导致内存无法释放

      Person * person = [[Person alloc]init];
     __weak Person * weakPerson = person;
//__weak typeof(person) weakPerson = person;
//typeof作用是保持person 跟weakPerson是相同类型的。
//也可以用__unsafe_unretained 来修饰
        person.block = ^{
            NSLog(@"%d",weakPerson.age);
        };

区别:__weak :当指向的指针没有强指针指向的时候,会将当前对象置为nil,__unsafe_unretained:当指向的指针没有强指针指向的时候,会将当前对象内存地址不变,容易造成野指针,访问错误的情况,所以不常用。
__block : 也可以解决循环引用的问题,但是使用__block时候必须执行block,并且在block内部将对象置为nil。

面试题

  • block本质是什么?
    封装了函数调用以及调用环境的OC对象
  • __block的作用是什么?
    1.如果__block在栈上,将不会对指向的对象产生强引用。
    2.如果__block被copy到堆上,会调用block内部的copy函数,copy内部函数会调用_Block_object_assign 函数,_Block_object_assign函数会根据auto变量的修饰符(__strong, __weak,__unsafe_unretaineaod)做出相应的操作,类似于retain(形成强引用,弱引用)(这里只是针对ARC时会retain,在MRC下不会进行retain操作
    3.如果变量从堆中移除,会调用block内部的dispose函数,dispose内部会调用_Block_object_dispose函数会自动释放其指向的函数
  • block使用修饰词为什么用copy,注意的细节
    block如果没有进行copy操作,就不会在堆上,无法控制block的生命周期,违背了block得初衷。
    应避免循环引用的问题
  • block在修改NSMutableArray的时候,需要增加__block么?
    不需要,修改可变数组内容,只是对其内容的操作,并没有对指针方面的修改,是对数组的使用并没有重新赋值操作。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容