ARC 下内存泄露的那些点

  1. block

解决循环引用

__weak typeof(self) weakSelf = self;
self.block = ^{
       [weakSelf print];
};

这样确实解决了循环引用,但考虑另一种情况。

__weak typeof(self) weakSelf = self;
self.block = ^{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:5];
        [weakSelf print];
    });
};

这时,虽然循环引用解决了,但是异步打印却没有。原因是异步任务执行的时候,执行self的弱引用已经被释放了。相当于向nil发送了print,虽然不会崩溃,但是也不会打印。
解决:

__weak typeof(self) weakSelf = self;
self.block = ^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       [NSThread sleepForTimeInterval:5];
        [strongSelf print];
   });
};

在block内添加一个指向self的强引用strongSelf,strongSelf是一个临时变量,在异步任务执行完之后会释放,这样既不会有提前释放,block也不会直接持有self。

最近发现一个很坑的 tableView的循环引用问题, 项目中应该很多时候碰到,为什么以前没注意呢?
注意!!!!!
我们使用tableView时,一般会把tableView设为属性,cell一般都会从重用池拿,tableView就会持有该cell,
若是cell中有block的回调,就要注意循环引用的问题了。
我以前一直以为tableView不会持有cell,只会注意自定义view的循环引用问题。

  1. NSTimer Repeat为YES时,会保留target。

  2. performSelector 系列
    performSelector 顾名思义即在运行时执行一个 selector,最简单的方法如下

- (id)performSelector:(SEL)selector;

这种调用 selector 的方法和直接调用 selector 基本等效,执行效果相同

[object methodName];
[object performSelector:@selector(methodName)];

但 performSelector 相比直接调用更加灵活

SEL selector;
if (/* some condition */) {
    selector = @selector(newObject);
} else if (/* some other condition */) {
    selector = @selector(copy);
} else {
    selector = @selector(someProperty);
}
id ret = [object performSelector:selector];

这段代码就相当于在动态之上再动态绑定。在 ARC 下编译这段代码,编译器会发出警告

warning: performSelector may cause a leak because its selector is unknow [-Warc-performSelector-leak]
正是由于动态,编译器不知道即将调用的 selector 是什么,不了解方法签名和返回值,甚至是否有返回值都不懂,所以编译器无法用 ARC 的内存管理规则来判断返回值是否应该释放。因此,ARC 采用了比较谨慎的做法,不添加释放操作,即在方法返回对象时就可能将其持有,从而可能导致内存泄露。

以本段代码为例,前两种情况(newObject, copy)都需要再次释放,而第三种情况不需要。这种泄露隐藏得如此之深,以至于使用 static analyzer 都很难检测到。如果把代码的最后一行改成

[object performSelector:selector];
不创建一个返回值变量测试分析,简直难以想象这里居然会出现内存问题。所以如果你使用的 selector 有返回值,一定要处理掉。
performSelector 的另一个可能造成内存泄露的地方在编译器对方法中传入的对象进行保留,performSelector关于内存管理的执行原理是这样的执行 [self performSelector:@selector(method1:) withObject:self.tableLayer afterDelay:3]; 的时候,系统会将tableLayer的引用计数加1,执行完这个方法时,还会将tableLayer的引用计数减1,而在我的游戏里这个延时执行函数是被多次调用的,有时切换场景时延时函数已经被调用但还没有执行,这时tableLayer的引用计数没有减少到0,也就导致了切换场景dealloc方法没有被调用,出现了内存泄露。

所以最后我的解决办法就是取消那些还没有来得及执行的延时函数,代码很简单:

[NSObject cancelPreviousPerformRequestsWithTarget:self]

当然你也可以一个一个得这样用:

[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(method1:) object:nil]

加上了这个以后,切换场景也就很顺利地执行了dealloc方法,至此问题解决!

最后最后总结:

SEL testSelector = @selector(test:);   
 if([tester respondsToSelector:testSelector])  
  {  
          [tester test:@"invoke test method"];  
  }

使用后,如果有必要,需要显示的调用cancelPreviousPerformRequestsWithTarget:selector:object: ,否则有可能产生内存泄露,而且这种内存泄露很难发现,因为它并不违反任何规则,所以一定要注意!

  1. try...catch
    Apple 提供了 [错误处理](NSError)和 [异常处理](NSException)两种机制,而 try...catch 就是使用 exception 捕获异常。NSError 应用在在绝大部分的场景下,并且这也是 Apple 所推荐。那什么时候用 NSException 呢?在极其严重的直接导致程序崩溃情况下才使用,并且无需考虑恢复问题。

那 try...catch 哪里会有内存泄露的隐患呢?我们先看 MRC 下的情况
// MRC 下的 try...catch
// 注意:在 @try @catch @finally 块内定义的变量都是局部变量

@try {
    EOCSomeClass *object = [[EOCSomeClass alloc] init];
    [object doSomethingMayThrowException];
    [object release];
}
@catch (NSException *exception) {
    NSLog(@"throw an exception: %@", exception.reason);
}

此处看似正常,但如果 doSomethingMayThrowException 方法抛出了异常,那么 object 对象就无法释放。如果 object 对象持有了重要且稀缺的资源,就可能会造成严重后果。

ARC 的情况会不会好点儿呢?其实更糟糕。我们以为 ARC 下,编译器会替我们做内存释放,其实不会,因为这样需要加入大量的样板代码来跟踪清理对象,从而在抛出异常时将其释放。即使这段代码即使不抛出异常,也会影响运行期的性能,而且增加进来的额外代码也会增加应用程序的体积,这些副作用都是很明显的。但另一方面,如果程序都崩溃了,回不回收内存又有什么意义呢?

所以可以总结下 try...catch 绝迹的原因:

try...catch 设计的目的是用来捕获程序崩溃的情况。
如果为了捕获异常,而在代码中添加 try...catch 和安全处理异常的代码,就会影响性能,增加应用体积。

你可能感兴趣的:(ARC 下内存泄露的那些点)