本文主要总结了:
- block的基本语法
有/无返回值和形参
typedef定义block类型- 变量访问,__block底层原理
- block的3种类型
- 什么时候会触发block的copy
- block的循环引用
什么是循环引用
block循环引用例子
检测工具instruments-Leaks- __weak
为什么__weak可以打破循环引用?
常见使用- __strong
使用场景,例子(延迟函数)
一、block基本语法
block用来保存一段代码,封装代码段,在需要的时候用。
block的标志:^
1. 没有返回值和形参时,可以省略后面的()
void (^myBlock) ()= ^ {
//要保存的代码段
};
// 调用方式与函数一样:
myBlock();
2. 有返回值和形参
int (^sumBlock) (int , int) = ^(int a , int b){
return a+b;
};
int c = sumBlock(10,12);
对比函数指针:
int sum(int a, int b){
return a+b;
}
//调用:
int (*p)(int, int)=sum;
int d = p(10,12);
- 再来一个例子:
void (^lineBlock)(int) = ^(int n){
for(int i=0; i<n; i++){
NSLog(@“_______”);
}
};
lineBlock(5);
3. 使用typedef定义block类型
//定义MyBlock是一个传两个int类型的参数,返回一个int的代码块
typedef int (^MyBlock)(int, int);
这时
int (^sumBlock) (int , int) = ^(int a , int b){
return a+b;
};
int c = sumBlock(10,12);
就可以写成:
MyBlock sumBlock=^(int a, int b){
return a+b;
};
sumBlock(1, 2);
二、变量访问
- 在block中可以使用和改变全局变量
- block内部可以访问外面的变量
- 局部变量,可以使用,不能改变
本地变量,代码块会在定义的时候复制并保存他们的状态,作为常量获取到
typedef double (^MulBlock)(void);
double a=10,b=20;
MulBlock b=^(void){
return a*b;
}
a=20;
b=50;
NSLog(@“%f”,b(a, b));//此时还是会输出200
*可以通过将变量标记为全局(static)解决
- 默认情况下,block内部不能修改外面的局部变量
- 给局部变量加上_blcok关键字,这个局部变量就可以在block内部捕捉到变量,并可以进行修改
__block int b=20;
【注意】__block修饰时,底层实现是将变量从栈拷贝一份到堆中,从而获得使用权,用代码打印一下:
__block int a = 0;
NSLog(@"a address %p",&a);
void (^function)()=^(){
NSLog(@"a address %p",&a);
a = 100;
NSLog(@"1 %d",a);
};
a = 20;
function();
NSLog(@"a address %p",&a);
NSLog(@"2 %d",a);
//打印结果:
// a address 0x7ffeefbff5d8,还在栈中
// a address 0x10060fff8,拷贝到了堆中
// 1 100
// a address 0x10060fff8
// 2 100,在block中对a修改成功
- __block int a = 0;对a的内存地址进行修改。从栈copy到堆中,此时不会被随意销毁。
- 栈的区间一般来说是2M,变化区间过大内存地址会发生变化,由高地址跑到低地址,进入堆中。
附上内存中堆栈的关系图(参考了_block关键字的实现原理):
三、block的3种类型:
1. NSGlobalBlock,全局block:静态
- 位于全局区
- 在block内部不使用外部变量,或者只使用静态变量和全局变量
//例子1
- (void)viewDidLoad {
// main thread [block copy]
void (^block)(void) = ^{
NSLog(@"hello block");
}; //匿名函数
block();
NSLog(@"%@",block);
//万物皆对象 <__NSGlobalBlock__: 0x108656088>
//虽然赋值给了强引用对象“block”(默认__strong修饰),但是没有使用外部变量,所以是globalBlock
}
//例子2
- (void)viewDidLoad {
NSLog(@"%@",^{
});
//<__NSGlobalBlock__: 0x100001028>
//没有使用外部变量,所以还是globalBlock
}
2. NSMallocBlock,堆block
- 位于堆区
- 在block内部使用局部变量或者OC属性,并且赋值给强引用或者Copy修饰的变量
- (void)viewDidLoad {
int a = 10; //捕获外部变量
//下面括号里的block,默认是用__strong修饰的强引用
void (^block)(void) = ^{
//访问内存空间访问、捕获外部变量
NSLog(@"hello block %d",a);
}; //匿名函数
block();
NSLog(@"%@",block); //<__NSMallocBlock__: 0x6000025c4ba0>
}
3. NSStackBlock,栈block
- 位于栈区
- 与mallocBlock一样,可以在内部使用局部变量或者OC属性。但是!不能赋值给强引用或Copy修饰的变量。
//例子1
- (void)viewDidLoad {
int a = 10;
NSLog(@"%@",^{
NSLog(@"%d",a); //<__NSStackBlock__: 0x7ffee71e2930>
});
}
//例子2
- (void)viewDidLoad {
int a = 10;
//不写"__weak"时默认是__strong修饰
void(^__weak block)(void) = ^{
NSLog(@"%d",a);
};
NSLog(@"%@",block); //<__NSStackBlock__: 0x7ffeefbff4c8>
}
四、什么情况下会触发block的Copy
- 手动copy✅
- (void)viewDidLoad {
int a = 10;
void (^__weak myBlock)(void) = ^{
NSLog(@"%d",a);
};
NSLog(@"%@",myBlock); //<__NSStackBlock__: 0x7ffeefbff4c8>
NSLog(@"%@",[myBlock copy]); //<__NSMallocBlock__: 0x1007003f0>
}
- Block作为返回值❌ 不一定会触发copy
- (void)viewDidLoad {
NSLog(@"%@", [self returnBlock]);
//因为下面方法中的block,并没有使用局部变量,仍然是globalBlock
}
- (void(^)(void))returnBlock {
return ^{
};
}
- 被强引用或者Copy修饰❌也要符合“在block内部使用了局部变量”
- 系统API包含usingBlock
- NSArray
五、block的循环引用
1. 什么是循环引用?
在堆上的对象与堆上的对象互相强引用造成的环。
可能是两个对象,可能是N个对象
2. block循环引用例子
- 例子1
person->block, block->person
@interface Person : NSObject
@property (nonatomic, copy) void(^block)(void);
@end
int main(int argh, const char * argue[]) {
Person *person = [Person new];
person.block = ^{ //赋值时person持有了block
NSLog(@"%@", person); //block内部又捕获person,引用+1
};
return 0;
}
- 例子2
在最外层ViewController中,创建BlockCoat类,BlockCoat中有有一个属性是BlockTest类,BlockTest类中有一个属性是block。
在ViewController中,调用BlockCoat的方法,通过方法调用完之后,BlockTest有无执行dealloc方法,来判断是否存在循环引用。
// ViewController.m
#import "BlockCoat.h"
@implementation ViewController
- (void)viewDidLoad {
NSLog(@"-----lychee1----");
BlockCoat *bc = [BlockCoat new];
[bc test]; //在最外层的vc中测试,blockTest是否能正常dealloc
NSLog(@"-----lychee2----");
}
// BlockCoat.h
#import <Foundation/Foundation.h>
@class BlockTest;
@interface BlockCoat : NSObject
@property(nonatomic, strong) BlockTest *bt;
- (void)test;
@end
// BlockCoat.m
#import "BlockCoat.h"
@implementation BlockCoat
- (void)test {
int a = 10;
self.bt = [BlockTest new]; //self(blockCoat)持有了blockTest
__weak typeof (self) weakSelf = self;
[self.bt testBlock:^BOOL{ //参数block中,捕获了self
NSLog(@"%@",self);
// NSLog(@"%@", weakSelf);
NSLog(@"%d",a);
return YES;
}];
}
@end
// BlockTest.h
#import <Foundation/Foundation.h>
typedef BOOL(^myBlock)(void);
@interface BlockTest : NSObject
@property(nonatomic, copy) myBlock b;
- (BOOL)testBlock:(myBlock)block;
@end
// BlockTest.m
#import "BlockTest.h"
@implementation BlockTest
- (BOOL)testBlock:(myBlock)block {
//还未进行赋值copy,传进来的block有捕获外部变量(self:BlockCoat),打印出来是stackBlock (不捕获外部变量时是globalBlock)
NSLog(@"33---%@",block);
self.b = block;
//赋值给了copy修饰的b属性,此时BlockTest持有了block,有捕获外部变量,打印的是mallocBlock
NSLog(@"44---%@",self.b);
return block();
}
- (void)dealloc {
NSLog(@"dealloc~~");
}
@end
由于在BlockCoat.m中,持有了BlockTest,同时block捕获了self(即BlockCoat);
下面的BlockTest.m中,持有了block,变为了mallocBlock;
此时就造成了循环引用:BlockCoat->BlockTest->block->BlockCoat
打破循环引用的方法?
在BlockCoat.m中,调用方法的block中,self换成weakSelf
什么时候不存在循环引用?(打破循环链中的任一环节)
(1)BlockCoat中,不把BlockTest作为属性赋值,用到的时候直接创建
(2)或者,BlockTest中,直接调用传来的block,不要赋值给自己的属性(持有)
(3)或者,block中,不捕获self(BlockCoat)
当不存在循环引用时,就不需要写__weak,直接用self
从上面这个例子中联想,如果BlockTest是系统框架中的类,那么在我们自定义的类中,如果持有了系统类对象,并且传参的block中捕获了self,就要特别注意测试是否存在循环引用。
如果能够确定系统库/三方库的类里,没有对block进行持有,例如masonry,只执行了block方法,那么就不会产生循环引用。
- 例子3
self -> _source -> block -> self
@interface ViewController()
@property (nonatomic, strong) dispatch_source_t source;
@end
@implementation ViewController
- (void)viewDidLoad {
[self dispatch_source_block];
}
- (void)dispatch_source_block {
_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(_source, dispatch_time(DISPATCH_TIME_NOW, 0), 1, 3);
dispatch_block_t block = ^{
NSLog(@"%@",self);
NSLog(@"12321");
};
dispatch_source_set_event_handler(_source, block); //_source持有block
dispatch_activate(_source);
}
- (void)dealloc{
NSLog(@"dealloc----");
}
3. 循环引用检测工具
instruments:Leaks
- 菜单栏 Xcode->Open developer tool->instruments
- 选择Blank
- 点右上角“+”号,下拉框中选Leaks
- 左上角选择要运行的应用程序,点击红点开始
有红色❌说明检测到内存泄露,根据下面显示的信息,具体排查问题。
(这里只是简单使用,具体还待进一步对工具进行学习、总结。)
六、 __weak 打破循环引用
为什么__weak可以打破循环引用?
(1)weak的作用:把self加入sideTables的弱引用表中,不对它产生强引用,在使用的时候取出来。当self的强引用计数变为0时,将其从表中移除,并自动置为nil
(2)mallocBlock捕获__weak修饰的变量时,捕获进内部的也是用__weak修饰。实际是在block内部另外定义了一个__weak修饰的局部变量,指向外部的变量。
//例如这么写
__weak typeof (self) weakSelf = self;
void(^block)(void) = ^{
weakSelf;
};
//在底层,实际上,block内部新建了__weak修饰的局部变量,来指向weakSelf
__weak typeof (self) weakSelf = self;
void(^block)(void) = ^{
//即 __weak NSObject *a = weakSelf;
__weak typeof(weakSelf) a = weakSelf;
a;
};
【tips】经常用到的宏定义弱引用:
//用weakState(weakVar, strongVar)来指代后面的__weak typeof(strongVar) weakVar = strongVar
#define weakState(weakVar, strongVar) __weak typeof(strongVar) weakVar = strongVar;
//__weak表示修饰词,typeof()获取原始变量的类型,定义同类型的新变量weakVar
//将原始变量strongVar,赋值给weakVar
__weak typeof(strongVar) weakVar = strongVar;
此时,在需要弱引用的地方只需要写:
weakState(weakSelf, self);
后文中,就可以拿weakSelf来用
七、 __strong
- 应用场景:希望对象存活的时间久一点(持续到block结束),一般用在异步回调block中,防止执行block时,引用的对象已经被释放。OC中给nil发送消息是不响应的。
- 以延时函数为例
(1)不存在循环引用的情况:
block中的self,延迟函数延迟2秒后才释放
换成weakSelf,self将立即释放,不会延迟2秒
- (void)viewDidLoad {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self tp]; //这里没有循环引用,直接使用self即可
//如果使用了weakSelf,由于没有强引用self,self释放掉之后,weakSelf就指向了nil,不会执行tp方法。
//[weakSelf tp];
NSLog(@"---延迟2秒");
});
}
- (void)tp{
NSLog(@"hahaha-----%@",self);
}
(2)存在循环引用,且block嵌套的情况
- 因为self->myBlock->self,所以myBlock中用到self的地方要用weakSelf替代
- 由于延迟函数,要执行self的tp方法,就要求self要能够延迟2秒后再释放,这时就需要用到__strong。但是__strong是写在myBlock中呢?还是dispatch_after中呢?
先写答案,要写在myBlock中,然后在dispatch_after中捕获外层的strongSelf。
- (void)viewDidLoad {
__weak typeof (self) weakSelf = self;
self.myBlock = ^(int a) {
__strong typeof(weakSelf) strongSelf = weakSelf; //正确写法
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// __strong typeof(weakSelf) strongSelf = weakSelf; //错误写法
[strongSelf tp];
NSLog(@"---延迟2秒");
});
[weakSelf tp];
};
self.myBlock(0);
}
原因是:变量在block中是层层传递的。
而如果__strong写在dispatch_after的block中,则会变成:
【延伸】
后面这种写法,虽然无法“延长变量存活时间”,但反其道行之,既然正常不存在循环引用的情况下,不会执行对应方法。那么如果方法有执行,就说明存在循环引用?
FBRetainCycleDetector + MLeaksFinder 阅读中,指出MLeaksFinder关键源码中,就有这样的写法:
- (BOOL)willDealloc {
NSString *className = NSStringFromClass([self class]);
if ([[NSObject classNamesWhitelist] containsObject:className]) return NO;
NSNumber *senderPtr = objc_getAssociatedObject([UIApplication sharedApplication], kLatestSenderKey);
if ([senderPtr isEqualToNumber:@((uintptr_t)self)]) return NO;
__weak id weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
__strong id strongSelf = weakSelf;
[strongSelf assertNotDealloc];
//此处,如果方法有响应,说明对象没有正常释放,存在内存泄露情况!!
});
return YES;
}
(flag: MLeaksFinder源码之后抽空学习总结!)
以上~如有错误还望不吝指出。