• [iOS]-Block


    参考的博客:

    iOS八股文(十六)关于Block,你在第几层?
    [iOS开发]Block

    Block的使用规范

    什么是Blocks?

    • 一句话简单概括一下:带有自动变量(局部变量)的匿名函数
      顾名思义:Block没有函数名,另外Block带有^标记,插入记号便于查找到Block
    • Block也被称作闭包、代码块。也就是说我们可以把我们想要执行的代码封装在这个代码块中,我们需要的时候直接进行调用
    • Block共享局部作用域的数据。如果实现一个方法,并且该方法定义一个块,则该块可以访问该方法的局部变量和参数(包括堆栈变量),以及函数和全局变量(包括实例变量)。这种访问是只读的,如果使用__block修饰符来声明变量,我们就可以在Block内修改其值。这些我们后面再做详解

    Block语法

    格式

    标准格式


    例子:

    ^int (int count){return count + 1;}
    
    • 1

    省略格式

    返回值类型默认void

    如果是void 我们也可以默认省略(void)

    另外如果没有参数的话也就直接相当于省略了参数列表部分
    在这里插入图片描述
    例子:

    ^{return count + 1;}
    
    • 1

    Block变量

    • Block变量类似于函数指针

    • 声明Block类型变量仅仅是将声明函数指针类型变量的"*“变为”^"(不考虑参数的情况下)

      例如:
      函数指针的声明:int result =(*funcptr)(10);int result =(*funcptr)(a);
      Block变量的声明:int (^blk)(int);int (^blk)(int a);

      block变量与c语言变量完全相同,可以作为以下用途:

      • 自动变量(局部变量)
      • 函数参数
      • 静态变量
      • 静态全局变量
      • 全局变量

    完整格式

    完整的格式就是:变量声明+定义
    在这里插入图片描述
    例子:

    int (^sumOfNumbers)(int a, int b) = ^int (int a, int b) {
    	return a + b;
    };
    
    int sum = sumOfNumbers(1,5);
    NSLog(@"sum = %d", sum);
    
    打印结果为:
    Block-Test[63351:3799010] sum = 6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    截获自动变量

    定义时的:带有自动变量值在Block中表现为“截获自动变量值”。

    int val = 10;
    void (^blk)(int a) = ^(int a){
    	printf("a = %d beforeVal = %d\n", a, val);
    };
    val = 2;
    blk(val);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    运行结果是:a = 2 beforeVal = 10
    beforeVal并不是咱们所修改后的2

    也就是说变量在代码运行到定义那一块时就被截获了,执行的时候已经不是原变量了,但是传进block的参数的值还是以调用block时的为准。

    值得注意的点: 在现在的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获,例子如下:

    	const char text[] = "hello";
        void (^blkTest)(void) = ^ {
            NSLog(@"%c\n", text[2]);
        };
    
    • 1
    • 2
    • 3
    • 4

    它会报错:
    请添加图片描述
    我们可以看到报错信息:无法引用块内具有数组类型的声明。

    然而使用指针表示这个数组就没有问题了:
    请添加图片描述
    可以注意到现在已经没有报错了,而且运行的结果是:Block-Test[65785:3914212] l

    __block说明符

    block可以截获变量,但是在块里不能修改变量的值
    修改会报错

    此时我们使用__block修饰符修饰变量,对需要在block内进行赋值的变量,使用修饰符修饰,保证可以对变量进行赋值
    例如:

       __block int val = 10;
        void (^blk)(void) = ^{
            //val = 5;
            printf("val = %d\n",val);
        };
        val = 2;
        blk();
        return 0;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    现在我们的运行结果就是:val = 2
    如果我们将里面注释的://val = 5;改为不注释,如下:

       __block int val = 10;
        void (^blk)(void) = ^{
            val = 5;
            printf("val = %d\n",val);
        };
        val = 2;
        blk();
        return 0;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    那么运行结果就会变为:val = 5

    Block的循环引用

    - (id)init {
    	self = [super init];
    	blk_ = ^{NSLog(@"self = %@", self);};
    	return self;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可以看到这是一个类的初始化方法,我们在初始化这个类的实例时就会造成循环引用,因为Block语法赋值在了成员变量blk中,因此通过Block语法生成在栈上的Block此时由栈复制到了堆,并持有这个self。
    self持有Block,Block持有self,形成了循环引用。

    解决Block循环引用的方法

    __weak__strong协作(强弱共舞)

    - (id)init {
    	self = [super init];
    	__weak typeof(self) weakSelf = self;
    	blk_ = ^{
    		__strong typeof(weakSelf) strongSelf = weakSelf;
    		NSLog(@"self = %@", strongSelf);
    	};
    	return self;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    block对象,并没有引用self,在执行block的时候strongSelf的生命周期只有在block内部,在block内部,self的引用计数+1,当执行完block,引用计数-1。既没有引起循环引用,又适当延长了self的生命周期,一举双得。

    __block

    - (id)init {
    	self = [super init];
    	__block Person *blockSelf = self;
    	blk_ = ^{
    		NSLog(@"self = %@", blockSelf);
    		blockSelf = nil;
    	};
    	return self;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    使用这种方式,同样也可以解决循环引用,但是要注意,block执行完一次,下一次执行之前记得要给blockSelf重新赋值,不然会出问题,显然维护这个是非常麻烦的,所以不推荐。

    参数传递

    - (id)init {
    	self = [super init];
    	__block Person *blockSelf = self;
    	blk_ = ^(Person * _Nonnull selfTest) {
    		NSLog(@"self = %@", selfTest);
    	};
    	blk_(self);
    	return self;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    通过block的参数进行传递,同样可以解决循环引用(由于函数参数作用域的特点),但是这样做的意义不大,因为block在执行的地方,一定是需要获取到self的,如果已经获取到self了,就可以直接对self操作了,再使用block有点多余。应用并不多,只做了解。

    Block的实现

    BLock的实现是基于指针和函数指针,Block属性是指向结构体的指针。

    我们可以将block代码通过clang重写为C++代码,看其底层实现:

    	//这是正常的block流程
        void(^myBlock)(void) = ^{
            printf("myBlock");
        };
        myBlock();
    
    • 1
    • 2
    • 3
    • 4
    • 5

    重写命令的方法(先在终端cd进对应项目文件夹):

    //x86架构写法
    xcrun -sdk iphoneos clang -rewrite-objc <OriginFile> -o <CppFile>
    //arm64架构写法
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc < OC FileName> -o <Cpp FileName>
    //注意实际写文件名的时候不要带<>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    得到Block的结构的源码定义(C++):

        void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    
    • 1
    • 2

    我们来分析一下:
    初始化定义部分block语法变成了:&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    调用block的过程变成了:(__block_impl *)myBlock)->FuncPtr

    接着我们来看一下其中涉及到的对应的部分:

    初始化Block部分

    //Block结构体
    struct __main_block_impl_0 {
      struct __block_impl impl;//impl:Block的实际函数指针,就是指向包含Block主体部分的__main_block_func_0结构体
      struct __main_block_desc_0* Desc;//Desc指针,指向包含Block附加信息的__main_block_desc_0()结构体
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {//__main_block_impl_0:Block构造函数(可以看到都是对上方两个成员变量的赋值操作)
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    所以我们可以看出,__main_block_impl_0结构体也就是Block结构体包含了三个部分:

    1. 成员变量impl
    2. 成员变量Desc指针
    3. __main_block_impl_0构造函数

    struct __block_impl结构

    包含Block实际函数指针的结构体:

    struct __block_impl {
      void *isa;//用于保存Block结构体的实例指针
      int Flags;//标志位
      int Reserved;//今后版本升级所需的区域大小
      void *FuncPtr;//函数指针
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • __block_impl包含了Block实际函数指针FuncPtrFuncPtr指针指向Block的主体部分,也就是Block对应OC代码中的^{...}的部分
    • 还包含了标志位Flags,在实现block的内部操作时可能会用到
    • 今后版本升级所需的区域大小Reserved
    • __block_impl结构体的实例指针isa

    struct __main_block_desc_0

    Block附加信息结构体:包含今后版本升级所需区域的大小,Block的大小

    static struct __main_block_desc_0 {
      size_t reserved;//今后版本升级所需区域大小
      size_t Block_size;//Block大小
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    
    • 1
    • 2
    • 3
    • 4

    Block构造函数__main_block_impl_0

    即然都是构造函数了,其就负责初始化__main_block_impl_0结构体(也就是Block结构体)的成员变量

      //可以看到里面都是一些赋值操作
      __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;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    关于构造函数对各个成员变量的赋值我们回到上面刚刚转成C++的地方(将其转换为可读性更高的形式):

    struct __main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
    struct __main_block_impl_0 *myBlock = &temp;
    
    • 1
    • 2

    该代码通过__main_block_impl_0构造函数,生成的__main_block_impl_0结构体(Block结构体)的实例的指针,赋值给__main_block_impl_0结构体(Block结构体)类型的指针变量myBlock

    可以看到,调用Block结构体构造函数的时候,传入了两个参数:
    第一个参数:__main_block_func_0

    1. 其就是Block对应的Block语法的主体部分,看一下__main_block_func_0结构体在底层C++部分的定义:
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf("myBlock");
    }
    
    • 1
    • 2
    • 3

    这是一个C语言函数,函数里面的内容和我们OC block中编写的操作是一样的。

    这里传入的参数__cself是指向Block的值的指针变量,相当于OC的self

    构造函数传入的第二个参数是:__main_block_desc_0_DATA包含该Block的相关信息

    Block调用部分

    Block结构体和成员变量已经分析完了,我们来看一下是如何调用Block的:

        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    
    • 1
    • myBlock结构体的第一个成员变量为__block_impl,所以myBlock的首地址,就是__block_impl impl的首地址,也就意味着我们第一个强制类型转换可以直接转换成__block_impl类型
    • ((void (*)(__block_impl *))是__block_impl中Func的类型
    • ((__block_impl *)myBlock)->FuncPtr)调用__main_block_func_0函数
    • ((__block_impl *)myBlock);__main_block_func_0函数的参数
      对于这个调用参数我的理解是传递其Block本身到__main_block_func_0函数的参数__cself中去,这也就可以看出Block自身正是作为参数进行的传递

    Block的实质总结

    用一句话来说,Block是个对象(其内部第一个成员为isa指针)

    在构造函数中,我们可以看到impl.isa = &_NSConcreteStackBlock;
    我们对isa指针进行了赋值,_NSConcreteStackBlock相当于该block实例的父类。在将Block作为OC对象调用时,关于该类的信息放置于_NSConcretestackBlock中,这也证明了 block出生就是在栈上(isa指针指向_NSConcreteStackBlock)

    Block拥有捕获变量的能力:
    关于捕获变量这一点,我们举一个例子:

    	//相比前面的例子就是在定义Block之前多定义了一个NSObject *obj对象,以供Block去捕获
    	NSObject *obj = [[NSObject alloc] init];
    	//这是正常的block流程
        void(^myBlock)(void) = ^{
            printf("myBlock");
        };
        myBlock();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    然后我们用clang编译为C++的形式:

    //Block结构体
    struct __main_block_impl_0 {
      struct __block_impl impl;//impl:Block的实际函数指针,就是指向包含Block主体部分的__main_block_func_0结构体
      struct __main_block_desc_0* Desc;//Desc指针,指向包含Block附加信息的__main_block_desc_0()结构体
      //可以看到下方相比之前的例子多了一个NSObject *obj;对象,这个就是Block所捕获的变量,也证实了block有捕获变量的能力
      NSObject *obj;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {//__main_block_impl_0:Block构造函数(可以看到都是对上方两个成员变量的赋值操作)
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    可以看到编译后在Block结构体里面多出了一个NSObject *obj;对象,这个就是Block所捕获的变量,也证实了block有捕获变量的能力。

    Block的类型

    Block根据其类型可以分为三类:

    • 全局Block(数据区) NSGlobalBlock_NSConcreteGlobalBlock
    • 栈Block(栈区) NSStackBlock_NSConcreteStackBlock
    • 堆Block(堆区) NSMallocBlock_NSConcteteMallocBlock

    而其区分的规则为:

    如果没有引用局部变量,或者只引用了静态变量和全局变量,则为全局Block,如果内部有使用局部变量,如果有被强指针引用过,就是堆Block,如果没有则为栈Block。

    代码例子如下:

    - (void)func2 {
        //静态变量
        static int blockInt = 2;
        /**
         — 全局block,没有使用局部变量,或者只使用了静态变量或者只使用了全局变量
         */
        // 没有使用局部变量
        NSLog(@"block0 - %@",^{});
        // 使用了静态变量
        void(^block1)(void) = ^{
            blockInt = 3;
        };
        NSLog(@"block1 - %@",block1);
        
        /**
         - 堆Block 使用局部变量 并且用强指针引用过
         */
        // 即使用局部变量又使用静态变量
        NSInteger i = 1;
        void(^block2)(void) = ^{
            NSLog(@"block %ld", i);
            NSLog(@"block static %d", blockInt);
        };
        NSLog(@"block2 - %@",block2);
        // 只使用局部变量
        void(^block3)(void) = ^{
            NSLog(@"block %ld", i);
        };
        NSLog(@"block3 - %@",block3);
        // 使用强指针引用过,再使用弱指针引用
        void(^ __weak block4)(void) = block3;
        NSLog(@"block4 - %@",block4);
        /**
         - 栈Block没有被强引用过的
         */
        // 没有使用强指针引用过
        void (^__weak block5)(void) = ^{
            NSLog(@"block %ld", i);
        };
        NSLog(@"block5 - %@",block5);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    分析:

    • block0 : 没有使用任何变量,属于全局block
    • block1 : 只使用了静态变量blockInt,属于全局block
    • block2 : 使用了局部变量和静态变量,并且有被strong引用过,属于堆block
    • block3 : 使用了局部变量i,并且有被strong引用过,属于堆block
    • block4 : 虽然被weak指针引用的,但其已经被strong引用过,属于堆block
    • block5 : 没有被strong指针引用过。即使使用了局部变量,属于栈block

    打印结果:
    请添加图片描述

    下面我们总结一下这三种类型的Block:

    NSGlobalBlock

    没有截获自动变量或者没有被强引用过的话就是NSGlobalBlock,上面我们已经举过一个例子了,现在我们再举一个更常见的例子:

    记述全局变量的地方定义Block就会默认为Global

    //在记述全局变量的地方定义Block
    void (^myGlobalBlock)(void) = ^{
        NSLog(@"this is GlobalBlock");
    };
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
        	//打印类型
            NSLog(@"myGlobalBlock- %@", myGlobalBlock);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    打印结果如下:
    请添加图片描述
    可以看到我们的myGlobalBlock__NSGlobalBlock__的(由于其没有截获自动变量)。

    NSStackBlock

    除了上述的情况,其他情况下创建的Block都是NSConcreteStackBlock对象(只要没有被强引用)

    其存储在栈区。如果其所属的变量作用域结束,则该Block就会被废弃,如果Block使用了__block变量,则__block变量同样被废弃

    NSMallocBlock

    arc下没有栈block了。因为block会自动copy变成堆block

    为了解决栈上的Block在变量作用域结束被废弃这一问题,Block提供了复制的功能,可以将Block对象和__block变量从栈区复制到堆区上。

    所以后面即使栈区的作用域结束时,堆区上的Block和__block变量仍然可以继续存在,也可以继续使用

    如书上下图:
    在这里插入图片描述
    block作为属性,用什么关键字修饰呢?

    MRC下: 使用copy修饰。因为block申明在栈区,使用copy修饰可以将block从栈区copy到堆区。(现已优化,使用copy或strong修饰符都可以copy到堆区)

    ARC下: 使用copy与strong修饰都可以。因为就算用strong,程序会自动copy将block从栈区copy到堆区。

    实际测试中,无论是ARC还是MRC,strong和copy都可以使对象复制到堆上。 测试例子如下:

    @interface block : NSObject {
        void (^blk_)(void);
        void (^blk2_)(void);
    };
    
    @property (nonatomic, strong) void (^block)(void);
    @property (nonatomic, copy) void (^block2)(void);
    
    - (void) test2;
    @end
    
    
    #import "block.h"
    
    @implementation block
    - (void) test2 {
        int a = 2;
        _block = ^ {
            NSLog(@"%d", a);
        };
        
        _block2 = ^ {
            NSLog(@"%d", a);
        };
        
        blk_ = [_block copy];
        blk2_ = [_block2 copy];
        NSLog(@"_block = %@, _block2 = %@, blk_ = %@, blk2_ = %@", _block, _block2, blk_, blk2_);
    }
    @end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    打印结果如下:

    _block = <__NSStackBlock__: 0x3040cb278>, 
    _block2 = <__NSStackBlock__: 0x3040cb250>,
     blk_ = <__NSMallocBlock__: 0x108f292e0>, 
     blk2_ = <__NSMallocBlock__: 0x108f29310>
    
    • 1
    • 2
    • 3
    • 4

    可以看到即使是MRC,strong和copy都可以使对象复制到堆上。

    拷贝情况:

    在ARC环境下编译器会进行判断,三种情况会自动复制。

    • Block作为函数返回值返回
    • 向方法或函数中传递Block,使用以下两种方法的情况下
      1. Cocoa框架的方法且方法名中含有usingBlock等时
      2. GCD的API
    • 将Block赋值给类的附有__strong修饰符的id类型 或 Block类型的成员变量时
    - (void) test2 {
        int a = 2;
        void (^blkTest)(void) = ^{
            NSLog(@"%d", a);
        };
        NSLog(@"blkTest = %@", blkTest);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    打印结果如下:

    blkTest = <__NSMallocBlock__: 0x108e42690>
    
    • 1

    当然有自动拷贝我们也可以手动拷贝

    我们手动拷贝对不同的Block类有不同的效果:

    • 栈区 -> 从栈拷贝到堆
    • 数据区 -> 不改变
    • 堆区 -> 引用计数增加

    __block变量的拷贝:

    在使用__block变量的Block从栈复制到堆上时,__block变量也会受到影响

    按照oc的内存管理机制来管理,此时两者的关系就从block使用__block变成了block持有__block

    __block变量的配置存储区域Block从栈复制到堆时的影响
    堆区被Block所持有,引用计数加1
    栈区从栈复制到堆,并被Block所持有

    当然,如果不再有Block引用该__block变量,那么该__block变量也会被废除

    Block捕获变量

    ARC的时候我们就了解过,变量的几种修饰符,我们来看一下Block如何捕获不同修饰符的类型变量

    先把几个结论扔出来:

    • 全局变量:不捕获
    • 局部变量:捕获值
    • 静态全局变量:不捕获
    • 静态局部变量:捕获指针

    先一个一个看:

    捕获局部变量的例子

    int c = 30;
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            int a = 10, b = 20;
            
            void(^myLocalBlock)(void) = ^{
                NSLog(@"%d %d %d",a,b,c);
            };
            c = 50;
            void(^Block)(int, int, int) = ^(int a, int b, int c) {
                NSLog(@"%d %d %d",a,b,c);
            };
            a = 40;
            b = 50;
            myLocalBlock();
            Block(a,b,c);
        }
        return 0;
    }
    打印结果为:
    Block-Test[70423:4137195] 10 20 50
    Block-Test[70423:4137195] 40 50 50
    
    可以看到,直接访问会被捕获
    间接不会被捕获
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    Block表达式截获所使用的局部变量的值,保存了该变量的瞬时值。

    Block的自动变量的截获只针对Block中使用到的自动变量,没有使用到的自动变量不捕获

    直接访问局部变量

    为什么仅仅截获瞬时值,而不是局部变量的当前值?看一下对应的c++源码:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int a;
      int b;
      //下面的构造函数比以往多出了 : a(_a), b(_b) ,这个:后面的操作意识就是给Block自身的成员变量_a,_b赋值
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int _b, int flags=0) : a(_a), b(_b) {
        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 = __cself->a; // bound by copy
      int b = __cself->b; // bound by copy
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_6p_mn3hwpz14_7dg_gr79rtm4n80000gn_T_main_3bac69_mi_1,a,b,c);
            }
    
    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; 
            int a = 10, b = 20;
            void(*myLocalBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, b));
            c = 50;
            a = 40;
            b = 50;
            ((void (*)(__block_impl *))((__block_impl *)myLocalBlock)->FuncPtr)((__block_impl *)myLocalBlock);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    可以看到__main_block_impl_0结构体里多出了两个成员变量a,b,这两个的值来自__main_block_impl_0构造函数中传递的值
    构造函数加冒号就是相当于一个赋值的过程(这点很重要)
    在这里插入图片描述

      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int _b, int flags=0) : a(_a), b(_b) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • __main_block_func_0结构体中,变量a,b的值使用的是__cself获取的值
      a = __cself->a; b = __cself->b;,这也说明了a,b只是Block内部的变量,改变Block外部的局部变量值,并不能改变Block内部的变量值
    • 我们也可以发现全局变量并没有存储在Block的结构体中,而是在调用的时候通过直接访问的方式来调用

    浅浅地总结一下这个流程:

    Block语法部分的源码就是
    调用__main_block_impl_0的构造函数,构造函数正常有两个参数,func_0desc_0,在截获自动变量时,会把需要截获的自动变量也放入参数列表中,同时__main_block_impl_0中也会增加两个成员变量a,b,构造函数带参数就是自动给这两个成员变量赋值。在调用func_0时,直接通过__cself(相当于self,其传递的参数也是其Block本身)的a、b(此时的a,b就是Block成员变量中的,因为上面的构造函数已经赋值了)

    通过传值间接访问局部变量

    源码如下:

    struct __main_block_impl_1 {
      struct __block_impl impl;
      struct __main_block_desc_1* Desc;
      __main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_1(struct __main_block_impl_1 *__cself, int a, int b, int c) {
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_6p_mn3hwpz14_7dg_gr79rtm4n80000gn_T_main_3bac69_mi_2,a,b,c);
            }
    
    static struct __main_block_desc_1 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_1_DATA = { 0, sizeof(struct __main_block_impl_1)};
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            int a = 10, b = 20;
            c = 50;
            void(*Block)(int, int, int) = ((void (*)(int, int, int))&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA));
            a = 40;
            b = 50;
            ((void (*)(__block_impl *, int, int, int))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block, a, b, c);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • __main_block_impl_1结构体并没有变量a、b,说明通过直接传值的方式,变量并没有存进Block的结构体中。
    • func_0函数中,直接通过参数列表把a,b传进去了,并且在调用时直接传入a,b,c的值这是和直接调用局部变量不同的,并没有直接调用局部变量的那个用:赋值的操作。

    static修饰变量的捕获

    首先了解一下静态变量的概念:

    1. 静态变量一经创建,只要进程还在它就一直在内存的静态存储区当中存在,也可以认为是其内存地址不变,直到整个程序运行结束
    2. 静态变量虽在程序的整个执行过程中始终存在,但是在它作用域之外不能使用。
    3. 所有的全局变量都是静态变量,而局部变量只有定义时加上类型修饰符static,才为局部静态变量。
    4. 静态变量可以在任何可以申请的地方申请,一旦申请成功后,它将不再接受其他的同样申请。
    5. 静态变量并不是说其就不能改变值,不能改变值的量叫常量。 其拥有的值是可变的 ,而且它会保持最新的值。

    所以静态变量的概念和全局变量是有些相似的。

    下面我们看Block中对于静态变量的捕获情况:

    static int c = 30;
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            static const int a = 10;
            static int b = 20;
            void (^Block)(void) = ^{
                printf("a = %d, b = %d, c = %d\n",a, b, c);
            };
            b = 100;
            c = 100;
            Block();
    	}
        return 0;
    }
    
    输出结果为:
    a = 10, b = 100, c = 100
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    从上面的例子中我们可以看出,Block对象好像没有捕获静态全局变量和静态局部变量,那么具体有没有捕获还得看源码分析:

    static int c = 30;
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      const int *a;
      int *b;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const int *_a, int *_b, int flags=0) : a(_a), b(_b) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      const int *a = __cself->a; // bound by copy
      int *b = __cself->b; // bound by copy
    
                printf("a = %d, b = %d, c = %d\n",(*a), (*b), c);
            }
    
    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 const int a = 10;
            static int b = 20;
              void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a, &b));
            b = 100;
            c = 100;
              ((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    我们发现:__main_block_impl_0结构体中,静态局部变量static int b以指针int *b;的形式添加为成员变量,而静态局部常量static const int aconst int *指针的形式添加为成员变量。而全局静态变量并没有添加为成员变量

    我们接着看下方的func_0函数中,静态全局变量直接访问,静态局部变量和静态局部常量都是通过指针传递:

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      const int *a = __cself->a; // bound by copy
      int *b = __cself->b; // bound by copy
    
                printf("a = %d, b = %d, c = %d\n",(*a), (*b), c);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    但是const修饰的无法进行赋值操作(毕竟是常量):
    请添加图片描述
    我们为什么能获取static变量最新的值?
    这个问题又扯到了静态变量的概念了

    1. static修饰的,均存储在全局存储区,该地址在程序运行过程中一直不会改变,所以能访问最新值。
    2. static在修饰后,全局变量直接访问,局部变量指针访问(只要含static变量)。

    const修饰变量的捕获

    代码例子如下:

    const int c = 30;
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            const int a = 10;
            int b = 20;
            void (^Block)(void) = ^{
                printf("a = %d, b = %d, c = %d\n",a, b, c);
            };
            Block();
        }
        return 0;
    }    
    
    输出结果:
    a = 10, b = 20, c = 30   
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    源码如下:

    const int c = 30;
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      const int a;
      int b;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const int _a, int _b, int flags=0) : a(_a), b(_b) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      const int a = __cself->a; // bound by copy
      int b = __cself->b; // bound by copy
    
                    printf("a = %d, b = %d, c = %d\n",a, b, c);
                }
    
    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; 
            const int a = 10;
                int b = 20;
                  void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, b));
                  ((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    可以看到:

    • const全局变量仍然是直接访问的
    • const局部变量和正常的自动变量一样,依然是值传递

    总结

    • 全局变量: 不捕获
    • 局部变量: 捕获值
    • 静态全局变量: 不捕获
    • 静态局部变量: 捕获指针
    • const修饰的局部常量:捕获值
    • const修饰的静态局部常量:捕获指针

    Block捕获对象

    代码例子如下:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            id obj = [NSMutableArray array];
            void (^Block)(void) = ^{
                  NSLog(@"%@",obj);
            };
            [obj addObject:@1];
            Block();
        }
        return 0;
    }   
    输出结果为:
    Block-Test[71641:4198978] (
        1
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    源码为:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      id obj;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _obj, int flags=0) : obj(_obj) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      id obj = __cself->obj; // bound by copy
    
                  NSLog((NSString *)&__NSConstantStringImpl__var_folders_6p_mn3hwpz14_7dg_gr79rtm4n80000gn_T_main_4a265c_mi_1,obj);
            }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    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};
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            id obj = ((NSMutableArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
            void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
            ((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)obj, sel_registerName("addObject:"), (id _Nonnull)((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 1));
            ((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 我们看到__main_block_impl_0结构体中多了一个成员变量__strong id obj;因为obj是自动变量,所以这里捕获了自动变量obj作为_main_block_impl_0结构体的成员变量。
    • fun0也是指针传递(结构体外面自动变量是什么类型,结构体里面成员变量也是什么类型,id是指针类型,所以结构体里面也是指针类型),func_0也有__strong id obj = __cself->obj;这样的赋值操作
    • 但是这里的源码多出了两个函数指针:
    //其中调用的_Block_object_assign相当于retain,将对象赋值在对象类型的结构体变量__main_block_impl_0中。在栈上的Block复制到堆时会进行调用。
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    //其中调用的_Block_object_dispose相当于release,释放赋值在对象类型的结构体变量中的对象。在堆上Block被废弃时会被调用
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    • 1
    • 2
    • 3
    • 4

    针对这两个函数指针:

    • __main_block_copy_0作用就是调用_Block_object_assign,相当于retain,将对象赋值在对象类型的结构体变量__main_block_impl_0中。在栈上的Block复制到堆时会进行调用。
    • __main_block_dispose_0调用_Block_object_dispose,相当于release,释放赋值在对象类型的结构体变量中的对象。在堆上的Block被废弃时会被调用。

    __block修饰符

    __block修饰局部变量

    代码例子如下:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            __block int a = 10, b = 20;
            void (^Block)(void) = ^{
                  NSLog(@"%d %d", a, b);
            };
            a = 100;
            b = 100;
            Block();
        }
        return 0;
    }
    
    输出结果:
    100  100 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    源码如下:

    struct __Block_byref_a_0 {
      void *__isa;
    __Block_byref_a_0 *__forwarding;
     int __flags;
     int __size;
     int a;
    };
    struct __Block_byref_b_1 {
      void *__isa;
    __Block_byref_b_1 *__forwarding;
     int __flags;
     int __size;
     int b;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_a_0 *a; // by ref
      __Block_byref_b_1 *b; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_b_1 *_b, int flags=0) : a(_a->__forwarding), 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_a_0 *a = __cself->a; // bound by ref
      __Block_byref_b_1 *b = __cself->b; // bound by ref
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_6p_mn3hwpz14_7dg_gr79rtm4n80000gn_T_main_4a8210_mi_1, (a->__forwarding->a), (b->__forwarding->b));
            }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);_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->a, 8/*BLOCK_FIELD_IS_BYREF*/);_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};
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
    __Block_byref_b_1 b = {(void*)0,(__Block_byref_b_1 *)&b, 0, sizeof(__Block_byref_b_1), 20};
            void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, (__Block_byref_b_1 *)&b, 570425344));
            (a.__forwarding->a) = 100;
            (b.__forwarding->b) = 100;
            ((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    我们可以发现,就仅仅添加了一个__block修饰符,就增加了好多内容,接下来我们一步一步来分析:

    首先从__main_block_impl_0看起:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_a_0 *a; // by ref
      __Block_byref_b_1 *b; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_b_1 *_b, int flags=0) : a(_a->__forwarding), b(_b->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    我们发现__block int a __block int b分别变成了__Block_byref_a_0 *a;__Block_byref_b_1 *b;,这是结构体类型的指针,对于用__block修饰的变量,不管使用了没,都会相应的生成一个结构体,我们拿其中任何一个结构体举个例子:

    struct __Block_byref_a_0 {
      void *__isa;//标识对象类的isa实例变量
    __Block_byref_a_0 *__forwarding;//传入变量的地址(局部变量a本身的地址)
     int __flags;//标志位
     int __size;//结构体大小
     int a;//存放a实际的值,和之前加__block修饰符时一致,其实就相当于原自动变量
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    接着我们来看一下这个结构体的赋值情况(main函数中):

    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {
    	(void*)0,
    	(__Block_byref_a_0 *)&a,
    	0,
    	sizeof(__Block_byref_a_0), 
    	10
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    将这个赋值情况一一对应写成可读性更高的形式是:

    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {
    	void *__isa = (void*)0,
    	__Block_byref_a_0 *__forwarding = (__Block_byref_a_0 *)&a,
    	int __flags = 0,
    	int __size = sizeof(__Block_byref_a_0), 
    	int a = 10
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    我们可以看到__isa指针值传空,__forwarding指向了局部变量a本身的地址,__flags分配了0,__size为结构体大小,a赋值为10。

    __forwarding的指向如下图所示:
    在这里插入图片描述
    main函数赋值操作__Block_byref_a_0 *__forwarding = (__Block_byref_a_0 *)&a就可以看出__forwarding是一个指针,它的值是最初的我们在main函数中声明的局部变量a的地址值,所以__forwarding其实就是局部变量a本身的地址。

    现在访问成员变量a的完整流程解释如下:
    我们可以看到main函数中(a.__forwarding->a) = 100;,说明现在想要访问自动变量a,就需要__main_block_impl_0结构体中的__Block_byref_a_0 *a;指针先访问__Block_byref_a_0结构体,接着通过__Block_byref_a_0结构体中的成员变量__forwarding访问成员变量a。到这里,整个流程就完成了。

    一个问题,那么如果堆上和栈上都有我们的__block,我们如何找到我们需要的那个?

    这也就用到了__forwarding指针,它在没有复制的时候就是简单的指向自己,而当进行复制以后,就会指向堆上的那个__block变量

    栈上不持有仅仅只是使用,复制到堆上才持有:

    在这里插入图片描述

    这里还有一个疑问?为什么我们的__block变量的__Block_byref_a_0结构体不直接存放在Block__main_block_impl_0结构体中,而是要将其指针存放在__main_block_impl_0结构体中,其实原因是这样子的话,我们就可以在多个Block中使用同一个__block变量了

    __block修饰对象

    代码例子如下:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            
            __block Test *testFirst = [[Test alloc] init];
            NSLog(@"%@",testFirst);
            void (^Block)(void) = ^{
            	NSLog(@"%@",testFirst);
                testFirst = [[Test alloc]init];
                NSLog(@"%@",testFirst);
            };
            Block();
        }
        return 0;
    }
    打印结果为:
    Block-Test[72427:4254748] <Test: 0x10d830bf0>
    Block-Test[72427:4254748] <Test: 0x10d830bf0>
    Block-Test[72427:4254748] <Test: 0x10d904180>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    可以看到未再次在Block中进行初始化之前两次打印的testFirst对象的地址是相同的

    源码如下:

    struct __Block_byref_testFirst_0 {
      void *__isa;
    __Block_byref_testFirst_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     Test *testFirst;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_testFirst_0 *testFirst; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_testFirst_0 *_testFirst, int flags=0) : testFirst(_testFirst->__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_testFirst_0 *testFirst = __cself->testFirst; // bound by ref
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_6p_mn3hwpz14_7dg_gr79rtm4n80000gn_T_main_ab9f4e_mi_2,(testFirst->__forwarding->testFirst));
                (testFirst->__forwarding->testFirst) = ((Test *(*)(id, SEL))(void *)objc_msgSend)((id)((Test *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Test"), sel_registerName("alloc")), sel_registerName("init"));
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_6p_mn3hwpz14_7dg_gr79rtm4n80000gn_T_main_ab9f4e_mi_3,(testFirst->__forwarding->testFirst));
            }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->testFirst, (void*)src->testFirst, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->testFirst, 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};
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            __attribute__((__blocks__(byref))) __Block_byref_testFirst_0 testFirst = {(void*)0,(__Block_byref_testFirst_0 *)&testFirst, 33554432, sizeof(__Block_byref_testFirst_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((Test *(*)(id, SEL))(void *)objc_msgSend)((id)((Test *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Test"), sel_registerName("alloc")), sel_registerName("init"))};
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6p_mn3hwpz14_7dg_gr79rtm4n80000gn_T_main_ab9f4e_mi_1,(testFirst.__forwarding->testFirst));
            void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_testFirst_0 *)&testFirst, 570425344));
            ((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    继续是先从__main_block_impl_0结构体开始看:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_testFirst_0 *testFirst; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_testFirst_0 *_testFirst, int flags=0) : testFirst(_testFirst->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    __block变量的情况相比并没有什么大的区别。
    我们接着来看main函数中的对于__Block_byref_testFirst_0结构体的初始化。

     __attribute__((__blocks__(byref))) __Block_byref_testFirst_0 testFirst = {
     	(void*)0,
     	(__Block_byref_testFirst_0 *)&testFirst, 
     	33554432, 
     	sizeof(__Block_byref_testFirst_0), 
     	__Block_byref_id_object_copy_131, 
     	__Block_byref_id_object_dispose_131, 
     	((Test *(*)(id, SEL))(void *)objc_msgSend)((id)((Test *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Test"),sel_registerName("alloc")), sel_registerName("init"))
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    转换为可读性更高的形式就是:

     __attribute__((__blocks__(byref))) __Block_byref_testFirst_0 testFirst = {
      	void *__isa = (void*)0;
    	__Block_byref_testFirst_0 *__forwarding = (__Block_byref_testFirst_0 *)&testFirst;
     	int __flags = 33554432;
     	int __size = sizeof(__Block_byref_testFirst_0);
     	void (*__Block_byref_id_object_copy)(void*, void*) = __Block_byref_id_object_copy_131;
    	void (*__Block_byref_id_object_dispose)(void*) = __Block_byref_id_object_dispose_131;
     	Test *testFirst = ((Test *(*)(id, SEL))(void *)objc_msgSend)((id)((Test *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Test"),sel_registerName("alloc")), sel_registerName("init"));
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    flags = 33554432 即二进制的 1 << 25
    BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler 译:compiler 含有copy_dispose助手[即拥有copy和dispose函数]

    这里有两个函数:__Block_byref_id_object_copy_131__Block_byref_id_object_dispose_131

    //其中也是调用了_Block_object_assign方法,在Block拷贝在堆上时对对象进行持有
    static void __Block_byref_id_object_copy_131(void *dst, void *src) {
     _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
    }
    //其中也是调用了_Block_object_dispose方法,在Block从堆上废弃时对__block对象的持有进行释放
    static void __Block_byref_id_object_dispose_131(void *src) {
     _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这两个函数的作用看函数名就知道:__Block_byref_id_object_copy_131是当__block对象随Block从栈拷贝到堆上时调用的,调用后Block就持有了该对象,在栈上的时候只是使用并没有持有。而__Block_byref_id_object_dispose_131就是Block从堆上释放时调用该方法,从而释放掉对__block对象的持有。

    但是我们发现这里调用_Block_object_assign_Block_object_release的时候还加了40,究竟是为什么呢,我们接着看下面:

    struct __Block_byref_testFirst_0 {
      void *__isa;//8字节
    __Block_byref_testFirst_0 *__forwarding;//8字节
     int __flags;//4字节
     int __size;//4字节
     void (*__Block_byref_id_object_copy)(void*, void*);//8字节
     void (*__Block_byref_id_object_dispose)(void*);//8字节
     Test *testFirst;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    我们发现__Block_byref_testFirst_0结构体的起始地址和testFirst的地址之间相差40字节,所以+40也是为了可以找到testFirst指针,通过testFirst指针进行调用。

    __block修饰局部变量,这个变量在block内外属于同一个地址上的变量,可以被block内部的代码修改:
    在这里插入图片描述
    对于__block类型的对象的捕获中的内存管理逻辑就是:
    (以上方代码例子为例:)struct __main_block_impl_0中的对象指针的成员变量__Block_byref_testFirst_0 *testFirst;指向__Block_byref_testFirst_0结构体,再由__Block_byref_testFirst_0结构体中的__forwarding指针指向我们所需要的__block类型的对象。
    (其中__forwarding指针的值就是原本我们所需要的__block类型的对象的地址值)

    Block的内存管理

    干预:程序员手动管理,本质还是要系统管理内存的分配和释放

    • 自动局部基本类型变量,因为是值传递,内存是跟随Block,不用干预
    • static局部基本类型变量,指针传递,由于分配在静态区,故不用干预
    • 全局变量,存储在数据去,不用干预
    • 局部对象变量,如果在栈上,只是使用,不用干预。但Block在拷贝到堆时,对其retain,在Block对象销毁时,对其release

    引用计数相关内容:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            
            __block Test *testFirst = [[Test alloc] init];
            NSLog(@"%lu", CFGetRetainCount((__bridge  CFTypeRef)testFirst));
            NSLog(@"%@",testFirst);
            void (^Block)(void) = ^{
                NSLog(@"%@",testFirst);
                NSLog(@"%lu", CFGetRetainCount((__bridge  CFTypeRef)testFirst));
                Test *testSecond = testFirst;
                NSLog(@"%lu", CFGetRetainCount((__bridge  CFTypeRef)testFirst));
            };
            Block();
            NSLog(@"%@", Block);
            NSLog(@"%lu", CFGetRetainCount((__bridge  CFTypeRef)testFirst));
        }
        return 0;
    }
    打印结果如下:
    Block-Test[73044:4299100] 1
    Block-Test[73044:4299100] <Test: 0x10e060070>
    Block-Test[73044:4299100] <Test: 0x10e060070>
    Block-Test[73044:4299100] 1
    Block-Test[73044:4299100] 2
    Block-Test[73044:4299100] <__NSMallocBlock__: 0x108e26060>
    Block-Test[73044:4299100] 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    我们看到这个例子的引用计数只有在Block中将testFirst强持有了一次之后才变为2,其他情况全都是1,而且Block的类型结果打印显示Block就是在堆上,让人有些疑惑是不是Block从栈拷贝到堆上的时候没有调用_Block_object_assign方法。

    其实其原因如下:
    从栈上拷贝Block到堆上的时候会调用这么一个函数:

    // Copy, or bump refcount, of a block.  If really copying, call the copy helper if present.
    // 拷贝 block,
    // 如果原来就在堆上,就将引用计数加 1;
    // 如果原来在栈上,会拷贝到堆上,引用计数初始化为 1,并且会调用 copy helper 方法(如果存在的话);
    // 如果 block 在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身
    // 参数 arg 就是 Block_layout 对象,
    // 返回值是拷贝后的 block 的地址
    // 运行?stack -》malloc
    void *_Block_copy(const void *arg) {
        struct Block_layout *aBlock;
    
        // 如果 arg 为 NULL,直接返回 NULL
    
        if (!arg) return NULL;
        
        // The following would be better done as a switch statement
        // 强转为 Block_layout 类型
        aBlock = (struct Block_layout *)arg;
        const char *signature = _Block_descriptor_3(aBlock)->signature;
        
        // 如果现在已经在堆上
        if (aBlock->flags & BLOCK_NEEDS_FREE) {
            // latches on high
            // 就只将引用计数加 1
            latching_incr_int(&aBlock->flags);
            return aBlock;
        }
        // 如果 block 在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身
        else if (aBlock->flags & BLOCK_IS_GLOBAL) {
            return aBlock;
        }
        else {
            // Its a stack block.  Make a copy.
            // block 现在在栈上,现在需要将其拷贝到堆上
            // 在堆上重新开辟一块和 aBlock 相同大小的内存
            struct Block_layout *result =
                (struct Block_layout *)malloc(aBlock->descriptor->size);
            // 开辟失败,返回 NULL
            if (!result) return NULL;
            // 将 aBlock 内存上的数据全部复制新开辟的 result 上
            memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
    #if __has_feature(ptrauth_calls)
            // Resign the invoke pointer as it uses address authentication.
            result->invoke = aBlock->invoke;
    #endif
            // reset refcount
            // 将 flags 中的 BLOCK_REFCOUNT_MASK 和 BLOCK_DEALLOCATING 部分的位全部清为 0
            result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
            // 将 result 标记位在堆上,需要手动释放;并且引用计数初始化为 1
            result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
            // copy 方法中会调用做拷贝成员变量的工作
            _Block_call_copy_helper(result, aBlock);
            // Set isa last so memory analysis tools see a fully-initialized object.
            // isa 指向 _NSConcreteMallocBlock
            result->isa = _NSConcreteMallocBlock;
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    可以看到该函数作用是:

    1. 如果原来就在堆上,就将引用计数加 1;
    2. 如果原来在栈上,会拷贝到堆上,引用计数初始化为 1

    所以这就是新拷贝到堆上的Block的引用计数为什么是1的原因,因为被重新初始化为1啦。

    关于这部分的详解可以参考:iOS八股文(十六)关于Block,你在第几层?

    另一个引用计数情况:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            
            Test *testFirst = [[Test alloc] init];
            NSLog(@"%lu", CFGetRetainCount((__bridge  CFTypeRef)testFirst));
            NSLog(@"%@",testFirst);
            void (^Block)(void) = ^{
                NSLog(@"%@",testFirst);
                NSLog(@"%lu", CFGetRetainCount((__bridge  CFTypeRef)testFirst));
                Test *testSecond = testFirst;
                NSLog(@"%lu", CFGetRetainCount((__bridge  CFTypeRef)testFirst));
            };
            Block();
            NSLog(@"%@", Block);
            NSLog(@"%lu", CFGetRetainCount((__bridge  CFTypeRef)testFirst));
        }
        return 0;
    }
    打印结果如下:
    Block-Test[73128:4302875] 1
    Block-Test[73128:4302875] <Test: 0x1098a0930>
    Block-Test[73128:4302875] <Test: 0x1098a0930>
    Block-Test[73128:4302875] 3
    Block-Test[73128:4302875] 4
    Block-Test[73128:4302875] <__NSMallocBlock__: 0x109905460>
    Block-Test[73128:4302875] 3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    可以看到从进入了Block之后,__block对象的引用计数居然从1突增到了3,然后在Block中经过一次强引用之后引用计数又达到了4,最后出Block之后那一次强引用释放,引用计数又到了3

    其实这个3也好理解:

    1. 创建好对象之后对象本身持有一次
    2. 栈上的block没有持有,但是这个第四行的成员变量这个指针持有一次
    3. copy到堆上之后在堆上的第四行的成员变量这个指针又持有一次

    所以一共是3次引用计数。

    Block循环引用的问题

    1. 什么时候会发生循环引用,如何解决?
      一个对象中强引用了block,在block中又使用了该对象,就会发生循环引用。
      解决是:将该对象使用__weak或者__block修饰符修饰之后再在block中使用。
    2. 变量前加block和weak和strong的区别
    • __strong是为了防止block持有的对象提前释放,__weak和__block是解决循环引用
    • __block不管是ARC还是MRC都可以使用,可以修饰对象和基本数据类型
    • __weak只能在ARC模式下使用,也只能修饰对象,不能修饰基本数据类型
    • __block对象可以在Block中被重新赋值,__weak不可以
    1. 对于__block变量MRC如何解决循环引用?ARC如何解决?
    • MRC解决循环引用用__block,禁止Block对所引用的对象进行retain操作
    • ARC时期__block并不能禁止Block对所引用的对象进行强引用。解决办法可以是在Block中将变量置空,因为需要在Block中对Block进行操作,所以还是需要__block修饰符
    • __block在MRC下有两个作用
      1. 允许在Block中访问和修改局部变量
      2. 禁止Block对所引用的对象进行retain操作
    • ARC下仅仅只有一个作用
      1. 允许在Block中访问和修改局部变量

    解决Block循环引用的方法上文已经介绍了三种,其中最常用的就是: iOS开发“强弱共舞”——weak和strong配套使用解决block循环引用问题

    • __weak是为了解决循环引用
    • __strong是为了防止block持有的对象提前释放
  • 相关阅读:
    开发《星球大战》小游戏的意义
    h5日历组件制作
    Docker核心概念
    Minio + Nginx 实现静态资源对外访问
    deeplearning4j使用vgg19图片向量比对springboot+es环境
    【LeetCode】57. 插入区间
    Java之字符流的详细解析
    JAVA:实现Node节点类算法(附完整源码)
    Vue中的事件监听
    双线性插值算法原理讲解及计算示例
  • 原文地址:https://blog.csdn.net/m0_52192682/article/details/125900989