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 ...
随机推荐
- Java基础-四要素之一《多态》
什么是多态 指允许不同类的对象对同一消息做出响应.即同一消息可以根据发送对象的不同而采用多种不同的行为方式.(发送消息就是函数调用) 多态是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的 ...
- 高斯混合聚类及EM实现
一.引言 我们谈到了用 k-means 进行聚类的方法,这次我们来说一下另一个很流行的算法:Gaussian Mixture Model (GMM).事实上,GMM 和 k-means 很像,不过 G ...
- [IOS+PHP Jason格式的发送与解析]
服务器端PHP文件connect.php: <?php $q = mysql_connect("localhost","root","" ...
- groovy-输入输出
Groovy为I/O提供了一系列的helper methods ,所有的这些方法都适用于标准的 Java Reader/Writer ,InputStream/OutputStream 和File 以 ...
- UVA 1398 Meteor
传送门 Solution: 记一颗流星在视野内的时间段为(L, R), 为了使所有(L, R)都取整数,首先将坐标放大. 放大倍数可取为 LCM(1, 2, ..., 10)= 2520 接着计算:从 ...
- HDU 1060 Left-most Digit
传送门 Leftmost Digit Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Other ...
- Git Pull 避免用户名和密码方法
在开发中使用的版本控制器时git , 每次使用命令"git pull"从服务器获得最新代码时,都需要输入用户名和密码,这样浪费了大量的时间和热情,在此背景下,本文在网上找到解决版本 ...
- Graphtree--zabbix增强功能(一屏展示所有内容)
Graphtree--zabbix增强功能 Graphtree由OneOaaS开发并开源出来. 功能 集中展示所有分组设备 集中展示一个分组图像 集中展示一个设备图像 展示设备下的Applicatio ...
- WIN 2003服务器终极安全及问题解决方案
一.硬盘分区与操 作系统的安装硬盘分区 总的来讲在硬盘分区上面没什么值得深入剖析的地方,无非就是一个在分区前做好规划知道要去放些什么东西, 如果实在不知 道.那就只一个硬盘只分一个区,分区要一次性完成 ...
- exe4j中"this executable was created with an evaluation错误解决方法
在使用exe4j时,如果您的exe4j没有注册,在运行有exe4j转换的*.jar为*.exe的可执行文件是会提示:"this executable was created with an ...