Effective Objective-C 2.0: Item 32: Beware of Memory Management with Exception-Safe Code

本文讨论了C++中使用异常处理时可能导致的内存泄漏问题,特别是手动引用计数环境中,在try-catch块中创建的对象未正确释放时的问题。文章提供了使用@finally块解决此类问题的方法,并讨论了ARC(Automatic Reference Counting)环境下处理异常的安全性及其默认配置。最后建议在频繁捕获异常的情况下,考虑使用类似NSError的错误传递方式。
摘要由CSDN通过智能技术生成

Item 32: Beware of Memory Management with Exception-Safe Code

Exceptions are a language feature offered by many modern languages. Exceptions do not exist in pure C but do in both C++ and Objective-C. In fact, in the modern runtime, C++ and Objective-C exceptions are compatible, meaning that an exception thrown from one language can be caught using a handler from the other language.

Even though the error model of Objective-C (see Item 21) states that you should use exceptions only for fatal errors, you may still need code that catches and handles them. Examples are Objective-C++ code or code that interfaces with a third-party library such that you have no control over the exceptions being thrown. Also, some system libraries still make use of exceptions, harking back to the days when exceptions were in common use. For example, Key-Value Observing (KVO) will throw an exception if you attempt to unregister an observer that was not already registered.

When it comes to memory management, exceptions introduce an interesting problem. Inside atry block, if an object is retained and then an exception is thrown before the object has been released, the object will leak unless this case is handled in the catch block. C++ destructors are run by the Objective-C exception-handling routines. This is important for C++ because any object whose lifetime has been cut short by a thrown exception needs to be destructed; otherwise, the memory it uses will be leaked, not to mention all the other system resources, such as file handles, that may not be cleaned up properly.

The destruction of objects automatically by the exception-handling routines is something that is trickier to do in an environment of manual reference counting. Consider the following Objective-C code, which uses manual reference counting:

@try {
    EOCSomeClass *object = [[EOCSomeClass allocinit];
    [object doSomethingThatMayThrow];
    [object release];
}
@catch (...) {
    NSLog(@"Whoops, there was an error. Oh well...");
}

At first glance, this may seem correct. But what happens if doSomethingThatMayThrow throws an exception? The release on the following line would not be run, because the exception would halt the execution and jump to the catch block. So in this scenario, the object would be leaked if an exception were thrown. That’s not ideal. The way this is solved is to use the @finally block, which is guaranteed to be run once and only once, whether or not an exception is thrown. For example, the code could be transformed into this:

EOCSomeClass *object;
@try {
    object = [[EOCSomeClass allocinit];
    [object doSomethingThatMayThrow];
}
@catch (...) {
    NSLog(@"Whoops, there was an error. Oh well...");
}
@finally {
    [object release];
}

Note how the object has had to be pulled out of the @try block because it needs to be referenced in the @finally block. This can get very tedious if you have to do this for all objects that need releasing. Also, if the logic is more complex than this, with multiple statements within the @tryblock, it can be easy to overlook the scenario in which an object might potentially leak. If the object that is leaked is a scarce resource (or manages one), such as a file descriptor or database connection, the leak is potentially disastrous because eventually, the application could end up unnecessarily holding onto all of a system’s resources.

With ARC, the situation is even more serious. The equivalent ARC code for the original code is this:

@try {
    EOCSomeClass *object = [[EOCSomeClass allocinit];
    [object doSomethingThatMayThrow];
}
@catch (...) {
    NSLog(@"Whoops, there was an error. Oh well...");
}

Now it is even more of a problem; you can’t use the trick of putting the release in the @finallyblock, as it is illegal to call release. But surely ARC handles this situation, you are probably thinking. Well, by default, it does not; to do so requires a large amount of boilerplate code to be added to track the objects that potentially need cleaning up if an exception is thrown. This code can severely decrease performance at runtime even when no exceptions are thrown. This code also increases the size of an application significantly with all the extra code that has to be added. These side effects are not ideal.

Although it is not turned on by default, ARC does support emitting this extra code to handle exceptions safely. The code can be turned on by using the compiler flag -fobjc-arc-exceptions. The rationale behind not turning it on by default is that in Objective-C programming, exceptions should be used only when the application will terminate as a result of that exception being thrown (see Item 21). Therefore, if the application is going to terminate anyway, the potential memory leak is irrelevant. There is no point adding the required code to become exception safe if the application is going to terminate.

The one scenario in which the -fobjc-arc-exceptions flag is turned on by default occurs when the compiler is in Objective-C++ mode. C++ already needs to have code similar to the code that ARC would implement, so the performance hit is not as great if ARC adds in its own code to ensure exception safety. Also, C++ makes heavy use of exceptions, so it’s likely that a developer using Objective-C++ would want to use exceptions.

If you are using manual reference counting and must catch exceptions, remember to ensure that code is written in such a way to clean up correctly. If you are using ARC and must catch exceptions, you will need to turn on the -fobjc-arc-exceptions flag. But above all, if you find yourself catching a lot of exceptions, consider refactoring to make use of NSError-style error passing instead, as explained in Item 21.

Things to Remember

Image When exceptions are caught, care should be taken to ensure that any required cleanup is done for objects created within the try block.

Image By default, ARC does not emit code that handles cleanup when exceptions are thrown. This can be enabled with a compiler flag but produces code that is larger and comes with a runtime cost.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值