block常见类型(3种):
-
NSGlobalBlock:全局的静态block 没有访问外部变量
image.png
-
NSMallocBlock 保存在堆中的block 此类型blcok是用copy修饰出来的block 它会随着对象的销毁而销毁,只要对象不销毁,我们就可以调用的到在堆中的block
@interface Person : NSObject
@property(nonatomic,copy) void(^mallocBlock)(void);
@end

-
NSStackBlock:保存在栈中的block,没有用copy去修饰并且访问了外部变量,会在函数调用结束被销毁 (但是要在MRC模式下)
ARC改MRC方法
在ARC中的NSStackBlock:
需要用assign去修饰Block(测试用,工作中用copy最好)
@property(nonatomic,assign) void(^mallocBlock)(void);
NSLog(@"%@",[person.mallocBlock class]);
//打印结果
block[22533:1198598] __NSStackBlock__
//注意一下,在之前iOS8之前,如果用retain去修饰Block和打印结果和assign是一样的
//但是之后retain修饰Block打印结果就变成了__NSMallocBlock__(应该是苹果修改了)
//只是警告:Retain'ed block property does not copy the block - use copy attribute instead 让我们使用copy去修饰
// 但是用strong去修饰Block,打印结果和copy一致,并且不会报错
NSLog(@"StackBlock 当前类型-- %@",^{
NSLog(@"%d",a);
});
//打印结果:
// StackBlock 当前类型-- <__NSStackBlock__: 0x7ffee1f3ca40>
Block在ARC下的内存管理
在ARC默认情况下,Block的内存存储在堆中,ARC会自动进行内存管理,程序员只需要避免循环引用即可
在Block的内存存储在堆中时,如果在Block中引用了外面的对象,会对所引用的对象进行强引用,但是在Block被释放时会自动去掉对该对象的强引用,所以不会造成内存泄漏
如果对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用,解决循环引用的办法是使用一个弱引用的指针指向该对象,然后在Block内部使用该弱引用指针来进行操作,这样避免了Block对对象进行强引用
解决循环引用的方式一:
@interface Person : NSObject
@property(nonatomic,copy) void(^mallocBlock)(void);
@property(nonatomic,copy) NSString * str;
@end
Person * person = [[Person alloc] init];
person.str = @"外部属性";
__weak Person * personWeak = person;
person.mallocBlock = ^{
NSLog(@"访问外部变量 %@ 则变成mallocBlock",personWeak.str);
};
person.mallocBlock();
@implementation Person
-(void)dealloc{
NSLog(@"释放成功");
}
@end
解决循环引用的方式二:
Person * p = [[Person alloc] init];
//这里用 __block去修饰,是为了在block内部可以对weakP进行修改
__block Person * weakP = p;
p.myBlock = ^{
NSLog(@"%@",weakP.myBlock);
//通过__block修饰可以对weakP进行修改
weakP = nil;
};
p.myBlock();
解决循环引用的方式三(优雅的方式):
@interface Person : NSObject
//定义一个有参数的block类型的成员属性,参数类型是Person(也就是自身的类型)
@property(nonatomic,copy) void(^myBlock)(Person*);
@end
Person * p = [[Person alloc] init];
p.myBlock = ^(Person * weakP) {
NSLog(@"%@",weakP);
};
//需要传入一个类型为Person的参数,将p传进去
p.myBlock(p);
block代码块中修改局部变量
直接在block代码中修改局部变量,会报出错误!
错误信息:Variable is not assignable (missing __block type specifier)
变量不可分配(缺少__block类型说明符)

所以正确的修改方式是 在局部变量前加__block
//__block 底层实现:用__block修饰的对象,会修改它的指针地址
__block int a =1;
NSLog(@"外部%p",&a);
void(^myBlock)(void) = ^{
a++;
NSLog(@"%d",a);
NSLog(@"内部%p",&a);
};
myBlock();
NSLog(@"最后外部%p",&a);
//打印结果:
//外部0x7ffee6e21a28
// 2
// 内部0x60400023b9f8
//最后外部0x60400023b9f8
可以很清楚的看到,用__block修饰以后a的指针地址发生了改变。
如果先执行 block,代码块中局部变量
会保持之前的值,之后再去更改局部变量的值,对代码块的内容无影响(需要注意)
int a = 1;
void(^editBlcok)(void) = ^{
NSLog(@"%d",a);
};
a = 10;
editBlcok();
// 控制台输出 1 ;
Block在MRC下的内存管理
默认情况下,Block的内存存储在栈中,不需要开发人员对其进行内存管理
在Block的内存存储在栈中时,如果在Block中引用了外面的对象,不会对所引用的对象进行任何操作
如果对Block进行一次copy操作,那么Block的内存会被移动到堆中,这时需要开发人员对其进行release操作来管理内存
如果对Block进行一次copy操作,那么Block的内存会被移动到堆中,在Block的内存存储在堆中时,如果在Block中引用了外面的对象,会对所引用的对象进行一次retain操作,即使在Block自身调用了release操作之后,Block也不会对所引用的对象进行一次release操作,这时会造成内存泄漏
为了不对所引用的对象进行一次retain操作,可以在对象的前面使用下划线下划线block来修饰。
__block Person *p = [[Person alloc] init];
void(^myBlock)() = ^{
NSLog(@"------%@", p);
};
myBlock();
总结:
__block在MRC下有两个作用
- 允许在Block中访问和修改局部变量
- 禁止Block对所引用的对象进行隐式retain操作
__block在ARC下只有一个作用
- 允许在Block中访问和修改局部变量
__weak 解决ARC下的循环引用
如果对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用
网友评论