前言:Block 是开发过程中常用便捷的回调方式,本文简单介绍 Block
一、Block 简介
Block 对象是 C 级别的语法和运行时特性,和标准的 C 函数类似,除了可执行代码外,还可能包含变量自动绑定(栈)和内存托管(堆)。一个 Block 维护一个状态集。
闭包 = 一个函数「或指向函数的指针」+ 该函数执行的外部的上下文变量「也就是自由变量」;Block 是 Objective-C 对于闭包的实现。
- 可以嵌套定义,定义 Block 方法和定义函数方法相似
- Block 可以定义在方法内部或外部
- 只有调用 Block 时候,才会执行其{}体内的代码
- 本质是对象,使代码高聚合
使用 clang 将 OC 代码转换为 C++ 文件查看 block 的方法:
- 在命令行输入代码 clang -rewrite-objc 需要编译的OC文件.m
- 这时查看当前的文件夹里 多了一个相同的名称的 .cpp 文件,在命令行输入 open main.cpp 查看文件
1.1 定义和使用
1.无参数无返回值
void (^ MyBlockOne)(void) = ^(void){
NSLog(@"无参数,无返回值");
};
MyBlockOne();//block的调用
2.有参数无返回值
void(^MyblockTwo)(int a) = ^(int a){
NSLog(@"@ = %d我就是block,有参数,无返回值",a);
};
MyblockTwo(100);
3.有参数有返回值
int(^MyBlockThree)(int,int) = ^(int a,int b){
NSLog(@"%d我就是block,有参数,有返回值",a + b);
return a + b;
};
MyBlockThree(12,56);
4.无参数有返回值
int(^MyblockFour)(void) = ^{
NSLog(@"无参数,有返回值");
return45;
};
MyblockFour();
5.定义声明
声明
typedef void (^Block)();
typedef int (^MyBlock)(int , int);
typedef void(^ConfirmBlock)(BOOL isOK);
typedef void(^AlertBlock)(NSInteger alertTag);
定义属性
@property (nonatomic,copy) MyBlock myBlockOne;
使用
self.myBlockOne = ^int (int ,int){
//TODO
}
1.2 Block与外界变量
1、截获自动变量(局部变量)值
(1)默认情况
对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的。也就是说block的自动变量截获只针对block内部使用的自动变量, 不使用则不截获, 因为截获的自动变量会存储于block的结构体内部, 会导致block体积变大。特别要注意的是默认情况下block只能访问不能修改局部变量的值。
int age = 10;
myBlock block = ^{
NSLog(@"age = %d", age);
};
age = 18;
block();
输出结果:
age = 10
(2) __block 修饰的外部变量
对于用 __block 修饰的外部变量引用,block 是复制其引用地址来实现访问的。block可以修改__block 修饰的外部变量的值。
__block int age = 10;
myBlock block = ^{
NSLog(@"age = %d", age);
};
age = 18;
block();
输出为:
age = 18
2、__block 修饰的外部变量的值就可以被block修改
我们使用 clang 将 OC 代码转换为 C++ 文件:
clang -rewrite-objc 源代码文件名
__block int val = 10;
转换成
__Block_byref_val_0 val = {
0,
&val,
0,
sizeof(__Block_byref_val_0),
10
};
会发现一个局部变量加上__block修饰符后竟然跟block一样变成了一个__Block_byref_val_0
结构体类型的自动变量实例。
此时我们在block内部访问val变量则需要通过一个叫__forwarding
的成员变量来间接访问val变量。
1.3 Block 存储
1、Block的存储域及copy操作
由C/C++/OBJC编译的程序占用内存分布的结构:
block有三种类型:
- 全局块(_NSConcreteGlobalBlock)
- 栈块(_NSConcreteStackBlock)
- 堆块(_NSConcreteMallocBlock)
三种block各自的存储域:
- 全局块存在于全局内存中, 相当于单例.
- 栈块存在于栈内存中, 超出其作用域则马上被销毁
- 堆块存在于堆内存中, 是一个带引用计数的对象, 需要自行管理其内存
简而言之,存储在栈中的Block就是栈块、存储在堆中的就是堆块、既不在栈中也不在堆中的块就是全局块。
1.4 判断Block的存储位置
(1)Block不访问外界变量(包括栈中和堆中的变量)
Block 既不在栈又不在堆中,在代码段中,ARC和MRC下都是如此。此时为全局块。
(2)Block访问外界变量
MRC 环境下:访问外界变量的 Block 默认存储栈中。
ARC 环境下:访问外界变量的 Block 默认存储在堆中(实际是放在栈区,然后ARC情况下自动又拷贝到堆区),自动释放。
ARC下,访问外界变量的 Block为什么要自动从栈区拷贝到堆区呢?
栈上的Block,如果其所属的变量作用域结束,该Block就被废弃,如同一般的自动变量。当然,Block中的__block变量也同时被废弃。
为了解决栈块在其变量作用域结束之后被废弃(释放)的问题,我们需要把Block复制到堆中,延长其生命周期。开启ARC时,大多数情况下编译器会恰当地进行判断是否有需要将Block从栈复制到堆,如果有,自动生成将Block从栈上复制到堆上的代码。Block的复制操作执行的是copy实例方法。Block只要调用了copy方法,栈块就会变成堆块。
- 在ARC的Block是配置在栈上的,所以返回函数调用方时,Block变量作用域就结束了,Block会被废弃。种情况编译器会自动完成复制。
- 在非ARC情况下则需要开发者调用copy方法手动复制。
- 将Block从栈上复制到堆上相当消耗CPU,所以当Block设置在栈上也能够使用时,就不要复制了,因为此时的复制只是在浪费CPU资源。
Block的复制操作执行的是copy实例方法。不同类型的Block使用copy方法的效果如下表:
根据表得知,Block在堆中copy会造成引用计数增加,这与其他Objective-C对象是一样的。虽然Block在栈中也是以对象的身份存在,但是栈块没有引用计数,因为不需要,我们都知道栈区的内存由编译器自动分配释放。
不管Block存储域在何处,用copy方法复制都不会引起任何问题。在不确定时调用copy方法即可。在ARC有效时,多次调用copy方法完全没有问题:
blk = [[[[blk copy] copy] copy] copy];
// 经过多次复制,变量blk仍然持有Block的强引用,该Block不会被废弃。
1.5 __block变量与__forwarding
在copy操作之后,既然__block变量也被copy到堆上去了, 那么访问该变量是访问栈上的还是堆上的呢?__forwarding 终于要闪亮登场了。通过__forwarding, 无论是在block中还是 block外访问__block变量, 也不管该变量在栈上或堆上, 都能顺利地访问同一个__block变量。
1.6 Block 循环引用
Block 循环引用的情况:
某个类将 block 作为自己的属性变量,然后该类在 block 的方法体里面又使用了该类本身。
self.someBlock = ^(Type var){
[self dosomething];
};
解决办法:
(1)ARC 下:使用 __weak
__weak typeof(self) weakSelf = self;
self.someBlock = ^(Type var){
[weakSelf dosomething];
};
(2)MRC 下:使用 __block
__block typeof(self) blockSelf = self;
self.someBlock = ^(Type var){
[blockSelf dosomething];
};
解决办法:
//1.使用__weak ClassName
__block XXViewController* weakSelf = self;
self.blk = ^{
NSLog(@"In Block : %@",weakSelf);
};
//2.使用__weak typeof(self)
__weak typeof(self) weakSelf = self;
self.blk = ^{
NSLog(@"In Block : %@",weakSelf);
};
//3.Reactive Cocoa中的@weakify和@strongify
@weakify(self);
self.blk = ^{
@strongify(self);
NSLog(@"In Block : %@",self);
};
二、Block 应用
2.1 Block 应用
1、Block作为变量(Xcode快捷键:inlineBlock)
int (^sum) (int, int); // 定义一个 Block 变量 sum
// 给 Block 变量赋值
// 一般 返回值省略:sum = ^(int a,int b)…
sum = ^int (int a,int b){
return a+b;
}; // 赋值语句最后有 分号
int a = sum(10,20); // 调用 Block 变量
2、Block作为属性(Xcode 快捷键:typedefBlock)
// 1. 给 Calculate 类型 sum变量 赋值「下定义」
typedef int (^Calculate)(int, int); // calculate就是类型名
Calculate sum = ^(int a,int b){
return a+b;
};
int a = sum(10,20); // 调用 sum变量
// 2. 作为对象的属性声明,copy 后 block 会转移到堆中和对象一起
@property (nonatomic, copy) Calculate sum; // 使用 typedef
@property (nonatomic, copy) int (^sum)(int, int); // 不使用 typedef
// 声明,类外
self.sum = ^(int a,int b){
return a+b;
};
// 调用,类内
int a = self.sum(10,20);
3、作为 OC 中的方法参数
// ---- 无参数传递的 Block ---------------------------
// 实现
- (CGFloat)testTimeConsume:(void(^)())middleBlock {
// 执行前记录下当前的时间
CFTimeInterval startTime = CACurrentMediaTime();
middleBlock();
// 执行后记录下当前的时间
CFTimeInterval endTime = CACurrentMediaTime();
return endTime - startTime;
}
// 调用
[self testTimeConsume:^{
// 放入 block 中的代码
}];
// ---- 有参数传递的 Block ---------------------------
// 实现
- (CGFloat)testTimeConsume:(void(^)(NSString * name))middleBlock {
// 执行前记录下当前的时间
CFTimeInterval startTime = CACurrentMediaTime();
NSString *name = @"有参数";
middleBlock(name);
// 执行后记录下当前的时间
CFTimeInterval endTime = CACurrentMediaTime();
return endTime - startTime;
}
// 调用
[self testTimeConsume:^(NSString *name) {
// 放入 block 中的代码,可以使用参数 name
// 参数 name 是实现代码中传入的,在调用时只能使用,不能传值
}];
4、Block回调
Block回调是关于Block最常用的内容,比如网络下载,我们可以用Block实现下载成功与失败的反馈。block使用简单,逻辑清晰,灵活。
2.2 Block 几种类型演算
{
NSLog(@"\n--------------------block调用 基本数据类型---------------------\n");
int a = 10;
NSLog(@"block定义前a地址=%p", &a);
void (^aBlock)() = ^(){
NSLog(@"block定义内部a地址=%p", &a);
};
NSLog(@"block定义后a地址=%p", &a);
aBlock();
}
/*
结果:
block定义前a地址=0x7fff5bdcea8c
block定义后a地址=0x7fff5bdcea8c
block定义内部a地址=0x7fa87150b850
*/
/*
流程:
1. block定义前:a在栈区
2. block定义内部:里面的a是根据外面的a拷贝到堆中的,不是一个a
3. block定义后:a在栈区
*/
{
NSLog(@"\n--------------------block调用 __block修饰的基本数据类型---------------------\n");
__block int b = 10;
NSLog(@"block定义前b地址=%p", &b);
void (^bBlock)() = ^(){
b = 20;
NSLog(@"block定义内部b地址=%p", &b);
};
NSLog(@"block定义后b地址=%p", &b);
NSLog(@"调用block前 b=%d", b);
bBlock();
NSLog(@"调用block后 b=%d", b);
}
/*
结果:
block定义前b地址=0x7fff5bdcea50
block定义后b地址=0x7fa873b016d8
调用block前 b=10
block定义内部b地址=0x7fa873b016d8
调用block后 b=20
*/
/*
流程:
1. 声明 b 为 __block (__block 所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。)
2. block定义前:b在栈中。
3. block定义内部: 将外面的b拷贝到堆中,并且使外面的b和里面的b是一个。
4. block定义后:外面的b和里面的b是一个。
5. block调用前:b的值还未被修改。
6. block调用后:b的值在block内部被修改。
*/
{
NSLog(@"\n--------------------block调用 指针---------------------\n");
NSString *c = @"ccc";
NSLog(@"block定义前:c=%@, c指向的地址=%p, c本身的地址=%p", c, c, &c);
void (^cBlock)() = ^{
NSLog(@"block定义内部:c=%@, c指向的地址=%p, c本身的地址=%p", c, c, &c);
};
NSLog(@"block定义后:c=%@, c指向的地址=%p, c本身的地址=%p", c, c, &c);
cBlock();
NSLog(@"block调用后:c=%@, c指向的地址=%p, c本身的地址=%p", c, c, &c);
}
/* 输出结果
block定义前:c=ccc, c指向的地址=0x1068aac68, c本身的地址=0x7ffee93a7ab8
block定义后:c=ccc, c指向的地址=0x1068aac68, c本身的地址=0x7ffee93a7ab8
block定义内部:c=ccc, c指向的地址=0x1068aac68, c本身的地址=0x6000002542a0
block调用后:c=ccc, c指向的地址=0x1068aac68, c本身的地址=0x7ffee93a7ab8
c指针本身在block定义中和外面不是一个,但是c指向的地址一直保持不变。
1. block定义前:c指向的地址在堆中, c指针本身的地址在栈中。
2. block定义内部:c指向的地址在堆中, c指针本身的地址在堆中(c指针本身和外面的不是一个,但是指向的地址和外面指向的地址是一样的)。
3. block定义后:c不变,c指向的地址在堆中, c指针本身的地址在栈中。
4. block调用后:c不变,c指向的地址在堆中, c指针本身的地址在栈中。
*/
{
NSLog(@"\n--------------------block调用 指针并修改值---------------------\n");
NSMutableString *d = [NSMutableString stringWithFormat:@"ddd"];
NSLog(@"block定义前:d=%@, d指向的地址=%p, d本身的地址=%p", d, d, &d);
void (^dBlock)() = ^{
NSLog(@"block定义内部:d=%@, d指向的地址=%p, d本身的地址=%p", d, d, &d);
d.string = @"dddddd";
};
NSLog(@"block定义后:d=%@, d指向的地址=%p, d本身的地址=%p", d, d, &d);
dBlock();
NSLog(@"block调用后:d=%@, d指向的地址=%p, d本身的地址=%p", d, d, &d);
}
/*输出结果
block定义前:d=ddd, d指向的地址=0x600000440300, d本身的地址=0x7ffee9b2dab8
block定义后:d=ddd, d指向的地址=0x600000440300, d本身的地址=0x7ffee9b2dab8
block定义内部:d=ddd, d指向的地址=0x600000440300, d本身的地址=0x604000253940
block调用后:d=dddddd, d指向的地址=0x600000440300, d本身的地址=0x7ffee9b2dab8
d指针本身在block定义中和外面不是一个,但是d指向的地址一直保持不变。
在block调用后,d指向的堆中存储的值发生了变化。
*/
{
NSLog(@"\n--------------------block调用 __block修饰的指针---------------------\n");
__block NSMutableString *e = [NSMutableString stringWithFormat:@"eee"];
NSLog(@"block定义前:e=%@, e指向的地址=%p, e本身的地址=%p", e, e, &e);
void (^eBlock)() = ^{
NSLog(@"block定义内部:e=%@, e指向的地址=%p, e本身的地址=%p", e, e, &e);
e = [NSMutableString stringWithFormat:@"new-eeeeee"];
};
NSLog(@"block定义后:e=%@, e指向的地址=%p, e本身的地址=%p", e, e, &e);
eBlock();
NSLog(@"block调用后:e=%@, e指向的地址=%p, e本身的地址=%p", e, e, &e);
}
/*
从block定义内部使用__block修饰的e指针开始,e指针本身的地址由栈中改变到堆中,即使出了block,也在堆中。
在block调用后,e在block内部重新指向一个新对象,e指向的堆中的地址发生了变化。
*/
{
NSLog(@"\n--------------------block调用 retain cycle---------------------\n");
View *v = [[View alloc] init];
v.tag = 1;
v.frame = CGRectMake(100, 100, 100, 100);
[self.view addSubview:v]; //self->view->v
void (^block)() = ^{
v.backgroundColor = [UIColor orangeColor]; //定义内部:block->v
};
v.block = block; //v->block
block();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//预计3秒后释放v对象。
[v removeFromSuperview];
});
}
/*
结果:
不会输出 dealloc.
*/
/*
流程:
1. self->view->v
2. block定义内部:block->v 因为block定义里面调用了v
3. v->block
结论:
引起循环引用的是block->v->block,切断其中一个线即可解决循环引用,跟self->view->v这根线无关
*/
{
NSLog(@"\n--------------------block调用self---------------------\n");
View *v = [[View alloc] init];
v.tag = 2;
v.frame = CGRectMake(100, 220, 100, 100);
[self.view addSubview:v]; //self->view->v
void (^block)() = ^{
self.view.backgroundColor = [UIColor redColor]; //定义内部:block->self
_count ++; //调用self的实例变量,也会让block强引用self。
};
v.block = block; //v->block
block();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//预计3秒后释放self这个对象。
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
appDelegate.window.rootViewController = nil;
});
}
/*
结果:
不会输出 dealloc.
*/
/*
流程:
1. self->view->v
2. v->block
3. block->self 因为block定义里面调用了self
结论:
在block内引用实例变量,该实例变量会被block强引用。
引起循环引用的是self->view->v->block->self,切断一个线即可解决循环引用。
*/
2.3 Block 存储域
1. 在全局数据区的Block对象
NSGlobalBlock 静态block,释放有两种不同的时机:
- 1、如果这个block引用了外部变量后是栈block,则在定义此block的函数出栈时,block释放。
- 2、如果这个blcok引用了外部变量之后是堆block,则其宿主target释放的时候此block才释放。
{
NSLog(@"\n--------------------block的存储域 全局块---------------------\n");
void (^blk)(void) = ^{
NSLog(@"Global Block");
};
blk();
NSLog(@"%@", [blk class]);
}
/*
结果:输出 __NSGlobalBlock__
*/
/*
结论:
全局块:这种块不会捕捉任何状态(外部的变量),运行时也无须有状态来参与。块所使用的整个内存区域,在编译期就已经确定。
全局块一般声明在全局作用域中。但注意有种特殊情况,在函数栈上创建的block,如果没有捕捉外部变量,block的实例还是会被设置在程序的全局数据区,而非栈上。
*/
2.在堆上创建的Block对象
NSMallocBlock 堆区block
堆区是内存的常驻区域,也叫永久存储区,block一般在函数中定义,最多是个栈block。在MRC时代你需要使用Block_copy()方法,才可以将blcok复制到堆中。
复制到堆中有何用处呢?
- 首先,作为一个对象,把它复制到堆中,想要使用它肯定要有一个指针指向它,而指向它的指针是作为property或静态变量出现的(如果不被引用也就没有了常驻于堆区的意义),而实际开发中多使用poperty引用。
- 在MRC中,如果一个类的block属性是使用copy修饰的,则不需要手动调用Block_copy将其复制到堆中。如果是用strong修饰的,则必须使用Block_copy()将其复制到堆中,并在释放时调用Block_release()方法将其释放,否则会因野指针导致程序崩溃。
{
NSLog(@"\n--------------------block的存储域 堆块---------------------\n");
int i = 1;
void (^blk)(void) = ^{
NSLog(@"Malloc Block, %d", i);
};
blk();
NSLog(@"%@", [blk class]);
}
/*
结果:输出 __NSMallocBlock__
*/
/*
结论:
堆块:解决块在栈上会被覆写的问题,可以给块对象发送copy消息将它拷贝到堆上。复制到堆上后,块就成了带引用计数的对象了。
在ARC中,以下几种情况栈上的Block会自动复制到堆上:
- 调用Block的copy方法
- 将Block作为函数返回值时(MRC时此条无效,需手动调用copy)
- 将Block赋值给__strong修饰的变量时(MRC时此条无效)
- 向Cocoa框架含有usingBlock的方法或者GCD的API传递Block参数时
上述代码就是在ARC中,block赋值给__strong修饰的变量,并且捕获了外部变量,block就会自动复制到堆上。
*/
3.在栈上创建的Block对象
NSStackBlock 栈区block
- 函数只有入栈后才能执行,出栈后就释放了。
- 栈block一般在函数内部定义,并在函数内部调用;或者在函数外部定义,作为函数的一个参数在函数内部调用。函数出栈时和其他变量或参数一起释放。
{
NSLog(@"\n--------------------block的存储域 栈块---------------------\n");
int i = 1;
__weak void (^blk)(void) = ^{
NSLog(@"Stack Block, %d", i);
};
blk();
NSLog(@"%@", [blk class]);
}
/*
结果:输出 __NSStackBlock__
*/
/*
结论:
栈块:块所占内存区域分配在栈中,编译器有可能把分配给块的内存覆写掉。
在ARC中,除了上面四种情况,并且不在global上,block是在栈中。
*/
三、Block 原理
Block 优缺点
优点:
- 捕获外部变量
- 降低代码分散程度
缺点:
- 循环引用引起内存泄露
Block 总结
- 在block内部使用的是将外部变量的拷贝到堆中的(基本数据类型直接拷贝一份到堆中,对象类型只将在栈中的指针拷贝到堆中并且指针所指向的地址不变)。
- __block修饰符的作用:是将block中用到的变量,拷贝到堆中,并且外部的变量本身地址也改变到堆中。
- __block不能解决循环引用,需要在block执行尾部将变量设置成nil
- __weak可以解决循环引用,block在捕获weakObj时,会对weakObj指向的对象进行弱引用。
- 使用__weak时,可在block开始用局部__strong变量持有,以免block执行期间对象被释放。
- 全局块不引用外部变量,所以不用考虑。
- 堆块引用的外部变量,不是原始的外部变量,是拷贝到堆中的副本。
- 栈块本身就在栈中,引用外部变量不会拷贝到堆中。
- __weak 本身是可以避免循环引用的问题的,但是其会导致外部对象释放了之后,block 内部也访问不到这个对象的问题,我们可以通过在 block 内部声明一个 __strong 的变量来指向 weakObj,使外部对象既能在 block 内部保持住,又能避免循环引用的问题。
- __block 本身无法避免循环引用的问题,但是我们可以通过在 block 内部手动把 blockObj 赋值为 nil 的方式来避免循环引用的问题。另外一点就是 __block 修饰的变量在 block 内外都是唯一的,要注意这个特性可能带来的隐患。
- block的实现原理是C语言的函数指针。函数指针即函数在内存中的地址,通过这个地址可以达到调用函数的目的。
Q:什么是Block?
A:Block是将函数
及其执行上下文
封装起来的对象
struct __block_impl{
void *isa;//Block 是对象的标志
int Flags;
int Reserved;
void *FuncPtr;//函数指针
};
Q:什么是Block调用?
A:Block调用是函数调用
Q:Block 如何截获变量?
A:1.基本数据
类型的局部变量
截获其值
2.对象
类型的局部变量连同所有权修饰符
一起截获
3.局部静态变量
以指针
形式截获
4.不截获
全局变量、静态全局变量
Q:什么情况使用__block修饰符?
A:一般情况下,对被截获变量进行赋值
操作需要添加__block修饰符
赋值 != 使用
赋值:
赋值操作需要使用 __block
修饰
__block NSMutableArray *arrM = nil;
void (^testBlock)(void) = ^{
arrM = [NSMutableArray array];
};
testBlock();
使用:如下代码不需要使用__block
,因为是对数组的操作而不是数组的赋值。
NSMutableArray *arrM = [NSMutableArray array];
void (^testBlock)(void) = ^{
[arrM addObject:@"addObj"];
};
testBlock();
Q:__block做了什么
A:__block修饰变量变成了对象
Q:Block的Copy操作效果
A:如图
Q:栈上Block的销毁
A:如图
Q:栈上Block的Copy操作
A:如图
Q:栈上Block的Copy,MRC是否会引起内存泄漏
A:会的
Q:栈上__block变量的Copy操作
A:如图,修改__block变量值,修改的都是堆上的值
Q:__forwarding存在的意义
A:无论在栈还是堆上,__forwarding都可以顺利访问到同一个__block变量
Q:Block外部定义__weak修饰变量可以解决循环引用?
A:Block截获对象
是连同所有权修饰符
一起截获的,如在外部对变量进行__weak
修饰,结构体里持有的对象类型也是weak
Q:Block的引用循环,如图代码出现什么问题?
A:MRC下,不会产生循环引用;ARC下回产生循环引用,引起内存泄漏
ARC下的引用循环
ARC下的引用循环解决方案
上述代码:ARC下解决方案
弊端:如果该Block长时间不被引用,该断环处一直存在,循环引用无法解除
Question1:__weak修饰对象,当外部对象释放了之后,block 内部也访问不到这个对象,怎么办?
Answer:通过在 block 内部声明一个 __strong 的变量来指向 weakObj,使外部对象既能在 block 内部保持住,又能避免循环引用的问题。
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf print];
};
我们以 AFNetworking 中 AFNetworkReachabilityManager.m 的一段代码举例:
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};
Question2:__strong修饰对象,会不会引起循环引用?
Answer:不会!
详解:
__weak修饰的对象被Block引用,不会影响对象的释放,而__strong在Block内部修饰的对象,会保证,在使用这个对象在scope内,这个对象都不会被释放,出了scope,引用计数就会-1。
self是一个指向实例对象的指针,它的生命周期至少是伴随着当前的实例对象的,一旦它和对象之间有循环引用是无法被自动打破的;strongSelf是block内部的一个局部变量,变量的作用域仅限于局部代码,而程序一旦跳出作用域,strongSelf就会被释放,这个临时产生的“循环引用”就会被自动打破,代码的执行事实上也是这样子的。