现在在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的更多相关文章

  1. iOS中使用block进行网络请求回调

    iOS中使用block进行网络请求回调 HttpRequest.h // // HttpRequest.h // UseBlockCallBack // // Created by Michael o ...

  2. IOS中的Block与C++11中的lambda

    ios中的block 可以说是一种函数指针,但更确切的讲,其实际上其应该算是object-c对C++11中lambda的支持或者说是一个语言上的变体,其实际内容是一样的,C++的lambda我已经有简 ...

  3. iOS中为什么block用copy属性

    1. Block的声明和线程安全Block属性的声明,首先需要用copy修饰符,因为只有copy后的Block才会在堆中,栈中的Block的生命周期是和栈绑定的,可以参考之前的文章(iOS: 非ARC ...

  4. iOS 中的 block 是如何持有对象的

    Block 是 Objective-C 中笔者最喜欢的特性,它为 Objective-C 这门语言提供了强大的函数式编程能力,而最近苹果推出的很多新的 API 都已经开始原生的支持 block 语法, ...

  5. iOS开发——高级篇——iOS中为什么block用copy属性

    1. Block的声明和线程安全Block属性的声明,首先需要用copy修饰符,因为只有copy后的Block才会在堆中,栈中的Block的生命周期是和栈绑定的,可以参考之前的文章(iOS: 非ARC ...

  6. ios 中的block应用

    在这个大冬天里默默敲着键盘,勿喷.今天学习swift过程中,学习到闭包,发现闭包和oc的block中有很多的相同之处,又重新学习了一下并且学习了一些高级点的用法,内容如下: 1.block格式说明:( ...

  7. iOS 中的block异常

    转自:iOS 知识小集 我们在调用block时,如果这个block为nil,则程序会崩溃,报类似于EXC_BAD_ACCESS(code=1, address=0xc)异常[32位下的结果,如果是64 ...

  8. 关于查找iOS中App路径时所要注意的一个问题

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交 ...

  9. iOS 中的block异常 判断block是否为空

    我们在调用block时,如果这个block为nil,则程序会崩溃,报类似于EXC_BAD_ACCESS(code=1, address=0xc)异常[32位下的结果,如果是64位,则address=0 ...

随机推荐

  1. oracle使用存储过程实现日志记录.sql

    --这段sql语句是用来实现oracle后台记录操作日志的,代替或者补充应用系统的操作日志. --1.对应的日志记录表----------------------------------------- ...

  2. Ubuntu安装VMware Tools的方法

    最后我将提供一个12版本的VMware Tools集合,包括了linux.iso. 背景: VMware Tools是VMware虚拟机中自带的一种增强工具,相当于VirtualBox中的增强功能(S ...

  3. easyVS

    easyVS 详细说明点这里

  4. GCC、Makefile编程学习

    相关学习资料 http://gcc.gnu.org/ https://gcc.gnu.org/onlinedocs/ http://zh.wikipedia.org/zh/GCC http://blo ...

  5. POJ1703Find them, Catch them

    Find them, Catch them Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 37722   Accepted: ...

  6. JavaScript中call、apply、bind、slice的使用

    1.参考资料 http://www.cnblogs.com/coco1s/p/4833199.html   2.归结如下 apply . call .bind 三者都是用来改变函数的this对象的指向 ...

  7. [LeetCode] Best Time to Buy and Sell Stock III

    将Best Time to Buy and Sell Stock的如下思路用到此题目 思路1:第i天买入,能赚到的最大利润是多少呢?就是i + 1 ~ n天中最大的股价减去第i天的. 思路2:第i天买 ...

  8. mysql 视图(view)

    什么是视图 视图是从一个或多个表中导出来的表,是一种虚拟存在的表. 视图就像一个窗口,通过这个窗口可以看到系统专门提供的数据. 这样,用户可以不用看到整个数据库中的数据,而之关心对自己有用的数据. 数 ...

  9. 获取JDBC中的ResultSet的记录的条数

    方法一:利用ResultSet的getRow方法来获得ResultSet的总行数 Java代码 ResultSet rs; rs.last(); //移到最后一行 int rowCount = rs. ...

  10. sql server 复制需要有实际的服务器名称才能连接到服务器(转载)

    今天在做sql server 2005 复制的时候,提示复制需要有实际的服务器名称才能连接到服务器……的消息,一开始不知道什么意思!后来在网上查了一下才知道,原来是以前我把机器改过名 字.用selec ...