Block

1、Block

struct Block_layout {
    void *isa;//指向所属类的指针,也就是block的类型 NSStackBlock NSGlobalBlock  NSMallocBlock   
    int flags;//标志变量,在实现block的内部操作时会用到
    int reserved;//保留变量
    void (*invoke)(void *, ...);//执行时调用的函数指针,block内部的执行代码都在这个函数中
    struct Block_descriptor *descriptor;//block的详细描述,包含 copy/dispose 函数,处理block引用外部变量时使用
    /* Imported variables. */
//variables: block范围外的变量,如果block没有调用任何外部变量,该变量就不存在
};

struct Block_descriptor {
    unsigned long int reserved;//保留变量
    unsigned long int size;//block的内存大小
    void (*copy)(void *dst, void *src);// 拷贝block中被 __block 修饰的外部变量
    void (*dispose)(void *);//和 copy 方法配置应用,用来释放资源
};

2、Block语法

@property(nonatomic, copy)  void (^NormalBlock)(void);

typedef void (^NormalBlock)(void);

@property(nonatomic, copy)  NormalBlock block;
^【返回值类型】【参数列表】【表达式】
exp. ^int (int count) {return count + 1;}
注意:【返回值类型】和【参数列表】可省略

void (^blockName) (int parameter);
//void代表返回值类型,后面一次是block名,参数

3、Block有哪几种类型

NSStackBlock存储于栈区

block 内部引用外部变量,retain、release 操作无效,存储于栈区,变量作用域结束时,其被系统自动释放销毁。
MRC 环境下,[[mutableAarry addObject: blockA],(在arc中不用担心此问题,因为arc中会默认将实例化的block拷贝到堆上)在其所在作用域结束也就是函数出栈后,从mutableAarry中取到的blockA已经被回收,变成了野指针。正确的做法是先将blockA copy到堆上,然后加入数组。支持copy,copy之后生成新的NSMallocBlock类型对象。

NSGlobalBlock 存储于程序数据区

block 内部没有引用外部变量的 Block 类型都是 NSGlobalBlock 类型,存储于全局数据区,由系统管理其内存,retain、copy、release操作都无效。引用static也为globalBlock

NSMallocBlock 存储于堆区

如上例中的_block[blockA copy]操作后变量类型变为 NSMallocBlock,支持retain、release,虽然 retainCount 始终是 1,但内存管理器中仍然会增加、减少计数,当引用计数为零的时候释放(可多次retain、release 操作验证)。copy之后不会生成新的对象,只是增加了一次引用,类似retain,尽量不要对Block使用retain操作。

在ARC环境下,Block也是存在__NSStackBlock的时候的,平时见到最多的是_NSMallocBlock,是因为我们会对Block有赋值操作,所以ARC下,block 类型通过=进行传递时,会导致调用objc_retainBlock->_Block_copy->_Block_copy_internal方法链。并导致 NSStackBlock 类型的 block 转换为 NSMallocBlock 类型

4、Block的结构(Clang后的对应)

原代码

-(void)blockDemo{
    void (^block)(void) = ^{   
};
}

clang后:

struct __block_impl {  
void *isa;//指向所属类的指针,也就是block的类型
int Flags;  //标志变量,在实现block的内部操作时会用到
int Reserved;  //保留变量
void *FuncPtr;//block执行时调用的函数指针
};
//block内部实现 func0
static void __ViewController__blockDemo_block_func_0(struct __ViewController__blockDemo_block_impl_0 *__cself) {
 }

static struct __ViewController__blockDemo_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}
 __ViewController__blockDemo_block_desc_0_DATA = { 0, sizeof(struct __ViewController__blockDemo_block_impl_0)};


static void _I_ViewController_blockDemo(ViewController * self, SEL _cmd) {
    void (*block)(void) = ((void (*)())&__ViewController__blockDemo_block_impl_0((void *)__ViewController__blockDemo_block_func_0, &__ViewController__blockDemo_block_desc_0_DATA));
}

捕获变量 __block 源代码

-(void)blockDemo{
    
    __block int a = 100;
    void (^block)(void) = ^{
        a++;
    };
    
    block();
}

clang后

struct __Block_byref_a_0 {
  void *__isa; //指向所属类的指针,被初始化为 (void*)0
__Block_byref_a_0 *__forwarding;//指向对象在堆中的拷贝
 int __flags;//标志变量,在实现block的内部操作时会用到
 int __size;//对象的内存大小
 int a;//原始类型的变量
};
static void __ViewController__blockDemo_block_func_0(struct __ViewController__blockDemo_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref


        (a->__forwarding->a)++;

    }

可以看到 多了一个结构体 被__block修饰的变量被封装成了一个对象,类型为__Block_byref_a_0,然后把&a作为参数传给了block。
其中,isa、__flags 和 __size 的含义和之前类似,而 __forwarding 是用来指向对象在堆中的拷贝,runtime.c 里有源码说明:

static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
    ...
    struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
    copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
    // 堆中拷贝的forwarding指向它自己
    copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
    // 栈中的forwarding指向堆中的拷贝
    src->forwarding = copy;  // patch stack to point to heap copy
    copy->size = src->size;
    ...
}

这样做是为了保证在 block内 或 block 变量后面对变量a的访问,都是直接访问堆内的对象,而不上栈上的变量。同时,在 block 拷贝到堆内时,它所捕获的由 __block 修饰的局部基本类型也会被拷贝到堆内(拷贝的是封装后的对象),从而会有 copy 和 dispose处理函数。

5、 Block copy的过程

Block经过copy之后会在desc里生成的2个函数

  • copy函数
    调用时机 栈上的Block复制到堆时
  • dispose函数
    调用时机 堆上的Block被废弃时

Block内部访问了带有__block修饰符的对象类型的auto变量时

  • block在栈上时,并不会对__block变量产生强引用
  • blockcopy到堆时
    • 会调用block内部的copy函数
    • copy函数内部会调用_Block_object_assign函数
    • _Block_object_assign函数会根据所指向对象的修饰符(__strong, __weak, __unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retainMRC时不会retain

6、__block的作用

  • __block可以用于解决block内部无法修改auto变量值的问题
  • __block不能修饰全局变量、静态变量(static
    编译器会将__block变量包装成一个对象
  • __block修改变量:age->__forwarding->age
  • __Block_byref_age_0结构体内部地址和外部变量age是同一地址

7、__block的结构(Clang后的对应)

编译器会将 __block变量包装成一个对象

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;//age的地址
 int __flags;
 int __size;
 int age;//age 的值
};

8、__forwarding指针的作用

__forwarding, 它是结构体__Block_byref_abc_0的组成部分, 且它的类型是__Block_byref_abc_0 *

  • 栈上__block__forwarding指向本身
  • 栈上__block复制到堆上后,栈上block__forwarding指向堆上的block,堆上block__forwarding指向本身

__forwarding指针存在的意义就是,无论在任何内存位置,都可以顺利地访问同一个__block 变量。

9、Block 释放的过程

block从堆中移除时

1、会调用block内部的dispose函数
2、 dispose函数内部会调用_Block_object_dispose函数
3、_Block_object_dispose函数会自动释放引用的__block变量(release)

block从堆上移除时,都会通过dispose函数来释放它们

10、copy_dispose

11、Block循环引用

三种方式:__weak、__unsafe_unretained、__block 解决循环引用
1、__weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
2、__unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变
3、__block:必须把引用对象置位nil,并且要调用该block

12、Block捕获(多重嵌套情况)

block内部会专门新增一个成员来外面变量的值,这个操作称之为捕获

13、Block hook的几种实现

Hook Block 交换block的实现
BlockHookfishhookBlockHookDemoYSBlockHook...

  • 1、交换block的实现 aspect 原理就是在运行期间动态交换两个方法所指向的IMP指针,那么换作Block也是一样的道理。只要将invoke指针指向我们自定义的函数地址,就可以交换block的实现
  • 2、
  • 3、

14、Block为何会有Private Data

15、如果获取Block参数的个数及其类型

16、关于BLOCK_HAS_EXTENDED_LAYOUT的一些内容

17、Block 常见题

1、Block的原理是怎样的?本质是什么?
2、__block的作用是什么?有什么使用注意点?
3、Block的属性修饰词为什么是copy?使用Block有哪些使用注意?
4、Block在修改NSMutableArray,需不需要添加__block?

注:

clang 用法:clang -fobjc-arc -framework Foundation HelloWord.m -o HelloWord

  • -fobjc-arc表示编译器需要支持ARC特性
  • -framework Foundation表示引用Foundation框架
  • HelloWord.m为需要进行编译的源代码文件
  • -o HelloWord表示输出的可执行文件的文件名
  • 1、clang 生成C++
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk ViewController.m
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容