美文网首页
block学习笔记

block学习笔记

作者: 黄河hg | 来源:发表于2019-03-12 19:47 被阅读0次

    原文发布在个人博客


    • clang工具

    • block分类

    • block 结构

    • block调用

    • block类型以及ARC对block的影响

    • 外部变量对block的影响


    参考文章:

    Block技巧与底层解析

    Block底层实现分析

    iOS Block底层探索

    Block-ABI-Apple


    clang工具

    clang结构化编译器前端,简单理解为可以编译llvm架构的代码工具

    Clang 对源程序进行词法分析和语义分析,并将分析结果转换为 Abstract Syntax Tree ( 抽象语法树 ) ,最后使用 LLVM 作为后端代码的生成器。

    使用方法:

    clang -rewrite-objc 文件名

    新建一个工程,执行clang -rewrite-objc main.c会生成一个main.cpp文件

    image

    block 结构

    先看一个简单的block:

    
    int main(int argc, const char * argv[]) {
    
     ^{
    
     printf("hello world");
    
     }();
    
    }
    
    

    clang 之后看一下main.cpp, 把多余代码删掉主要看以下代码:

    
    struct __block_impl {
    
     void *isa;
    
     int Flags;
    
     int Reserved;
    
     void *FuncPtr;
    
    };
    
    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;
    
     }
    
    };
    
    //定义__main_block_desc_0结构体时,同时创建了__main_block_desc_0_DATA 并给它赋值,以供在main函数中对__main_block_impl_0进行初始化
    
    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)};
    
    //对应源代码中block内部代码
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    
     printf("hello world");
    
    }
    
    //对应源代码的main函数
    
    int main(int argc, const char * argv[]) {
    
     ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))();
    
     return 0;
    
    }
    
    

    通过对比可以看到,block对应struct __main_block_impl_0 这个结构体,意思就是main函数中第0block实现,这个结构体包含

    • struct __block_impl impl

    • void *isa

      • 指向对应类型的指针
    • int Flags

      • 标志变量,在实现block的内部操作时会用到
    • int Reserved

      • 保留字段
    • void *FuncPtr

      • block执行时调用的函数的指针
    • struct __main_block_desc_0

    • size_t reserved

      • 保留字段
    • size_t Block_size

      • block大小
    • __main_block_impl_0

    • 显式的构造函数

    这里有一个纠结的地方block到底是__main_block_impl_0 还是__block_impl,目前理解为__block_impl为系统定义block的实现,__main_block_impl_0是实际block实现,相当于在block本质实现的基础上新增了特性。

    对比官方定义的block

    
    /* Revised new layout. */
    
    struct Block_descriptor {
    
     unsigned long int reserved;
    
     unsigned long int size;
    
     void (*copy)(void *dst, void *src);
    
     void (*dispose)(void *);
    
    };
    
    struct Block_layout {
    
     void *isa;
    
     int flags;
    
     int reserved; 
    
     void (*invoke)(void *, ...);
    
     struct Block_descriptor *descriptor;
    
     /* Imported variables. */
    
    };
    
    

    其中invokeFuncPtr是一样的只是clang生成的变量名不同,copydispose时捕获外部变量时使用,在下面会讨论。

    所以得出一个结论block是一个包含调用函数指针block外部上下文变量的结构体,其次内部包含isa指针,说明block也是一个对象


    block调用

    创建简单block

    
     void(^testblock)() =^{
    
     printf("hello world");
    
    };
    
    testblock();
    
    

    执行clang,其他生成代码都和上面基本一致主要看main函数

    
    struct __main_block_impl_2 {
    
     struct __block_impl impl;
    
     struct __main_block_desc_2* Desc;
    
     __main_block_impl_2(void *fp, struct __main_block_desc_2 *desc, int flags=0) {
    
     impl.isa = &_NSConcreteStackBlock;
    
     impl.Flags = flags;
    
     impl.FuncPtr = fp;
    
     Desc = desc;
    
     }
    
    };
    
    void(*testblock)() =((void (*)())&__main_block_impl_2((void *)__main_block_func_2, &__main_block_desc_2_DATA));
    
     ((void (*)(__block_impl *))((__block_impl *)testblock)->FuncPtr)((__block_impl *)testblock);
    
    

    1. 调用__main_block_impl_2显式构造函数

    2. &将1结果地址赋值给testblock

    3. 将testblock强转成__block_impl调用FuncPtr也就是__main_block_func_2

    这里有一个问题,理论上testblock的类型是__main_block_impl_2为什么可以强转成__block_impl?

    这是因为&取得是起始地址,结构体的起始地址和他第一个元素的起始地址是一致的也就是说&__main_block_impl_2&(__main_block_impl_2->__block_impl)地址是一样的,所以这里可以强制转化


    block类型以及ARCblock的影响

    block的常见类型有3种:

    • NSConcreteStackBlock(栈)

    • NSConcreteGlobalBlock(全局)

    • NSConcreteMallocBlock(堆)

    我们先简单创建两个block

    
    #import "TestBlock.h"
    
    void (^globalBlock)(void) = ^{
    
    };
    
    @implementation TestBlock
    
    - (void)testStackBlock{
    
     void(^stackBlock)(void) = ^{
    
     NSLog(@"stackBlock");
    
     };
    
     stackBlock();
    
    }
    
    @end
    
    

    对其进行编译转换后得到以下缩略代码:

    
    ...
    
    struct __globalBlock_block_impl_0 {
    
     struct __block_impl impl;
    
     struct __globalBlock_block_desc_0* Desc;
    
     __globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
    
     impl.isa = &_NSConcreteGlobalBlock;
    
     impl.Flags = flags;
    
     impl.FuncPtr = fp;
    
     Desc = desc;
    
     }
    
    };
    
    ...
    
    struct __TestBlock__testStackBlock_block_impl_0 {
    
     struct __block_impl impl;
    
     struct __TestBlock__testStackBlock_block_desc_0* Desc;
    
     __TestBlock__testStackBlock_block_impl_0(void *fp, struct __TestBlock__testStackBlock_block_desc_0 *desc, int flags=0) {
    
     impl.isa = &_NSConcreteStackBlock;
    
     impl.Flags = flags;
    
     impl.FuncPtr = fp;
    
     Desc = desc;
    
     }
    
    };
    
    ...
    
    

    观察一下上面简单block发现****impl.isa****便是对应的block类型;可以看到globalBlock属于NSConcreteGlobalBlockstackBlock属于NSConcreteStackBlock

    然而我们实际输出:

    image

    stackblock也属于globalBlockwhy???

    参照唐巧博客解释

    由于 clang 改写的具体实现方式和 LLVM 不太一样,并且这里没有开启 ARC。所以这里我们看到 isa 指向的还是_NSConcreteStackBlock。但在 LLVM 的实现中,开启 ARC 时,block 应该是 _NSConcreteGlobalBlock 类型

    详细的LLVM解析看llvm对于Block的编译规则(我没全部看完😅)。

    可以理解为由于block中的代码没有捕获任何外部变量,这个block不存在任何内存泄漏的风险,也不需要引用计数,所以类型为__NSGlobalBlock__

    所以如果block内部引用了外部变量就不会变成__NSGlobalBlock__,新增以下代码:

    
    - (void)testStackBlock {
    
    ...
    
    int a = 0;
    
     void(^blockWithVar)(void) = ^{
    
     NSLog(@"%d", a);
    
     };
    
    blockWithVar();
    
    ...
    
    }
    
    

    clang 之后:

    
    struct __TestBlock__testStackBlock_block_impl_2 {
    
     struct __block_impl impl;
    
     struct __TestBlock__testStackBlock_block_desc_2* Desc;
    
     int a;
    
     __TestBlock__testStackBlock_block_impl_2(void *fp, struct __TestBlock__testStackBlock_block_desc_2 *desc, int _a, int flags=0) : a(_a) {
    
     impl.isa = &_NSConcreteStackBlock;
    
     impl.Flags = flags;
    
     impl.FuncPtr = fp;
    
     Desc = desc;
    
     }
    
    };
    
    

    发现impl.isa指向NSConcreteStackBlock,然而我们输出发现:

    image

    创建block时是在__NSStackBlock__,而赋值给blockWithVar后,blockWithVar属于__NSMallocBlock__,这是因为ARC环境下

    在 ARC 下,block 类型通过=进行传递时,会导致调用objc_retainBlock->_Block_copy->_Block_copy_internal方法链。并导致 NSStackBlock 类型的 block 转换为 NSMallocBlock 类型。

    原文地址:https://www.jianshu.com/p/0855b68d1c1d

    NSObject.mm源代码可以看到

    
    //
    
    // The -fobjc-arc flag causes the compiler to issue calls to objc_{retain/release/autorelease/retain_block}
    
    //
    
    id objc_retainBlock(id x) {
    
    #if ARR_LOGGING
    
     objc_arr_log("objc_retain_block", x);
    
     ++CompilerGenerated.blockCopies;
    
    #endif
    
     return (id)_Block_copy(x);
    
    }
    
    

    _Block_copy是在runtime.c中实现的

    
    void *_Block_copy(const void *arg) {
    
     return _Block_copy_internal(arg, WANTS_ONE);
    
    }
    
    ...
    
    #if 0
    
    #pragma mark Copy/Release support
    
    #endif /* if 0 */
    
    /* Copy, or bump refcount, of a block. If really copying, call the copy helper if present. */
    
    static void *_Block_copy_internal(const void *arg, const int flags) {
    
     struct Block_layout *aBlock;
    
     const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
    
     //printf("_Block_copy_internal(%p, %x)\n", arg, flags);
    
     if (!arg) return NULL;
    
     // The following would be better done as a switch statement
    
     aBlock = (struct Block_layout *)arg;
    
    // 堆block引用计数加1
    
     if (aBlock->flags & BLOCK_NEEDS_FREE) {
    
     // latches on high
    
     latching_incr_int(&aBlock->flags);
    
     return aBlock;
    
     }
    
     else if (aBlock->flags & BLOCK_IS_GC) {
    
     // GC refcounting is expensive so do most refcounting here.
    
     if (wantsOne && ((latching_incr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK) == 1)) {
    
     // Tell collector to hang on this - it will bump the GC refcount version
    
     _Block_setHasRefcount(aBlock, true);
    
     }
    
     return aBlock;
    
     }
    
     //全局类型直接返回
    
     else if (aBlock->flags & BLOCK_IS_GLOBAL) {
    
     return aBlock;
    
     }
    
    ------------------------------------------------------------
    
     // Its a stack block. Make a copy.
    
    ------------------------------------------------------------
    
     if (!isGC) {
    
     struct Block_layout *result = malloc(aBlock->descriptor->size);
    
     if (!result) return (void *)0;
    
     memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
    
     // reset refcount
    
     result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
    
     // 添加需要释放的flag
    
     result->flags |= BLOCK_NEEDS_FREE | 1;
    
     result->isa = _NSConcreteMallocBlock;
    
     if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
    
     //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
    
     (*aBlock->descriptor->copy)(result, aBlock); // do fixup
    
     }
    
     return result;
    
     }
    
     else {
    
     ...
    
     }
    
    }
    
    

    关闭ARC测试下:

    image

    此时输出blockWithVar是属于__NSStackBlock__

    https://huanghehg.github.io/images/block/5.png

    我们打开ARC继续看,ARC环境下所有的block赋值给变量都会copy到堆上吗?

    image

    发现使用__weak修饰时并不会复制到堆上。所以如果使用要注意!!!

    ARC对类型为strong且捕获了外部变量的block进行了copy。并且当block****类型为strong,但是创建时没有捕获外部变量,block最终会变成NSGlobalBlock类型

    外部变量对block的影响

    捕捉局部变量的影响

    首先看下面的代码

    
    int a = 1;
    
    void(^blockWithVar)(void) = ^{
    
     NSLog(@"%d", a);
    
    };
    
    void(^blockWithNonVar)(void) = ^{
    
     NSLog(@"test");
    
    };
    
    

    转化后

    
    struct __TestBlock__testStackBlock_block_impl_0 {
    
     struct __block_impl impl;
    
     struct __TestBlock__testStackBlock_block_desc_0* Desc;
    
     int a;
    
     __TestBlock__testStackBlock_block_impl_0(void *fp, struct __TestBlock__testStackBlock_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    
     impl.isa = &_NSConcreteStackBlock;
    
     impl.Flags = flags;
    
     impl.FuncPtr = fp;
    
     Desc = desc;
    
     }
    
    };
    
    static void __TestBlock__testStackBlock_block_func_0(struct __TestBlock__testStackBlock_block_impl_0 *__cself) {
    
     int a = __cself->a; // bound by copy
    
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_91f955_mi_0, a);
    
     }
    
    ...
    
    struct __TestBlock__testStackBlock_block_impl_1 {
    
     struct __block_impl impl;
    
     struct __TestBlock__testStackBlock_block_desc_1* Desc;
    
     __TestBlock__testStackBlock_block_impl_1(void *fp, struct __TestBlock__testStackBlock_block_desc_1 *desc, int flags=0) {
    
     impl.isa = &_NSConcreteStackBlock;
    
     impl.Flags = flags;
    
     impl.FuncPtr = fp;
    
     Desc = desc;
    
     }
    
    };
    
    static void __TestBlock__testStackBlock_block_func_1(struct __TestBlock__testStackBlock_block_impl_1 *__cself) {
    
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_91f955_mi_1);
    
     }
    
    ...
    
    static void _I_TestBlock_testStackBlock(TestBlock * self, SEL _cmd) {
    
     int a = 1;
    
     void(*blockWithVar)(void) = ((void (*)())&__TestBlock__testStackBlock_block_impl_0((void *)__TestBlock__testStackBlock_block_func_0, &__TestBlock__testStackBlock_block_desc_0_DATA, a));
    
     void(*blockWithNonVar)(void) = ((void (*)())&__TestBlock__testStackBlock_block_impl_1((void *)__TestBlock__testStackBlock_block_func_1, &__TestBlock__testStackBlock_block_desc_1_DATA));
    
    }
    
    

    对比发现blockWithVar在转化后多了一个int a的变量,同时在显式构造函数里多了int _a,后面的: a(_a)相当于a = _a,是c++中的初始化列表。通过

    
    static void _I_TestBlock_testStackBlock(TestBlock * self, SEL _cmd) {
    
    int a = 1;
    
    void(*blockWithVar)(void) = ((void (*)())&__TestBlock__testStackBlock_block_impl_0((void *)__TestBlock__testStackBlock_block_func_0, &__TestBlock__testStackBlock_block_desc_0_DATA, a));
    
    }
    
    static void __TestBlock__testStackBlock_block_func_0(struct __TestBlock__testStackBlock_block_impl_0 *__cself) {
    
     int a = __cself->a; // bound by copy
    
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_91f955_mi_0, a);
    
     }
    
    

    发现a传递到block中是值传递,在调用里会生成另外一个a(int a = __cself->a;) 所以我们在block中更改a的值是不会生效的。同时编译器也会报错

    image

    看报错提示加上__block,那我们加上__block看会有什么影响:

    
    - (void)testStackBlock{
    
     __block int a = 1;
    
     void(^blockWithVar)(void) = ^{
    
     NSLog(@"pre => %d", a);
    
     a = 3;
    
     };
    
     blockWithVar();
    
     NSLog(@"res => %d", a);
    
     void(^blockWithNonVar)(void) = ^{
    
     NSLog(@"test");
    
     };
    
    }
    
    

    转化后关键代码:

    
    struct __Block_byref_a_0 {
    
     void *__isa;
    
    __Block_byref_a_0 *__forwarding;
    
     int __flags;
    
     int __size;
    
     int a;
    
    };
    
    struct __TestBlock__testStackBlock_block_impl_0 {
    
     struct __block_impl impl;
    
     struct __TestBlock__testStackBlock_block_desc_0* Desc;
    
     __Block_byref_a_0 *a; // by ref
    
     __TestBlock__testStackBlock_block_impl_0(void *fp, struct __TestBlock__testStackBlock_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    
     impl.isa = &_NSConcreteStackBlock;
    
     impl.Flags = flags;
    
     impl.FuncPtr = fp;
    
     Desc = desc;
    
     }
    
    };
    
    static void __TestBlock__testStackBlock_block_func_0(struct __TestBlock__testStackBlock_block_impl_0 *__cself) {
    
     __Block_byref_a_0 *a = __cself->a; // bound by ref
    
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_63aeec_mi_0, (a->__forwarding->a));
    
     (a->__forwarding->a) = 3;
    
     }
    
    // 辅助copy函数,下面会用到
    
    static void __TestBlock__testStackBlock_block_copy_0(struct __TestBlock__testStackBlock_block_impl_0*dst, struct __TestBlock__testStackBlock_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    // 辅助dispose函数,下面会用到
    
    static void __TestBlock__testStackBlock_block_dispose_0(struct __TestBlock__testStackBlock_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static struct __TestBlock__testStackBlock_block_desc_0 {
    
     size_t reserved;
    
     size_t Block_size;
    
     void (*copy)(struct __TestBlock__testStackBlock_block_impl_0*, struct __TestBlock__testStackBlock_block_impl_0*);
    
     void (*dispose)(struct __TestBlock__testStackBlock_block_impl_0*);
    
    } __TestBlock__testStackBlock_block_desc_0_DATA = { 0, sizeof(struct __TestBlock__testStackBlock_block_impl_0), __TestBlock__testStackBlock_block_copy_0, __TestBlock__testStackBlock_block_dispose_0};
    
    // 对应的是testStackBlock
    
    static void _I_TestBlock_testStackBlock(TestBlock * self, SEL _cmd) {
    
     __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 1};
    
     void(*blockWithVar)(void) = ((void (*)())&__TestBlock__testStackBlock_block_impl_0((void *)__TestBlock__testStackBlock_block_func_0, &__TestBlock__testStackBlock_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    
     }
    
    
    1. 先看最后的_I_TestBlock_testStackBlock,发现加上__block关键字之后a已经不是int类型而是对应__Block_byref_a_0类型

    2. 再观察__Block_byref_a_0包含:

    • void *__isa 说明是一个对象

    • __Block_byref_a_0 *__forwarding;

    • int __flags;

    • int __size;

    • int a;这里面的a对应的就是block外面赋的值

    1. 继续观察(__Block_byref_a_0 *)&a,在block编译时将(__Block_byref_a_0 *)&a传给了block,所以不再是值传递而是内存地址传递,所以在block内可以操纵a

    那么如果直接传递内存地址而不使用__block可以吗?

    将代码修改如下

    
    - (void)testStackBlock{
    
     int a = 1;
    
     int *p = &a;
    
     void(^blockWithVar)(void) = ^{
    
     NSLog(@"pre => %d", *p);
    
     *p = 3;
    
     };
    
     blockWithVar();
    
     NSLog(@"res => %d", *p);
    
    }
    
    

    运行发现可以修改,但这样很明显可以看出来如果a释放了,p就变成了野指针,如果block是作为参数或者返回值,这些类型都是跨栈的,也就是说再次调用会造成野指针错误。例如下面的代码:

    
    - (void)testStackBlock{
    
     int a = 1;
    
     int *p = &a;
    
     void(^blockWithVar)(void) = ^{
    
     NSLog(@"pre => %d", *p);
    
     *p = 3;
    
     };
    
    // blockWithVar();
    
     NSLog(@"res => %d", *p);
    
     [self.blockArray addObject:blockWithVar];
    
    }
    
    - (void)testBlock:(void(^)(void))block {
    
     block();
    
    }
    
    
    捕捉局部静态变量的影响
    
    - (void)testStackBlock{
    
     static int a = 1;
    
     void(^blockWithVar)(void) = ^{
    
     a = 3;
    
     };
    
     NSLog(@"pre a => %d", a);
    
     blockWithVar();
    
     NSLog(@"res a => %d", a);
    
    }
    
    

    转化后

    
    struct __TestBlock__testStackBlock_block_impl_0 {
    
     struct __block_impl impl;
    
     struct __TestBlock__testStackBlock_block_desc_0* Desc;
    
     int *a;
    
     __TestBlock__testStackBlock_block_impl_0(void *fp, struct __TestBlock__testStackBlock_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
    
     impl.isa = &_NSConcreteStackBlock;
    
     impl.Flags = flags;
    
     impl.FuncPtr = fp;
    
     Desc = desc;
    
     }
    
    };
    
    static void __TestBlock__testStackBlock_block_func_0(struct __TestBlock__testStackBlock_block_impl_0 *__cself) {
    
     int *a = __cself->a; // bound by copy
    
    //地址访问
    
     (*a) = 3;
    
     }
    
    static struct __TestBlock__testStackBlock_block_desc_0 {
    
     size_t reserved;
    
     size_t Block_size;
    
    } __TestBlock__testStackBlock_block_desc_0_DATA = { 0, sizeof(struct __TestBlock__testStackBlock_block_impl_0)};
    
    static void _I_TestBlock_testStackBlock(TestBlock * self, SEL _cmd) {
    
     static int a = 1;
    
     void(*blockWithVar)(void) = ((void (*)())&__TestBlock__testStackBlock_block_impl_0((void *)__TestBlock__testStackBlock_block_func_0, &__TestBlock__testStackBlock_block_desc_0_DATA, &a));
    
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_188fa3_mi_0, a);
    
     ((void (*)(__block_impl *))((__block_impl *)blockWithVar)->FuncPtr)((__block_impl *)blockWithVar);
    
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_188fa3_mi_1, a);
    
    }
    
    

    可以看到此时a是地址传递,在block内部也可以成功更改a的值,

    需要注意一点的是静态局部变量是存储在静态数据存储区域的,也就是和程序拥有一样的生命周期,也就是说在程序运行时,都能够保证block访问到一个有效的变量。但是其作用范围还是局限于定义它的函数中,所以只能在block通过静态局部变量的地址来进行访问。

    捕捉全局变量的影响
    
    int b = 3;
    
    static int c = 4;
    
    - (void)testStackBlock{
    
     void(^blockWithVar)(void) = ^{
    
     b = 5;
    
     c = 6;
    
     };
    
     NSLog(@"pre b => %d", b);
    
     NSLog(@"pre c => %d", c);
    
     blockWithVar();
    
     NSLog(@"pre b => %d", b);
    
     NSLog(@"pre c => %d", c);
    
    }
    
    

    转化后

    
    int b = 3;
    
    static int c = 4;
    
    ...
    
    static void __TestBlock__testStackBlock_block_func_0(struct __TestBlock__testStackBlock_block_impl_0 *__cself) {
    
     b = 5;
    
     c = 6;
    
     }
    
    ...
    
    static void _I_TestBlock_testStackBlock(TestBlock * self, SEL _cmd) {
    
     void(*blockWithVar)(void) = ((void (*)())&__TestBlock__testStackBlock_block_impl_0((void *)__TestBlock__testStackBlock_block_func_0, &__TestBlock__testStackBlock_block_desc_0_DATA));
    
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_d646c1_mi_0, b);
    
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_d646c1_mi_1, c);
    
     ((void (*)(__block_impl *))((__block_impl *)blockWithVar)->FuncPtr)((__block_impl *)blockWithVar);
    
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_d646c1_mi_2, b);
    
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_d646c1_mi_3, c);
    
    }
    
    

    可以看到全局变量都是直接访问变量的,是因为全局变量存储在静态数据存储区,在程序结束前不会被销毁

    实例变量
    
    @interface TestBlock ()
    
    {
    
     NSString *_str;
    
     int _a;
    
    }
    
    @end
    
    - (void)testStackBlock{
    
     void(^blockWithVar)(void) = ^{
    
     _a = 5;
    
     _str = @"test";
    
     };
    
     NSLog(@"res a => %d", _a);
    
     NSLog(@"res str => %@", _str);
    
     blockWithVar();
    
     NSLog(@"res a => %d", _a);
    
     NSLog(@"res str => %@", _str);
    
    }
    
    

    这里编译器会给我们警告,意思就是有隐式的self引用,我们转化一下

    
    struct __TestBlock__testStackBlock_block_impl_0 {
    
     struct __block_impl impl;
    
     struct __TestBlock__testStackBlock_block_desc_0* Desc;
    
     TestBlock *self;//TestBlock 类
    
     __TestBlock__testStackBlock_block_impl_0(void *fp, struct __TestBlock__testStackBlock_block_desc_0 *desc, TestBlock *_self, int flags=0) : self(_self) {
    
     impl.isa = &_NSConcreteStackBlock;
    
     impl.Flags = flags;
    
     impl.FuncPtr = fp;
    
     Desc = desc;
    
     }
    
    };
    
    static void __TestBlock__testStackBlock_block_func_0(struct __TestBlock__testStackBlock_block_impl_0 *__cself) {
    
     TestBlock *self = __cself->self; // bound by copy
    
    // self+实例变量a的偏移值
    
     (*(int *)((char *)self + OBJC_IVAR_$_TestBlock$_a)) = 5;
    
     (*(NSString **)((char *)self + OBJC_IVAR_$_TestBlock$_str)) = (NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_0453bc_mi_0;
    
     }
    
     static void __TestBlock__testStackBlock_block_copy_0(struct __TestBlock__testStackBlock_block_impl_0*dst, struct __TestBlock__testStackBlock_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static void __TestBlock__testStackBlock_block_dispose_0(struct __TestBlock__testStackBlock_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static struct __TestBlock__testStackBlock_block_desc_0 {
    
     size_t reserved;
    
     size_t Block_size;
    
     void (*copy)(struct __TestBlock__testStackBlock_block_impl_0*, struct __TestBlock__testStackBlock_block_impl_0*);
    
     void (*dispose)(struct __TestBlock__testStackBlock_block_impl_0*);
    
    } __TestBlock__testStackBlock_block_desc_0_DATA = { 0, sizeof(struct __TestBlock__testStackBlock_block_impl_0), __TestBlock__testStackBlock_block_copy_0, __TestBlock__testStackBlock_block_dispose_0};
    
    static void _I_TestBlock_testStackBlock(TestBlock * self, SEL _cmd) {
    
    //self 传进去
    
     void(*blockWithVar)(void) = ((void (*)())&__TestBlock__testStackBlock_block_impl_0((void *)__TestBlock__testStackBlock_block_func_0, &__TestBlock__testStackBlock_block_desc_0_DATA, self, 570425344));
    
     ...
    
    }
    
    

    通过上面看到block内部会生成一个TestBlock *self,它的值便是_I_TestBlock_testStackBlock中的self所以可以更改实例变量,当然这里会有一个循环引用的问题,也就是说block引用实例变量也会强引用self


    总结:

    • block本质上是一个包含调用函数指针、block外部上下文变量的结构体

    • block有根据存储位置不同分为三种类型

    • NSConcreteStackBlock(栈)NSConcreteGlobalBlock(全局)NSConcreteMallocBlock(堆)

    • ARC模式下会把不引用外部变量的block转化成NSConcreteGlobalBlock,引用外部变量的block会在赋值时转化为NSConcreteMallocBlock

    • block引用外部局部变量和静态局部变量或实例变量时会在block内部生成对应的变量。在引用全局变量时并不会生成对应变量。

    • Block会对内部的变量形成强引用,而如果同时该变量又持有这个Block,就会导致循环引用而无法释放,从而导致内存泄露。注意隐式的循环引用

    相关文章

      网友评论

          本文标题:block学习笔记

          本文链接:https://www.haomeiwen.com/subject/nwqopqtx.html