iOS开发之block(二)
2015-12-08 15:55
447 查看
之前的文章中介绍了关于block的定义和使用。这篇文章主要介绍跟block的内存管理的有关知识。重点介绍block在使用内部变量、外部变量和ARC、非ARC的情况下的使用,以及block引发的循环引用问题。
根据Block在内存中的位置分为三种类型NSGlobalBlock,NSStackBlock, NSMallocBlock。(知道就行)
<1>NSGlobalBlock:类似函数,位于text段;
<2>NSStackBlock:位于栈内存,函数返回后Block将无效;
<3>NSMallocBlock:位于堆内存。
使用内部变量在ARC和非ARC条件下:
对于没有引用外部变量的Block,无论在ARC还是非ARC下,类型都是__NSGlobalBlock__,这种类型的block可以理解成一种全局的block,不需要考虑作用域问题。同时,对block进行Copy或者Retain操作也是无效的,
输出:
123
__NSGlobalBlock__
1
可以看到,copy后的block和原来是同一个对象的。
使用外部变量在ARC条件下:
对于引用了外部变量的block,如果没有对他进行copy,它的作用域只会在声明它的函数栈内(类型是__NSStackBlock__),如果在ARC下直接返回此类block是正确的,因为在ARC下,系统会自动添加copy,如下:
输出:
123
__NSMallocBlock__
类型是__NSMallocBlock__,说明block已经被copy到了堆中了。
使用外部变量在非ARC条件下:
block内使用外部变量,在非ARC下编译会直接报错(Xcode提示Returningblock that lives on the local stack),因为此时block还在栈中,不能执行retain操作。
当然其实在非ARC下,也可以使上面的情况编译通过,如下
我们把原来的返回值赋给一个变量,然后再返回这个变量,就可以编译通过了。不过虽然编译通过了,这个返回的block作用域仍是在函数栈中的,因此一旦函数运行完毕后再使用这个block很可能会引发BAD_ACCESS错误。
所以在非ARC下,必须把block复制到堆中才可以在函数外使用,或使用外部变量,如下:
block修改外部变量的值
block使用外部变量,并不对它修改的时候,一般来说,block 只是将变量值复制过来的。而当我们用__block标记的时候,被标记__block的变量事实上应该说是被移动到了堆上。表示在 block 中的修改对于 block 外也是有效地。(ARC和非ARC是一样的)
下面总结一下block被copy后,它所引用的变量被复制到了堆中的情况,如下代码(非ARC下):
输出:
=== block copy前
&a = 0x7fff5fbff8bc, &b = 0x7fff5fbff8b0
=== Block
&a = 0x100201048, &b = 0x100201068
a = 123, b = 456
=== block copy后
&a = 0x7fff5fbff8bc, &b = 0x100201068
a = 123, b = 456
可以看到,在block执行中,他所引用的变量a和b都被复制到了堆上。因此,当block执行后,函数栈内访问被__block标记的b的时候,b的地址会变成堆中的地址。而变量a,仍会指向函数栈内原有的变量a的空间。
循环引用
只要对象没有强指针指向它,它就会被释放,最后被销毁。
前面说过,使用block不管在ARC还是非ARC ,都必须使用copy将block复制到堆中。但block在copy时都会对block内部用到的对象进行强引用(ARC)或者retainCount增1(非ARC)。在ARC与非ARC环境下对block使用不当都会引起循环引用问题。
一般表现为:某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身。如下图:
block的这种循环引用会被编译器捕捉到并及时提醒。
网上大部分帖子都表述为"block里面引用了self导致循环引用",但事实真的是如此吗?其实这种说法是不严谨的,不一定要显式地出现"self"字眼才会引起循环引用。我们改一下代码,不通过属性self去访问变量,而是通过实例变量_去访问,如下:
很明显了:
即使在你的block代码中没有显式地出现"self",也会出现循环引用!只要你在block里用到了self所拥有的东西!就有可能引发循环引用。
循环引用使得两者都无法释放,导致内存泄露。解决思想:可以将其中一个声明为弱指针,这样就可以避免循环引用问题。
ARC
非ARC
__block和__weak修饰符的区别其实是挺明显的:
1.__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
2.__weak只能在ARC模式下使用,也只能修饰对象(如:NSString),不能修饰基本数据类型(如:int)。
3.__block对象可以在block中被重新赋值,__weak不可以。
PS:__unsafe_unretained修饰符可以被视为iOSSDK 4.3以前版本的__weak的替代品,不过不会被自动置空为nil。所以尽可能不要使用这个修饰符。
看下面的例子(ARC):
定义一个HXTool工具类,如下:
在控制器view中,ViewController.m
上面写法对应ViewController和HXTool的关系:
每个对象都有强指针引用着,谁也释放不了,导致循环引用。
将self声明为__blockViewController *vc = self;或者 __weak ViewController *vc = self;则关系变成下图:
(上面2张图中实线代表强引用,虚线代表弱引用)
由于self被声明为弱引用,block不会再对它进行强制引用,当不在使用self的时候,由于没有强引用引用它,所有它可以被释放掉,这样就不会循环引用。
retain cycle问题的根源在于Block和obj可能会互相强引用,互相retain对方,这样就导致了retaincycle,最后这个Block和obj就变成了孤岛,谁也释放不了谁。
解决:将其中一个声明为弱指针即可。
根据Block在内存中的位置分为三种类型NSGlobalBlock,NSStackBlock, NSMallocBlock。(知道就行)
<1>NSGlobalBlock:类似函数,位于text段;
<2>NSStackBlock:位于栈内存,函数返回后Block将无效;
<3>NSMallocBlock:位于堆内存。
使用内部变量在ARC和非ARC条件下:
对于没有引用外部变量的Block,无论在ARC还是非ARC下,类型都是__NSGlobalBlock__,这种类型的block可以理解成一种全局的block,不需要考虑作用域问题。同时,对block进行Copy或者Retain操作也是无效的,
typedef int(^MyBlock)(); MyBlock func() { return ^{ return 123; }; } // 在非ARC下执行如下代码: - (void)test { MyBlock block = func(); NSLog(@"%d", block()); NSLog(@"%@", [block class]); MyBlock block2 = [block copy]; //Copy操作对__NSGlobalBlock__类型无效 NSLog(@"%d", block == block2); }
输出:
123
__NSGlobalBlock__
1
可以看到,copy后的block和原来是同一个对象的。
使用外部变量在ARC条件下:
对于引用了外部变量的block,如果没有对他进行copy,它的作用域只会在声明它的函数栈内(类型是__NSStackBlock__),如果在ARC下直接返回此类block是正确的,因为在ARC下,系统会自动添加copy,如下:
typedef int(^MyBlock)(); MyBlock func() { int i = 123; return ^{ return i; };// 此处block使用了外部变量i } // 在非ARC下执行如下代码: void test() { MyBlock block = func(); NSLog(@"%d", block()); NSLog(@"%@", [block class]); }
输出:
123
__NSMallocBlock__
类型是__NSMallocBlock__,说明block已经被copy到了堆中了。
使用外部变量在非ARC条件下:
block内使用外部变量,在非ARC下编译会直接报错(Xcode提示Returningblock that lives on the local stack),因为此时block还在栈中,不能执行retain操作。
当然其实在非ARC下,也可以使上面的情况编译通过,如下
typedef int(^MyBlock)(); MyBlock func() { int i = 123; //非ARC下不要这样!!! MyBlock ret = ^{ return i; }; return ret; }
我们把原来的返回值赋给一个变量,然后再返回这个变量,就可以编译通过了。不过虽然编译通过了,这个返回的block作用域仍是在函数栈中的,因此一旦函数运行完毕后再使用这个block很可能会引发BAD_ACCESS错误。
所以在非ARC下,必须把block复制到堆中才可以在函数外使用,或使用外部变量,如下:
typedef int(^MyBlock)(); MyBlock func() { //非ARC int i = 123; return [^{ return i; } copy]; }
block修改外部变量的值
block使用外部变量,并不对它修改的时候,一般来说,block 只是将变量值复制过来的。而当我们用__block标记的时候,被标记__block的变量事实上应该说是被移动到了堆上。表示在 block 中的修改对于 block 外也是有效地。(ARC和非ARC是一样的)
下面总结一下block被copy后,它所引用的变量被复制到了堆中的情况,如下代码(非ARC下):
//非ARC void func() { int a = 123; __block int b = 123; NSLog(@"%@", @"=== block copy前"); NSLog(@"&a = %p, &b = %p", &a, &b); void(^block)() = ^{ NSLog(@"%@", @"=== Block"); NSLog(@"&a = %p, &b = %p", &a, &b); NSLog(@"a = %d, b = %d", a, b = 456); }; block = [block copy]; block(); NSLog(@"%@", @"=== block copy后"); NSLog(@"&a = %p, &b = %p", &a, &b); NSLog(@"a = %d, b = %d", a, b); [block release]; }
输出:
=== block copy前
&a = 0x7fff5fbff8bc, &b = 0x7fff5fbff8b0
=== Block
&a = 0x100201048, &b = 0x100201068
a = 123, b = 456
=== block copy后
&a = 0x7fff5fbff8bc, &b = 0x100201068
a = 123, b = 456
可以看到,在block执行中,他所引用的变量a和b都被复制到了堆上。因此,当block执行后,函数栈内访问被__block标记的b的时候,b的地址会变成堆中的地址。而变量a,仍会指向函数栈内原有的变量a的空间。
循环引用
只要对象没有强指针指向它,它就会被释放,最后被销毁。
前面说过,使用block不管在ARC还是非ARC ,都必须使用copy将block复制到堆中。但block在copy时都会对block内部用到的对象进行强引用(ARC)或者retainCount增1(非ARC)。在ARC与非ARC环境下对block使用不当都会引起循环引用问题。
一般表现为:某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身。如下图:
block的这种循环引用会被编译器捕捉到并及时提醒。
网上大部分帖子都表述为"block里面引用了self导致循环引用",但事实真的是如此吗?其实这种说法是不严谨的,不一定要显式地出现"self"字眼才会引起循环引用。我们改一下代码,不通过属性self去访问变量,而是通过实例变量_去访问,如下:
很明显了:
即使在你的block代码中没有显式地出现"self",也会出现循环引用!只要你在block里用到了self所拥有的东西!就有可能引发循环引用。
循环引用使得两者都无法释放,导致内存泄露。解决思想:可以将其中一个声明为弱指针,这样就可以避免循环引用问题。
ARC
__weak typeof(self) weakSelf=self; // 其实 __weak someClass *weakSelf = self也是OK的!!! __block typeof(self) blockSelf=self; // 其实 __block someClass*blockSelf = self也是OK的!!!
非ARC
__block typeof(self) blockSelf=self; // 其实 __block someClass *blockSelf = self也是OK的!!!
__block和__weak修饰符的区别其实是挺明显的:
1.__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
2.__weak只能在ARC模式下使用,也只能修饰对象(如:NSString),不能修饰基本数据类型(如:int)。
3.__block对象可以在block中被重新赋值,__weak不可以。
PS:__unsafe_unretained修饰符可以被视为iOSSDK 4.3以前版本的__weak的替代品,不过不会被自动置空为nil。所以尽可能不要使用这个修饰符。
看下面的例子(ARC):
定义一个HXTool工具类,如下:
#import <Foundation/Foundation.h> typedef void (^blockOperation)(); @interface HXTool : NSObject @property (nonatomic, copy) blockOperation operation;// 执行打印操作 @end
在控制器view中,ViewController.m
#import "ViewController.h" #import "HXTool.h" @interface ViewController () @property (nonatomic, strong) HXTool *tool; @property (nonatomic, assign) int age; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; HXTool *tool = [[HXTool alloc] init]; self.tool = tool; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { self.tool.operation = ^(){ self.age ++; NSLog(@"age = %d", self.age); }; self.tool.operation(); } @end
上面写法对应ViewController和HXTool的关系:
每个对象都有强指针引用着,谁也释放不了,导致循环引用。
将self声明为__blockViewController *vc = self;或者 __weak ViewController *vc = self;则关系变成下图:
(上面2张图中实线代表强引用,虚线代表弱引用)
由于self被声明为弱引用,block不会再对它进行强制引用,当不在使用self的时候,由于没有强引用引用它,所有它可以被释放掉,这样就不会循环引用。
retain cycle问题的根源在于Block和obj可能会互相强引用,互相retain对方,这样就导致了retaincycle,最后这个Block和obj就变成了孤岛,谁也释放不了谁。
解决:将其中一个声明为弱指针即可。
相关文章推荐
- IOS:两种回调的方式实现(delegate和block)
- iOS常用的公共方法详解
- iOS计算上次日期距离现在多久的代码
- mono在ios平台的局限性
- nagios安装
- ios开发创建证书和描述文件的整个流程
- ios 里如何判断当前应用的定位服务是否可用
- iOS自动布局实现Cell和Tableview高度自适应(SDAutoLayout)
- <iOS>git-起步
- iOS 控件
- 学习iOS的见解
- ios-kvc\kvo 用法
- iOS-初级数据持久化
- IOS开发copy,nonatomic, retain,weak,strong用法
- 自用cocoapods命令整合
- iOS第三方开源库
- ios属性修饰符总结
- iOS9无法安装企业版的解决办法
- 纪念,IOS第一次找工作!
- iOS 简单实现重新获取验证码并倒计时60秒