Block再探

Block的使用规范

什么是Blocks?

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

Block语法

格式

标准格式

请添加图片描述
例子:

^int (int count){return count + 1;}
省略格式

返回值类型默认void

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

另外如果没有参数的话也就直接相当于省略了参数列表部分
请添加图片描述

^{return count + 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

截获自动变量

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

int val = 10;
void (^blk)(int a) = ^(int a){
	printf("a = %d beforeVal = %d\n", a, val);
};
val = 2;
blk(val);

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

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

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

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

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

然而使用指针表示这个数组就没有问题了:请添加图片描述

可以注意到现在已经没有报错了,而且运行的结果是: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;

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

   __block int val = 10;
    void (^blk)(void) = ^{
        val = 5;
        printf("val = %d\n",val);
    };
    val = 2;
    blk();
    return 0;

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

Block的循环引用

- (id)init {
	self = [super init];
	blk_ = ^{NSLog(@"self = %@", self);};
	return self;
}

可以看到这是一个类的初始化方法,我们在初始化这个类的实例时就会造成循环引用,因为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;
}

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

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

参数传递
- (id)init {
	self = [super init];
	__block Person *blockSelf = self;
	blk_ = ^(Person * _Nonnull selfTest) {
		NSLog(@"self = %@", selfTest);
	};
	blk_(self);
	return self;
}

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

Block的实现

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

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

	//这是正常的block流程
    void(^myBlock)(void) = ^{
        printf("myBlock");
    };
    myBlock();

重写命令的方法(先在终端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>
//注意实际写文件名的时候不要带<>

得到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);

我们来分析一下:
初始化定义部分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;
  }
};

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

  • 成员变量impl
  • 成员变量Desc指针
  • __main_block_impl_0构造函数

struct __block_impl结构

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

struct __block_impl {
  void *isa;//用于保存Block结构体的实例指针
  int Flags;//标志位
  int Reserved;//今后版本升级所需的区域大小
  void *FuncPtr;//函数指针
};
  • __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)};

关于构造函数对各个成员变量的赋值我们回到上面刚刚转成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;

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

可以看到,调用Block结构体构造函数的时候,传入了两个参数:
第一个参数:__main_block_func_0
其就是Block对应的Block语法的主体部分,看一下__main_block_func_0结构体在底层C++部分的定义

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("myBlock");
}

这是一个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);
  • myBlock结构体的第一个成员变量为__block_impl,所以myBlock的首地址,就是__block_impl impl的首地址,也就意味着我们第一个强制类型转换可以直接转换成__block_impl类型
  • ((void (*)(__block_impl *))__block_implFunc的类型
  • ((__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();

然后我们用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;
  }
};

可以看到编译后在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);
}

分析:

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

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

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

拷贝情况:

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

  • Block作为函数返回值返回
  • 向方法或函数中传递Block,使用以下两种方法的情况下

Cocoa框架的方法且方法名中含有usingBlock等时
GCD的API

  • 将Block赋值给类的附有__strong修饰符的id类型 或 Block类型的成员变量时
- (void) test2 {
    int a = 2;
    void (^blkTest)(void) = ^{
        NSLog(@"%d", a);
    };
    NSLog(@"blkTest = %@", blkTest);
}

当然有自动拷贝我们也可以手动拷贝
我们手动拷贝对不同的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

可以看到,直接访问会被捕获
间接不会被捕获

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

可以看到__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;
  }

  • __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;
}
  • __main_block_impl_1结构体并没有变量a、b,说明通过直接传值的方式,变量并没有存进Block的结构体中。
  • func_0函数中,直接通过参数列表把a,b传进去了,并且在调用时直接传入a,b,c的值这是和直接调用局部变量不同的,并没有直接调用局部变量的那个用:赋值的操作。

static修饰变量的捕获
首先了解一下静态变量的概念:

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

下面我们看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

从上面的例子中我们可以看出,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;
}

我们发现:__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);
        }

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

static修饰的,均存储在全局存储区,该地址在程序运行过程中一直不会改变,所以能访问最新值。
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   

源码如下

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

可以看到:

  • 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
)

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

  • 我们看到__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*/);}

针对这两个函数指针:

  • __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 

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

我们可以发现,就仅仅添加了一个__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;
  }
};

我们发现__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修饰符时一致,其实就相当于原自动变量
};

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

__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {
	(void*)0,
	(__Block_byref_a_0 *)&a,
	0,
	sizeof(__Block_byref_a_0), 
	10
};

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

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

我们可以看到__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>

可以看到未再次在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;
}

继续是先从__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;
  }
};

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

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

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

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

这两个函数的作用看函数名就知道:__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;
};
**

我们发现__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

我们看到这个例子的引用计数只有在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;
如果原来在栈上,会拷贝到堆上,引用计数初始化为 1
所以这就是新拷贝到堆上的Block的引用计数为什么是1的原因,因为被重新初始化为1啦。

另一个引用计数情况:

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

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

其实这个3也好理解:

  • 创建好对象之后对象本身持有一次
  • 栈上的block没有持有,但是这个第四行的成员变量这个指针持有一次
  • copy到堆上之后在堆上的第四行的成员变量这个指针又持有一次
  • 所以一共是3次引用计数。

Block循环引用的问题

  • 什么时候会发生循环引用,如何解决?
    一个对象中强引用了block,在block中又使用了该对象,就会发生循环引用。

  • 解决是:将该对象使用__weak或者__block修饰符修饰之后再在block中使用。
    变量前加block和weak和strong的区别

  • __strong是为了防止block持有的对象提前释放,__weak和__block是解决循环引用

  • __block不管是ARC还是MRC都可以使用,可以修饰对象和基本数据类型

  • __weak只能在ARC模式下使用,也只能修饰对象,不能修饰基本数据类型

  • __block对象可以在Block中被重新赋值,__weak不可以
    对于__block变量MRC如何解决循环引用?ARC如何解决?

  • MRC解决循环引用用__block,禁止Block对所引用的对象进行retain操作

  • ARC时期__block并不能禁止Block对所引用的对象进行强引用。解决办法可以是在Block中将变量置空,因为需要在Block中对Block进行操作,所以还是需要__block修饰符

__block在MRC下有两个作用

  • 允许在Block中访问和修改局部变量
  • 禁止Block对所引用的对象进行retain操作

ARC下仅仅只有一个作用

  • 允许在Block中访问和修改局部变量

  • 解决Block循环引用的方法上文已经介绍了三种,其中最常用的就是:“强弱共舞”

  • __weak是为了解决循环引用

  • __strong是为了防止block持有的对象提前释放

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山河丘壑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值