iOS进阶之Block的本质及原理

iOS进阶之Block的本质及原理

前言

相信稍微有点开发经验的开发者,应该都对block有一定的了解。刚开始使用block的时候,可能觉得很方便。知道怎么去用,但是知其然知其所以然的态度来分析一下block的原理和内部结构是什么。

blcok是什么?

  • block本质上也是一个OC对象,它内部也有个isa指针
  • block是封装了函数调用以及函数调用环境的OC对象
  • block是封装函数及其上下文的OC对象

block内部构成(以局部变量为例子来了解整个执行流程)

一、通过main.m生成对应的main.cpp文件

  • 1、创建一个macOS的Command Line Tool项目,在项目里写上block代码如下:
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int age = 10;
        void(^block)(int, int) = ^(int a, int b) {
            
            NSLog(@"this is a block! -- %d", age);
            NSLog(@"this is a block!");
            NSLog(@"this is a block!");
            NSLog(@"this is a block!");
        };
        age = 20;
        block(10,10);
    }
    return 0;
}

  • 2、然后在终端进到main.m所在的文件目录下,指向下面的指令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

然后在main.m所在的文件夹下对象oc的main文件编译生成了对应的c++的main.cpp文件,在main.cpp中可以查看对应的。如下图:

main.cpp.png

二、查看block内部结构

1、编译后的main方法内代码如下:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int age = 10;
        void(*block)(int, int) = ((void (*)(int, int))
        &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age)); 
        //block(10,10)的的内部结构代码
        ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
    }
    return 0;
}

2、从以上c++代码中block的声明和定义分别与oc代码中相对应显示。将c++中block的声明和调用分别取出来查看其内部实现。定义block变量对应的代码:

 void(*block)(int, int) = ((void (*)(int, int))
 &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
 
//简化后的代码
 void (*block)(int, int) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age);
 

上述定义代码中,可以发现所有带_main_block,__main表示是main.cpp文件,_block代表block的变量名字就是block.
block定义中调用了__main_block_impl_0函数,并且将__main_block_impl_0函数的地址赋值给了block。

所以我先来看看__main_block_impl_0函数的内部结构。在main.cpp中查找__main_block_impl_0函数代码如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    //在函数栈上声明,则为_NSConcreteStackBlock
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

从上面的代码中可以看出:

  1. 声明了struct __block_impl impl结构体;

  2. 声明了struct __main_block_desc_0* Desc结构体;

  3. 声明int age;

  4. __main_block_imp_0结构体内,声明了一个同名构造函数__main_block_imp_0,构造函数中对一些变量进行了赋值最终会返回一个结构体。最终将一个__main_block_imp_0结构体的地址赋值给了block变量

  5. _main_block_impl_0结构体内可以发现__main_block_impl_0构造函数中传入了四个参数:

    1. (void *)__main_block_func_0
    2. &__main_block_desc_0_DATA
    3. age(age(_age)表示传入的_age参数会自动赋值给age成员,相当于age = _age)
    4. int flags=0(其中flags有默认值,也就说flags参数在调用的时候可以省略不传。)
一、先查看一下__block_impl 和 __main_block_desc_0的内部结构,代码如下:
struct __block_impl {
  void *isa; //指明对象的Class
  int Flags;
  int Reserved;
  void *FuncPtr;
};

//block描述信息
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_impl结构体,即为Block的结构体,可理解为Block的类结构。
  • _main表示的是main.cpp的文件名,_block表示定义Block的变量名字
  • block也有一个isa指针,所以block是一个OC对象
  • FuncPtr:指向调用函数的地址
  • __main_block_desc_0 :block描述信息
  • Block_size:block的大小
二、声明的int age

block是封装函数及其上下文的OC对象,block可以根据上下文环,因为block内部使用age,所以在结构体也声明int age,并且把赋值__main_bloc_impl_0里面的age参数值赋值10给age。

三、__main_block_imp_0中的(void *)__main_block_func_0、&__main_block_desc_0_DATA、age分别代表什么。

一、首页来查看(void *)__main_block_func_0的内部结构

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {

    int age = __cself->age; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_f3f131_mi_0, age);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_f3f131_mi_1);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_f3f131_mi_2);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_f3f131_mi_3);

}

从上边__main_block_func_0函数代码表明:

  1. 首先取出block中age的值,紧接着可以看到四个熟悉的NSLog,可以发现这段代码恰恰是我们在block块中写下的代码.
  2. 表明__main_block_func_0函数中其实存储着我们block中写下的代码
    4._cself相当于Objective-C中的self,前面不是__main_block_impl_0结构体函数把传进来的值传进来,int age = __cself->age; // bound by copy表明在函数内声明一个int age局部变量。所以在block后定义后改变值的话,block内部函数age值是不变的。
  3. 我们写在block块中的代码封装成__main_block_func_0函数,并将__main_block_func_0函数的地址传入了__main_block_impl_0的构造函数中保存在结构体内。

二、其次我在看看struct __main_block_desc_0 *Desc内部结构,&__main_block_desc_0_DATA赋值给了Desc

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

从上面的代码中可以表明:

  • __main_block_desc_0中存储着两个参数:
    • reserved赋值为0
    • Block_size存储__main_block_impl_0的占用空间大小
    • 将__main_block_desc_0结构体的地址传入__main_block_func_0中赋值给Desc。

三、最后还有age参数

  • age也就是我们定义的局部变量
  • 因为在block块中使用到age局部变量,所以在block声明的时候这里才会将age作为参数传入,也就说block会捕获age
  • 如果没有在block中使用age,这里将只会传入(void *)__main_block_func_0,&__main_block_desc_0_DATA两个参数。

这里可以根据源码思考一下为什么当我们在定义block之后修改局部变量age的值,在block调用的时候无法生效?

  • block在定义的之后已经将age的值传入,存储在__main_block_imp_0结构体中并在调用的时候将age从block中取出来使用。
  • 在block定义之后对局部变量进行改变是无法被block捕获的。

四、通过上面对__main_block_impl_0结构体构造函数三个参数在把上面的代码写下强调一下:

 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
  • _block_impl结构体中isa指针存储着&_NSConcreteStackBlock地址,可以暂时理解为其类对象地址,block就是_NSConcreteStackBlock类型的。

  • block代码块中的代码被封装成__main_block_func_0函数,FuncPtr则存储着__main_block_func_0函数的地址。

  • Desc指向__main_block_desc_0结构体对象,其中存储__main_block_impl_0结构体所占用的内存。

三、block()执行内部代码

//block(10,10)执行的内部代码  
((void (*)(__block_impl *, int, int))
((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);

通过查看block执行内部代码表明:

  • 发现调用block是通过block找到FunPtr直接调用
  • 通过上面分析我们知道block指向的是__main_block_impl_0类型结构体
  • 我们发现__main_block_impl_0结构体中并不直接就可以找到FunPtr,而FunPtr是存储在__block_impl中的

为什么block可以直接调用__block_impl中的FunPtr呢?

  • 通过上边的执行block(10,10)的内部代码,这块(__block_impl *)block代码表明block强制转化为__block_impl类型的
  • 因为__block_impl是__main_block_impl_0结构体的第一个成员,相当于将__block_impl结构体的成员直接拿出来放在__main_block_impl_0中
  • 那么也就说明__block_impl的内存地址就是__main_block_impl_0结构体的内存地址开头。所以可以转化成功。并找到FunPtr成员。

通过以上面的叙述也可以表明:

  • FunPtr中存储着通过代码块封装的函数地址,那么调用此函数,也就是会执行代码块中的代码。
  • 回头查看__main_block_func_0函数,可以发现第一个参数就是__main_block_impl_0类型的指针。也就是说将block传入__main_block_func_0函数中,便于从中取出block捕获的值。

四、验证block的本质是__main_block_impl_0结构体类型

通过模拟一下block的内部代码:

#import <Foundation/Foundation.h>

struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
};

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        void (^block)(int, int) =  ^(int a , int b){
            NSLog(@"this is a block! -- %d", age);
            NSLog(@"this is a block!");
            NSLog(@"this is a block!");
            NSLog(@"this is a block!");
        };
        // 将底层的结构体强制转化为我们自己写的结构体,通过我们自定义的结构体探寻block底层结构体
        struct __main_block_impl_0 *blockStruct = (__bridge struct __main_block_impl_0 *)block;
        block(10, 10);
    }
    return 0;
}

在block(10, 10)和NSLog(@"this is a block! -- %d", age)打个断点,查看一下blockStruct内部的堆栈信息。如下图:

blockStruct内部堆栈信息.png

表明FunPtr函数地址是0x100000eb0

在看一下block{}里实现的代码看一下地址和FunPtr地址是都一样,在 NSLog(@"this is a block! -- %d", age);断点处时候,在xcode->debug->Debug Workflow->always show Disassembly,会显示堆栈信息中的函数调用地址。

block内部的函数地址.png

如上图说明两个地址是一样的,可以说明block的本质就是_main_block_impl_0

五、验证一下block是不是对象

void (^block1)(int, int) =  ^(int a , int b){
    NSLog(@"this is a block! -- %d", age);
    NSLog(@"this is a block!");
    NSLog(@"this is a block!");
    NSLog(@"this is a block!");
};

NSLog(@"%@",[block1 class]);
NSLog(@"%@",[[block1 class] superclass]);
NSLog(@"%@",[[[block1 class] superclass] superclass]);
NSLog(@"%@",[[[[block1 class] superclass] superclass] superclass]);
NSLog(@"%@",[[[[[block1 class] superclass] superclass] superclass] superclass]);
  

打印结果如下:

 __NSGlobalBlock__
 __NSGlobalBlock
 NSBlock
 NSObject
 (null)
 

从打印结果可以表明:

  • 输出了block1的类型,也证实了block是对象,最终继承NSObject
  • block的类型是NSGlobalBlock(下面会讲)

block的三种类型基本概念

根据Block对象创建时所处数据区不同而进行区别:

  • _NSConcreteStackBlock:在栈上创建的Block对象
  • _NSConcreteMallocBlock:在堆上创建的Block对象
  • _NSConcreteGlobalBlock:全局数据区的Block对象

如下图:

block内存分配.png

对着三种类型block进行copy操作后的结果?

  • __NSGlobalBlock __ 调用copy操作后,什么也不做
  • __NSMallocBlock __ 调用copy操作后,复制效果是:引用计数增加;副本存储位置是堆
  • __NSStackBlock __ 调用copy操作后,复制效果是:从栈复制到堆;副本存储位置是堆

一、NSGlobalBlock

就是全局数据区的Block对象,那么什么时候block的类型是_NSConcreteGlobalBlock类型?

主要还是看block捕获变量的类型来确定的,以下几种情况block类型是_NSConcreteGlobalBlock:

  • 不捕获全局变量(包括全局静态变量)
  • 没有捕获变量或者捕获的只有局部静态变量
  • 以指针形式截获局部静态变量

下面就通过代码来验证一下:

没有捕获变量的block:

void (^myBlock)(int, int) =  ^(int a , int b){
           
    NSLog(@"没有捕获变量");   
};

NSLog(@"myBlockType:%@",[myBlock class]);

打印结果如下:

myBlockType:__NSGlobalBlock__

表明没有捕获变量的情况是NSGlobalBlock全局数据区block

int global_var = 50; //全局变量
static int global_staic_var = 20; //全局静态变量
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        static int staic_var = 10; //局部静态变量
        void (^myBlock)(int, int) =  ^(int a , int b){
            //只捕获局部静态变量
            NSLog(@"staic_var:%d",staic_var);
        };
        NSLog(@"myBlockType:%@",[myBlock class]);
        myBlock(10, 10);
    }
}

通过运行打印结果如下:

myBlockType:__NSGlobalBlock__
staic_var:10

下面看一下捕获全局变量和局部变量编译成的cpp文件代码:

int global_var = 50; //全局变量
static int global_staic_var = 20; //全局静态变量
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        static int staic_var = 10; //局部静态变量
        void (^myBlock)(int, int) =  ^(int a , int b){
            //只捕获局部静态变量
            NSLog(@"staic_var:%d",staic_var);
            NSLog(@"global_var:%d", global_var);
            NSLog(@"global_staic_var:%d", global_staic_var);
        };
        NSLog(@"myBlockType:%@",[myBlock class]);
        myBlock(10, 10);
    }
}

通过指令转换成cpp文件看一下:

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

打开main.cpp文件:

int global_var = 50;
static int global_staic_var = 20;
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *staic_var;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_staic_var, int flags=0) : staic_var(_staic_var) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//发现全局静态变量、全局变量直接用
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
    
    //赋值指针
    int *staic_var = __cself->staic_var; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_5050a8_mi_0,global_var);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_5050a8_mi_1,global_staic_var);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_5050a8_mi_2,(*staic_var));

}

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)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        static int staic_var = 10;

        void (*myBlock)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &staic_var));

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_5050a8_mi_3,((Class (*)(id, SEL))(void *)objc_msgSend)((id)myBlock, sel_registerName("class")));
        ((void (*)(__block_impl *, int, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 10, 10);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

通过声明了全局变量、全局静态变量、局部静态变量三种变量,通过上代码表明:

  • 发现可以捕获的只有&staic_var并且以指针形式截获局部静态变量。
  • 全局变量、全局静态变量直接用,所以在声明block后修改全局变量、全局静态变量是可以的
  • __main_block_func_0在函数方法内,赋值指针表明,所以在声明block后,修改staic_var局部静态变量的值是可以的。

NSMallocBlock

在ARC情况下,编译器会根据情况自动将栈上的block复制到堆上,那么几种情况的Block的类型进行copy?

  1. 匿名函数返回赋值给block变量
  2. 将block赋值给__strong指针时
  3. block作为Cocoa API中方法名含有usingBlock的方法参数时
  4. block作为GCD API的方法参数时

大多数情况都是block作为函数返回值时进行copy操作。
怎么判定是block类型是NSMallocBlock?
需要两个条件:

  • 对匿名的block进行copy(匿名函数block赋给block变量是一种情况)
  • 捕获到成员变量或者是__block声明的局部成员变量至少一种

下面就通过代码来表示一下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        int var = 30; //局部变量
        __block int block_var = 40; //加block局部变量
        /*
            捕获局部变量、block局部变量至少一种
            匿名block赋给myBlock
        */
        void (^myBlock)(int, int) = ^(int a , int b) {
            NSLog(@"var:%d",var);
            NSLog(@"block_var:%d",block_var);
        };
        var = 31;
        block_var = 41;
        NSLog(@"blockType:%@",[myBlock class]);
        return 0;
   }
    return 0;
}

打印结果如下:

blockType:__NSMallocBlock__
var:30
block_var:41

打印结果表明:

  • block的类型是NSMallocBlock类型
  • 成员变量在block声明后去改31,执行block发现值还是30.
  • __block在block声明后去改41,执行block发现值变成41.

那么为什么声明成员变量后改变不了,__block声明的成员变量却可以?

下面我们通过底层转换成c++代码来看一下为啥,因为上面已经写了block底层的整体流程:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

    int var = 30;
    __attribute__((__blocks__(byref))) __Block_byref_block_var_0 block_var = 
    {(void*)0,(__Block_byref_block_var_0 *)&block_var, 0, 
    sizeof(__Block_byref_block_var_0), 40};
    
    void (*myBlock)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)
    __main_block_func_0, &__main_block_desc_0_DATA, var, 
    (__Block_byref_block_var_0 *)&block_var, 570425344));
    
    (block_var.__forwarding->block_var)++;
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_b19769_mi_5,
    ((Class (*)(id, SEL))(void *)objc_msgSend)((id)myBlock, sel_registerName("class")));
    
    ((void (*)(__block_impl *, int, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 10, 10);
    }
    return 0;
}

我们声明的成员变量没发生变化,声明__block变量变成了

__attribute__((__blocks__(byref))) __Block_byref_block_var_0 block_var = 
{(void*)0,(__Block_byref_block_var_0 *)&block_var, 0, 
sizeof(__Block_byref_block_var_0), 40};

int类型变成__Block_byref_block_var_0类型。__Block_byref_block_var_0是啥?下面慢慢分析,看一下void (^myBlock)(int, int) = ^(int a , int b){}底层c++代码:

void (*myBlock)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)
__main_block_func_0, &__main_block_desc_0_DATA, var, 
(__Block_byref_block_var_0 *)&block_var, 570425344));

发现捕获的成员变量的值,捕获的__block变量类型变成了&block_var指针。
最大的变化就是block_var变量不再是int类型了,block_var变成了一个指向__Block_byref_block_var_0结构体的指针,__Block_byref_block_var_0结构如下:

struct __Block_byref_block_var_0 {
  void *__isa;
__Block_byref_block_var_0 *__forwarding;
 int __flags;
 int __size;
 int block_var;
};

结构里的结构可以看出:

  • 保存int block_var变量
  • 有一个指向__Block_byref_count_0实例的指针__forwarding

再看一下block执行函数:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int var;
  __Block_byref_block_var_0 *block_var; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_staic_var, int _var, __Block_byref_block_var_0 *_block_var, int flags=0) : staic_var(_staic_var), var(_var), block_var(_block_var->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {

    __Block_byref_block_var_0 *block_var = __cself->block_var; // bound by ref
    int var = __cself->var; // bound by copy
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_b19769_mi_3,
    var);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_b19769_mi_4,
    (block_var->__forwarding->block_var));

}

  • 发现在block执行函数里重新声明的int var把__cself(相当于__main_block_impl_0)里的var值赋值给它

  • 发现在block执行函数里重新声明的__Block_byref_block_var_0 *block_var,并把__cself的block_var地址给了它

  • 打印block_var的值发现变成了block_var->__forwarding->block_var说明了结构体内__forwarding指针指向还是自己。

为什么要通过__forwarding指针完成对count变量的读写修改?

这样就保证无论是在栈上还是在堆上,都能通过都__forwarding指针找到在堆上创建的block_var这个__main_block_func_0结构体,通过结构内的__forwarding指针
以完成对block_var(第一个block_var是__Block_byref_block_var_0对象,第二个block_var是int类型变量)的访问和修改。

通过下面的图可以表明:

block_var修改值示意图.png

下面通过代码来验证一下:

__block int block_var = 10;
NSLog(@"block捕获前地址:%p",&block_var);
        
void (^myblock)(void) =  ^{
          
    NSLog(@"block_var:%d",block_var);
};
        
NSLog(@"block捕获后地址:%p",&block_var);
block_var = 20;
myblock();

打印的结果表明捕获前后的地址是变化的并且值改变了。

block捕获前地址:0x7ffeefbff4e8
block捕获后地址:0x102968608
block_var:20

通过以上可以表明:

  • __block声明的变量就变成__Block_byref_block_var_0结构体对象
  • 在捕获前在栈上,捕获后相当于把__block 变量copy到堆上
  • 捕获后,copy到堆的结构体和捕获前栈的结构体都包含__forwarding指针都指向堆结构体__block变量的地址

NSStackBlock

栈上的block随时会被销毁,受系统控制。怎么声明NSStackBlock类型?

  • 匿名声明的block
  • 捕获局部变量或者__block局部变量至少一种
  • 没有进行copy操作
int var = 30; //局部变量
__block int block_var = 40; //加block局部变量

//没有捕获局部变量,所以是NSGlobalBlock类型
NSLog(@"Global Block:%@", [^{NSLog(@"Global Block");} class]);

//因为对其copy,说以是NSMallocBlock类型
NSLog(@"var Copy Block:%@", [[^{NSLog(@"Copy Block:%d",var);} copy] class]);
  
//虽然调用了局部变量,但是对其copy所以依然还是NSMallocBlock类型  
NSLog(@"block_var Copy Block:%@", [[^{NSLog(@"Copy Block:%d",block_var);} copy] class]);

//没有copy,并且只捕获局部变量,所以N是SStackBlock
NSLog(@"var Stack Block:%@", [^{NSLog(@"Stack Block:%d",var);} class]);
 
//没有copy,并且只捕获局部变量,所以N是SStackBlock       
NSLog(@"block_var Stack Block:%@", [^{NSLog(@"Copy Block:%d",block_var);} class]);

打印结果:

Global Block:__NSGlobalBlock__
var Copy Block:__NSMallocBlock__
block_var Copy Block:__NSMallocBlock__
var Stack Block:__NSStackBlock__
block_var Stack Block:__NSStackBlock__

总结

通过这边文章,可以总结一下:

  • 全局变量、全局静态变量不被block捕获,局部变量、局部静态变量被block捕获
  • 不被block捕获或者只捕获局部静态变量的block类型是NSGlobalBlock
  • 局部变量在block内传的是值,局部静态变量传的指针,全局(静态)变量都是直接用。
  • block是栈类型,对其进行copy操作,block就变成了NSMallocBlock
  • __block声明的变量就变成__Block_byref_block_var_0结构体对象,在捕获前是放在存在栈上的。
    • 栈上__block的__forwarding指向本身
    • 栈上__block复制到堆上后,栈上block的__forwarding指向堆上的block,堆上block的__forwarding指向本身
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容