OC中的block实现原理

一、block的本质

block本质上是一个OC对象,它内部也有isa指针,这个对象封装了函数的调用地址以及函数调用的环境(函数参数、返回值、捕获的外部变量等)。我们clang以后可以看一下它的底层存储结构:

clang操作(可以在命令行运行xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xx.m将这个xx.m文件转成编译后的c/c++文件,然后在这个文件搜索__main_block_impl_0就可以找到这个block的结构体)

clang前:
int a = 20;
void (^block)(void) = ^{
  NSLog(@"a=%d",a);
}
clang后:
struct __main_block_impl_0 {
   struct __block_impl impl;
   struct __main_block_desc_0 *Desc;
   int a;
}

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
}

struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}

*可见block底层就是一个结构体__main_block_impl_0
impl->isa:就是isa指针,可见它是一个OC对象。
impl->FuncPtr:是一个函数指针,也就是底层block中要执行的代码封装成了一个函数,然后用过这个指针去指向那个函数。
Desc->Block_size block占用的内存大小。
a:捕获的外部变量a,捕获后存储在block的底层结构体中。

二、block变量获取机制

1、全局变量的获取

不管是普通全局变量还是静态全局变量,block都不会捕获,因为全局变量在哪里都可以访问,所以block内部不对其进行捕获也可以执行访问,所以外部更改全局变量的值时,block内部打印的都是最新值。

1、静态局部变量的获取

静态局部变量在被block捕获后,在block内部是以 int *b;形式存储的,也就是block其实捕获的是变量b的地址,block内部通过b的地址获取或者修改b的值,所以外部更改b的值会影响block里面捕获的b的值,block里面更改b的值也会影响block外面b的值。

1、普通局部变量的获取

普通局部变量被block捕获后在block底层结构体中是以int a;形式存储的,也就是block捕获的其实是a的值,并且block内部重新定义了一个变量来存储这个值,这个时候block里面和外面的其实是2个不同的变量,所以外面更改a的值不会影响block内部的值。

三、block的类型

block有三种类型NSGlobalBlock,NSStackBlock,NSMallocBlock.

分别介绍一下:

  • NSGlobalBlock
    如果一个block里面没有访问普通的局部变量(也就是说没有访问任何外部变量或者访问的是静态局部变量或者全局变量)。那么这个block就是NSGlobalBlock。它在内存中是存放在数据区的(也叫做全局区或者静态区,全局变量和静态变量都存在这个区域),它调用copy方法的话什么也不会发生。
    *NSStackBlock
    如果一个block里面访问了普通的局部变量,那它就是一个NSStackBlock,它在内存中存储在栈区,栈区的特点就是其释放不受开发者控制,它是由系统释放操作的,所以在调用它的时候一定要保证它没有被释放。如果对一个NSStackBlock类型的block进行copy操作,那它会被复制到堆上。
    *NSMallocBlock
    一个NSStackBlock类型的block调用了copy,那会将这个block从栈复制到堆上,堆上的这个block类型为NSMallocBlock,所以NSMallocBlock类型的block是存储在堆上的。如果对一个NSMallocBlock类型的block进行copy操作,它的引用计数会+1.

四、 block对对象型的局部变量的捕获

block对对象类型和对基本数据类型变量的捕获是不一样的,对象类型的变量涉及到强引用和弱引用的问题。

如果block是在栈上,不管捕获的对象是强指针还是弱指针,block内部都不会对这个对象产生强引用。所以我们主要看block在堆上的情况。

强引用的对象在block捕获后,在结构体上多了一个修饰关键字__strong.
弱引用的对象在block捕获后,在结构体上多了一个修饰关键字__weak.

当block被拷贝到堆上的时候调用copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会根据这些关键字进行操作。

  • 如果关键字是__strong,那block内部会堆这个对象进行一次retain操作,引用计数+1,也就是block会强引用这个对象。也正是这个原因,导致在使用block时很容易造成循环引用。
    *如果关键子是__weak或者__unsafe__unretained,那么block对这个对象是弱引用,不会造成循环引用。所以我们通常在block外面定义一个__weak或者__unsafe__unretained修饰的弱指针指向对象,然后在block内部使用这个弱指针来解决循环引用的问题。
  • block从堆上移除时,会调用内部的dispose函数,dispose函数内部调用_Block_object_dipose函数会自动给释放强引用的变量。

五、 __block修饰符的作用

我们看一下使用__block关键字修饰后,底层到底做了什么。

- (void)test1 {
    __block int age = 10;
    void (^block)(void) = ^{
        age = 20;
    };
    block();
    NSLog(@"%d",age);
}

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
};

struct __Block_byref_age_0 {
  void *__isa; // isa指针
__Block_byref_age_0 *__forwarding; // 如果这block是在堆上那么这个指针就是指向它自己,如果这个block是在栈上,那这个指针是指向它拷贝到堆上后的那个block
 int __flags;
 int __size; // 结构体大小
 int age; // 真正捕获到的age
};

我们可以看到,age用__block修饰后,在block的结构体中变成了__Block_byref_age_0* age;而__Block_byref_age_0是一个结构体,里面有个成员int age;这个才是真正捕获到的外部变量age,实际上外部的age的地址也是指向这里的,所以不管外面还是block里面修改age时,其实都是通过地址找到这里进行修改的。
所以age用__block修饰后它就不再是一个test1方法内部的局部变量了,而是一个被包装成一个对象,age在这个对象里面存储。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352