OC-Block

Block

一个最简单的block,将源码编译后如下

// 基本使用 block
void (^block)() = ^{
    NSLog(@"block - 调用");
};
    
// 调用
block();

编译成C++后,其具体定义的类型如下,命令如下: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

struct __block_impl {
  void *isa;        // 其 isa 并非指向 Class,表明其为匿名对象
  int Flags;        // 系统传值默认为0
  int Reserved;     // 系统传值默认为0
  void *FuncPtr;    // 内部保存一个回调函数地址,可以在外界回调
};

// block 结构体的
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// block 调用时候的具体函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_3l_tslpwwd93px60pg28clgyfvh0000gn_T_main_654937_mii_3);
        }

// block 描述信息 __main_block_desc_0_DATA
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

重写为 C++ 代码后。block 的定义 & 调用

void (*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

// 移除类型转换如下
void (*block)() = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);

(block->FuncPtr)(block); // 找到block本身保存的 funcPtr 直接调用
15934170754263.jpg

Block 捕获变量

为了保证 block 能正常的访问外部的变量,其有一个变量捕获的机制

15934190192032.jpg

Block 的类型

Block 根据其存储域,分为三种类型

15934223328514.jpg

没有访问 Auto 变量 -> global 类型 -> 就相当于一个函数,全局只有一份
访问了 Auto 变量 -> stack 类型 -> 实际就是一个局部变量,出了其变量作用域直接就被销毁了
stack 类型调用 copy -> malloc 类型

block 的 copy

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,如下

1. 将block作为返回值返回时
2. 将block赋值给 __strong 指针时候,会自动copy,block的声明周期就是 strong的生命周期
3. block 作为 Cocoa Api 中方法,方法名中含有 usingBlock 的方法参数时
4. block 作为 GCD API 的方法参数时

MRC 下block属性的建议写法,直接拷贝到堆
@property (copy, nonatomtic) void (^block)(void);

ARC 下block属性的建议写法
@property (strong, nonatomtic) void (^block)(void);
@property (copy, nonatomtic) void (^block)(void);
  • Block 作为函数返回值的时候,会自动进行 copy 操作,复制到堆空间
  • 将 Block 赋值给强指针的时候也会自动进行 copy 操作

Block 捕获对象类型的 auto 变量

当 block 内部访问了对象类型的 auto 变量时

1. 如果block 在栈上,将不会对auto变量产生强引用
2. 如果block被拷贝到堆上
    1. 会调用block内部的copy函数
    2. copy 函数内部会调用_Block_object_assign函数
    3. _Block_object_assign函数会根据auto变量的修饰符(__strong,__weak,__unsage_unretained)做出响应的操作,类似retain(简单来说常用的 weakself 就是防止强引用)

3. 如果 block 从堆上移除
    1. 会调用block内部的 dispose 函数
    2. dispose 函数内部会调用_Block_object_dispose 函数
    3. _Block_object_dispose 函数会自动释放引用的 auto 变量,类似release 

Block 修改外界局部变量的值

通常情况下,block 无法修改外界变量的值,原因是 block 只是捕获了 auto 变量,实际在内部函数调用时候是一个新函数,无法访问外部变量。 如果是 static 变量或者全局变量就可以正常修改外部变量值,因为它们的访问方式不一样.

要实现修改外部的 auto 变量,可以增加 __block 修饰符

__block int b = 10;
void (^block)() = ^{
    b = 20;
};

可以看到,增加 __block 后代码经过重写为 C++

// block 源码如下:

struct __Block_byref_b_0 {
  void *__isa;
__Block_byref_b_0 *__forwarding;
 int __flags;
 int __size;
 int b;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_b_0 *b; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_b_0 *_b, int flags=0) : b(_b->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_b_0 *b = __cself->b; // bound by ref

            (b->__forwarding->b) = 20;
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}

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};

// 声明和调用如下:
__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 10};
void (*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_b_0 *)&b, 570425344));

((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

// 移除类型转换,简化如下:
__Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 10};
block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &b, 570425344));
(block->FuncPtr)(block);

可以发现block结构体 __main_block_impl_0 内多了一个__Block_byref_b_0 *b; 指针,外界变量 b 就相当于被封装成了一个对象,其指针被block引用,真正修改值操作是在该 b 对象内部修改的。

对象类型的 auto 变量,__block 变量

1. 当block 在栈上时候,对它们都不会产生强引用
2. 当block 被拷贝到堆上时候,会通过 copy 函数来处理它们,计数加1
3. 当block 被移除的时候,会通过 dispose 函数来移除它们,计数减1

被 __block 修饰的对象类型

1. 当__block 变量在栈上时候,不会对指向的对象产生强引用
2. 当__block 变量被 copy 到堆上时候
    1. 会调用 __block 变量内部的 copy 函数
    2. copy 函数内部会调用 _Block_object_assign 函数
    3. _Block_object_assign 函数会根据指向类型的修饰符(__strong,__weak,__unsage_unretained)做响应的持有操作,// 这里只有 ARC 时候会retain持有该对象,MRC时候不会持有该对象
3. 如果__block 变量从堆上移除
    1. 会调用 __block 变量内部的 dispose 函数
    2. dispose 函数内部会调用 _Block_object_dispose 函数
    3. _Block_object_dispose 函数会自动释放指向的对象

可以看出:Block 捕获变量的时候无论是否是 __block 变量其操作逻辑一样
__block 变量的作用为修改 Block持有其捕获变量的结构,如图

15935849845676.jpg

__block 变量的__forwording指针

Block 本身是可能存在堆和栈上的。 __block 变量也会因为Block从栈拷贝到堆上而赋值到堆上,所以它本身也是可能存在栈上和堆上的。

__block int b = 10;
void (^block)() = ^{
    b++; // 变量b为复制到堆上__block变量结构体的实例
};
b++;    // 变量b为复制到堆上之前,栈上__block 变量结构体的示例

// 以上两个地方的使用都可以转化为
(b.__forwording->b)++; // 通过__forwording指向无论是栈/堆上的结构体实例。

如图:

831593572769_.pic.jpg

Block 循环引用处理

由 Block 捕获外部 auto 变量的原理来看,如果 auto 变量持有 Block 且被 Block 捕获就会发生循环引用。处理方式如下:

ARC下:

1. __weak 修饰符修饰 auto 变量
2. __unsafe_unretained 修饰符修饰 auto 变量(其为弱引用,不安全,不会在变量被销毁时候被设置为nil)
3. __block 修饰,这样会修改 Block 持有该对象的数据结构,但是必须调用Block,并且在函数调用内部给变量手动置空。(不安全,容易发生泄漏)

MRC 下:

1. __block 修饰,在MRC下Block不会retain被__block修饰的对象
2. __unsafe_unretained 修饰符修饰

面试题------------

Block 原理是怎么样的,本质是什么?

Block 本质是一个封装了函数调用以及调用环境的 OC 对象,
原理如上面....

__block 的作用是什么,有什么注意点

作用:是的Block可以修改其捕获的外部变量
注意点: 内存管理,注意循环引用。。。在MRC下不会retain被捕获的对象,这点和ARC不同

Block 修饰词为什么是Copy,使用Block有哪些需要注意?

block 如果不 copy,其作用域在栈(数据区)上,为了给Block保活,在使用的时候不会被释放掉。

注意:内存管理,不要发生循环引用

block 在修改 NSMutableArray时候,是否需要加 __block 修饰符

如果是修改其实例指针的值,需要。如: array = nil;

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