iOS 中使用Block时需要注意的retain circle
现在在ios中,block是越来越多了。自己在类中定义block对象时,需要注意block对象的使用方法,防止产生retain circle,导致内存泄露。
现在分析一下产生retain circle的原因
比如我建立了Tools类,之后 建立了一个strong 类型的 block指针 callbackBlock,作为回掉函数使用。
之后在A类中,建立了Tools类的一个对象,并用Strong指针 aTool指向这个对象,再将它的callbackBlock指针赋值,也就是写出具体的block实现。
如果我在block的实现中用到了 self 这个指针,那么会导致retain circle。
形成的circle 如下图

解决方法:不使用self指针,使用A对象的weak引用,如果必须使用self指针,那么可以再block结束处加上 aTool = nil,用来打断retain circle。
除了这种比较明显的循环引用外,还有一些不明显的,比如以下代码
[[NSNotificationCenter defaultCenter] addObserverForName:@"UIWindowDidRotateNotification" object:nil queue:nil usingBlock:^(NSNotification *note) {
if ([note.userInfo[@"UIWindowOldOrientationUserInfoKey"] intValue] >= ) {
self.navigationController.navigationBar.frame = (CGRect){, , self.view.frame.size.width, };
}
}];
这是一段在 view controller 中的代码,由于block中使用了self指针,导致这个view controller无法被释放。由于调用的是系统函数,我们并不知道为何形成了循环引用,但是这个现象给了我们一个提示:在对象内部使用block时,一定要使用 weak self 指针!
另外,即使不直接使用self,而是使用self中定义的变量也不行!(ios8,ios9,测试了,不行,但是我记得这种情况以前不会有问题啊???难道是ios7?)
比如一下代码
@interface HubPreviewViewController (){
NSURL *webViewURL;
}
调用方法如下:
[[NSNotificationCenter defaultCenter] addObserverForName:@"UIWindowDidRotateNotification" object:nil queue:nil usingBlock:^(NSNotification *note) {
NSLog(@"webview url is %@", [weakSelf valueForKey:@"webViewURL"]);//一切正常,没有循环引用
NSLog(@"webview url is %@", webViewURL); //会造成循环引用 ,为什么呢?????没有self的调用啊??
}];
我猜测一个对象内调用的block的所有权都在这个对象上,比如上面这个例子,虽然引用block时是这样写的
^(NSNotification *note) {
if ([note.userInfo[@"UIWindowOldOrientationUserInfoKey"] intValue] >= ) {
self.navigationController.navigationBar.frame = (CGRect){, , self.view.frame.size.width, };
}
}
没有看到明显的strong 类型的 block对象指针,但是实际上,系统隐含地为这个block在 view controller对象中创建了一个 strong 类型指针,用来保存具体内容,这个block 的所有权和 上面代码中的NSNotificationCenter无关,所以从notification center 里是否移除 self 这个observer 都对block的释放没有影响。
这里推荐一篇关于block的专业文章,http://blog.csdn.net/jasonblog/article/details/7756763
阅读后,开始测试,先贴上我的测试文件:
#import <Foundation/Foundation.h>
@interface Person : NSObject{
NSURL *Testurl;
}
@end
@implementation Person
- (void)test{
[[NSNotificationCenter defaultCenter] addObserverForName:@"test" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
NSURL * innerUrl = [NSURL URLWithString:@"test"];
}];
}
- (void)dealloc{
[super dealloc];
NSLog(@"dealloc........");
}
@end
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, World!\n");
Person *person = [[Person alloc] init];
[person test];
[person release];
return ;
}
这个是person对象可以正常释放的版本,由于clang编译器的一些原因,这里没有用ARC。
在控制台输入编译命令:
clang -framework Foundation test.m -o test
得到可执行2进制文件:

拖入控制台执行:

再执行一条命令,生成可以被我们研究的cpp文件:
clang -rewrite-objc test.m
没有引用循环的版本完成了,再改写person对象无法正确释放的代码:
#import <Foundation/Foundation.h>
@interface Person : NSObject{
NSURL *Testurl;
}
@end
@implementation Person
- (void)test{
[[NSNotificationCenter defaultCenter] addObserverForName:@"test" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
Testurl = [NSURL URLWithString:@"test"];
}];
}
- (void)dealloc{
[super dealloc];
NSLog(@"dealloc........");
}
@end
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, World!\n");
Person *person = [[Person alloc] init];
[person test];
[person release];
return ;
}
同样经过刚才的2个步奏,但是这次的控制台没有输出 dealloc 。
为了探究原因,我们看2次生成的cpp有什么不同。
没有问题的版本:
struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int flags=) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
有问题的版本:
struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
Person *self;
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
而且,有问题的版本还会多2个函数:
static void __Person__test_block_copy_0(struct __Person__test_block_impl_0*dst, struct __Person__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, /*BLOCK_FIELD_IS_OBJECT*/);}
static void __Person__test_block_dispose_0(struct __Person__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, /*BLOCK_FIELD_IS_OBJECT*/);}
这里的self,正是Person对象,推测这2个函数就是为了释放block对person的“强引用”而存在的。可见,有问题的版本的中的block的确对person对象进行了所谓的强引用,那么又是谁对这个block进行了强引用呢?我在代码中没有找到对应的指针,后来发现是notification center:
The block to be executed when the notification is received. The block is copied by the notification center and (the copy) held until the observer registration is removed. To unregister observations, you pass the object returned by this method to removeObserver:. You must invoke removeObserver: or removeObserver:name:object: before any object specified by addObserverForName:object:queue:usingBlock: is deallocated.
又这个提示我又写了改写了代码:
#import <Foundation/Foundation.h>
@interface Person : NSObject{
NSURL *Testurl;
}
@end
@implementation Person
- (id)test{
void (^abcdTestBolck) (NSNotification * _Nonnull note) = ^ void (NSNotification * _Nonnull note){
Testurl = [NSURL URLWithString:@"test"];
};
id returnValue = [[NSNotificationCenter defaultCenter] addObserverForName:@"test" object:nil queue:nil usingBlock:abcdTestBolck];
return returnValue;
}
- (void)dealloc{
[super dealloc];
NSLog(@"dealloc........");
}
@end
int main(int argc, const char * argv[]) {
// insert code here...
printf("Hello, World!\n");
Person *person = [[Person alloc] init];
id returnedByNoti = [person test];
[[NSNotificationCenter defaultCenter] removeObserver:returnedByNoti];
[person release];
return ;
}
上面这段代码的 person对象可以正确释放!因为把 block从 notification center 中移除了,所以 person对象的强引用也就没了!
由此我们看出,block中使用 self中的属性变量,会导致block对self的强引用,因此需要注意block的释放时机,不要导致self无法被释放!
iOS 中使用Block时需要注意的retain circle的更多相关文章
- iOS中使用block进行网络请求回调
iOS中使用block进行网络请求回调 HttpRequest.h // // HttpRequest.h // UseBlockCallBack // // Created by Michael o ...
- IOS中的Block与C++11中的lambda
ios中的block 可以说是一种函数指针,但更确切的讲,其实际上其应该算是object-c对C++11中lambda的支持或者说是一个语言上的变体,其实际内容是一样的,C++的lambda我已经有简 ...
- iOS中为什么block用copy属性
1. Block的声明和线程安全Block属性的声明,首先需要用copy修饰符,因为只有copy后的Block才会在堆中,栈中的Block的生命周期是和栈绑定的,可以参考之前的文章(iOS: 非ARC ...
- iOS 中的 block 是如何持有对象的
Block 是 Objective-C 中笔者最喜欢的特性,它为 Objective-C 这门语言提供了强大的函数式编程能力,而最近苹果推出的很多新的 API 都已经开始原生的支持 block 语法, ...
- iOS开发——高级篇——iOS中为什么block用copy属性
1. Block的声明和线程安全Block属性的声明,首先需要用copy修饰符,因为只有copy后的Block才会在堆中,栈中的Block的生命周期是和栈绑定的,可以参考之前的文章(iOS: 非ARC ...
- ios 中的block应用
在这个大冬天里默默敲着键盘,勿喷.今天学习swift过程中,学习到闭包,发现闭包和oc的block中有很多的相同之处,又重新学习了一下并且学习了一些高级点的用法,内容如下: 1.block格式说明:( ...
- iOS 中的block异常
转自:iOS 知识小集 我们在调用block时,如果这个block为nil,则程序会崩溃,报类似于EXC_BAD_ACCESS(code=1, address=0xc)异常[32位下的结果,如果是64 ...
- 关于查找iOS中App路径时所要注意的一个问题
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交 ...
- iOS 中的block异常 判断block是否为空
我们在调用block时,如果这个block为nil,则程序会崩溃,报类似于EXC_BAD_ACCESS(code=1, address=0xc)异常[32位下的结果,如果是64位,则address=0 ...
随机推荐
- BZOJ-3227 红黑树(tree) 树形DP
个人认为比较好的(高端)树形DP,也有可能是人傻 3227: [Sdoi2008]红黑树(tree) Time Limit: 10 Sec Memory Limit: 128 MB Submit: 1 ...
- h5页面,改变数字默认颜色
最近遇到一个非常变态的bug,有一串数字,我设置color为白色,在pc端浏览器,无变化,但是到了手机端,会由白色跳成黑色,我无解啊... 刚刚找到方法,如下: <meta name=" ...
- poj 3463 最短路与次短路&&统计个数
题意:求最短路和比最短路长度多1的次短路的个数 本来想图(有)方(模)便(版)用spfa的,结果妹纸要我看看dijkstra怎么解.... 写了三遍orz Ver1.0:堆优化+邻接表,WA //不能 ...
- py替换掉换行符
for line in file.readlines(): line=line.strip('\n')
- python获取命令行参数的方法
想用python处理一下文件,发现有argv这个用法,搜来学习一下. 如果想对python脚步传参数,那么就需要命令行参数的支持了,这样可以省的每次去改脚步了. 用法是:python xx.py ...
- 5个最好的Python Web开发框架
Python是最受欢迎的和最有效率的开发语言之一.Python能让你更快完成工作,并且更有效地集成系统.Python是动态的面向对象的语言.即便你刚刚开始学习Python,也立即就能获得生产力上的提升 ...
- linux 7 常见命令
修改网卡配置文件,如下:ONBOOT=yesIPADDR=192.168.1.11NETMASK=255.255.255.0NM_CONTROLLED=no重启网卡:systemctl restart ...
- POJ2299Ultra-QuickSort(归并排序 + 树状数组求逆序对)
树状数组求逆序对 转载http://www.cnblogs.com/shenshuyang/archive/2012/07/14/2591859.html 转载: 树状数组,具体的说是 离散化+树 ...
- EasyUI queryParams属性 在请求远程数据同时给action方法传参
http://www.cnblogs.com/iack/p/3530500.html?utm_source=tuicool EasyUI queryParams属性 在请求远程数据同时给action方 ...
- Hibernate之多对多
一.项目结构如下图 二.保存学生和课程及其学生选课关系代码如下(测试类中不能再有双向关联,否则会报错,因为,都维护了中间表外键,会有中间表外键冲突,如果非要写双向关联,就需要配置中设置某一方维护主键, ...