MRC下block循环嵌套暴露的内存问题

 iOS4以后引入的block,一个比较方便且实用的功能。但是自己在开发的时候遇到了不少的坑,很多都是和内存管理相关的,后来iOS5.0以后有了ARC,有了__weak关键字,所以block使用也就更安全了。

  但是,吐槽下我们公司。对于像我们公司这种,还在支持iOS4.3,还在用MRC的来说,很多后来方便的框架和工具都不能用了。而那个坑还是那个坑。

  最近在看C++的东西,无意中又想起了这个坑,所以立即写了个Demo,应该算是一种思路。

  MRC中使用block,为了避免在block中retain外部的变量导致引用计数的增加,我们往往要在block中使用的变量前加上 __block 关键字,这样就不会retain了,比如__block typeof(self) weakSelf = self;

  但是这里又会带来另外一个问题,因为block很多时候是用在异步的情况下,如果这时候别的地方已经把self给释放掉了,那么那个weakSelf就变成了野指针,这个时候再在block中去调用weakSelf很有可能就崩溃掉了。这个坑在网络调用的时候害了我很多次。

  我尝试了很多方法一直都没有很好的效果,比如用一个static变量存储self指针,因为static变量在block内是不会retain的,它在整个包内(当前.m文件中)是共用的。但是一个类共享一个static变量,那么我创建很多个实例对象的时候这个方法就失效了。

  后来我想起了一个类似引用计数的方法,创建一个实例就+1,释放一个就-1,当总数 > 0 的时候才调用block。这样又有一个问题,就是假如是一个延时方法调用block,在这个延时的时间内我释放了所有的实例,然后又重新创建一遍,这个时候总数还是 > 0,block会执行下去,但是原先的实例已经释放掉了,所以还是会崩溃。

  再后来,我网上查了下4.3下__weak关键字替代方案,果真有,github上有个专门的类模拟__weak效果。具体叫什么我忘了。但是这里我没用,因为我觉得我只是用在block上,而不是替代__weak。所以还是想个简单点的方法。

  当然很多时候没必要想那么复杂。既然不想retain,那么我使用它的指针就行了,那么我这里想到的就是用一个全局的NSMutbleSet去存储所有存活的需要用在block中的对象的指针。 举个例子,用到BLObject对象的话, 我们init的时候 把self的指针add到Set中,dealloc的时候从Set中remove掉,判断指针是不是有效只要判断Set有没有这个指针就行了,这里我把指针变量转换成NSValue便于存储。 初步证明还是有效的。

  这两天再学习C++,看到realloc的时候,想仔细研究下这个C函数,恰恰也时因为这个受到一定启发。其实每一个平台动态内存分配函数的底层实现是不一样的,但是逻辑应该是一样,realloc的时候,当原来的连续内存不够存储要扩展空间的时候,那么会再去开辟一块更大的连续存储空间,然后把原先数据拷贝到新开辟的内存上去,但是这个函数是怎么知道需要拷贝多少的呢?同样的它也只是对指针的操作而已。

  也就是说,我们分配内存的时候(比如malloc,alloc或是new)的时候,系统一定会记录这块内存的一些信息,以便于后续系统对内存进行调度。当然我们free掉内存的时候,关于这个区域信息也会被复位,这块区域就恢复自由了。也就是说我们应该通过某个函数,获取一个指针指向内存的使用信息,如果信息是空白的,那么指针就是悬垂指针。

  抱着试试看的态度,我发现<malloc/malloc.h>中包含内存分配的一些函数和结构体,简单的说通过malloc_zone_from_ptr函数,可以获取这个指针是否已经分配空间,如果是NULL的话,说明指针并没有指向任何可用区域,那么就不因该使用它去调用任何方法。

  通过内核函数判断,只是个人的一个推断,Demo中是有效的,但是具体的我还没找到其他任何Demo有这个使用的,但是这个函数调用的是一个inline函数,所以应该还是很快的。

  Demo如何证明?我们present到第二个viewController的时候,点击Test,这个时候会触发延时方法,如果在延时时间内(Demo中是2秒)你dismiss的话第二个viewController已经释放了,这个block执行的时候再调用外部的变量就有可能会崩溃掉。但是这里我们检查了原先的指针是不是有效,失效就立即返回,不会崩溃。



_weak typeof(self) weakSelf = self; 
(一)内存管理原则 
1、默认strong,可选weak。strong下不管成员变量还是property,每次使用指针指向一个对象,等于自动调用retain(), 并对旧对象调用release(),所以设为nil等于release。 
2、只要某个对象被任一strong指针指向,那么它将不会被销毁,否则立即释放,不用等runloop结束。所有strong指针变量不需要在dealloc中手动设为nil,ios会自动处理,debug可以看到全部被置为nil,最先声明的变量最后调用dealloc释放。

3、官方建议IBOutlet加上__weak,实际上不用加也会自动释放;

4、优先使用私有成员变量,除非需要公开属性才用property。

5、避免循环引用,否则手动设置nil释放。

6、block方法常用声明:@property (copy) void(^MyBlock)(void); 如果超出当前作用域之后仍然继续使用block,那么最好使用copy关键字,拷贝到堆区,防止栈区变量销毁。

7、创建block匿名函数之前一般需要对self进行weak化,否则造成循环引用无法释放controller:

 __weak MyController *weakSelf = self 或者 __weak __typeof(self) weakSelf = self;
执行block方法体的时候也可以转换为强引用之后再使用:MyController* strongSelf = weakSelf; if (!strongSelf) { return; }

(一)typeof关键字是C语言中的一个新扩展,这个特性在linux内核中应用非常广泛。

一,说明 
typeof的参数可以是两种形式:表达式或类型。

1,表达式的的例子:
    typeof(x[0](1)
    这里假设x是一个函数指针数组,这样就可以得到这个函数返回值的类型了。
    如果将typeof用于表达式,则该表达式不会执行。只会得到该表达式的类型。
    以下示例声明了int类型的var变量,因为表达式foo()是int类型的。由于表达式不会被执行,所以不会调用foo函数。
        extern int foo();
        typeof(foo()) var;

2,参数的例子:
    typeof(int *) a,b;
        等价于:
        int *a,*b;

二,实例 
1,把y定义成x指向的数据类型: 
typeof(*x) y; 
2,把y定义成x指向数据类型的数组: 
typeof(*x) y[4]; 
3,把y定义成一个字符指针数组: 
typeof(typeof(char *)[4] y; 
这与下面的定义等价: 
char *y[4];

4,typeof(int *) p1,p2; /* Declares two int pointers p1, p2 */
        int *p1, *p2;

5,typeof(int) *p3,p4;/* Declares int pointer p3 and int p4 */
        int *p3, p4;

6,typeof(int [10]) a1, a2;/* Declares two arrays of integers */
        int a1[10], a2[10];

三,局限 
typeof构造中的类型名不能包含存储类说明符,如extern或static。不过允许包含类型限定符,如const或volatile。 
例如,下列代码是无效的,因为它在typeof构造中声明了extern: 
typeof(extern int) a;


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值