Objective-C内存管理注意事项

1.在delloc方法中只释放引用并解除监听

1.1在delloc方法中释放对象所拥有的引用并解除监听

(1)ARC会通过自动生成的.cxx_destruct方法在delloc中添加释放代码。对象所拥有的其他非Objective-C对象也要释放。比如CoreFoundation对象。
(2)在delloc要把原来配置过的观测行为都清理。如果用NSNotification给此对象订阅过某种通知,那么一般应该在注销,这样的话,通知系统就不再把通知发给回收后的对象了,若是还向其发送通知,则必然会令应用程序崩溃。

1.2开销较大或系统稀缺资源应该在delloc前释放:比如文件描述符、套接字、大块内存

原因如下:
(1)不能指望dealloc方法必定在某个特定的时机调用,因为有一些无法预料的东西可能也持有此对象。如果非要等到系统调用dealloc方法时才释放,那么保留这些稀缺资源的时间有些长了。
(2)系统并保证每个创建出来的对象的dealloc都会执行。极个别情况下,当应用程序终止时,仍有对象处于存活状态,这些对象没有收到dealloc消息。

通常的做法是,实现另外一些清理的方法,当应用程序用完资源对象后,就调用此方法。比如,如果某对象管理着连接服务器所用的套接字,此对象可能要通过套接字连接数据库。对于对象所属的类,其接口:
@interface  EOCServerConnection :  NSObject
- (
void )open:( NSString *)address;
- (
void )close;
@end

在dealloc中也要调用“清理方法”,以防开发者忘了清理这些资源。
- ( void )close {
    _close =
  YES ;
}

- (
void )dealloc {
   
  if  (!_close) {
        [
self  close];
    }
}

1.3不要在dealloc方法里随便调用其他方法。

    无论在这里调用什么方法都不太应该,因为对象此时已经接近尾声了。如果在这里所调用的方法又要异步执行某些任务,当这些任务执行完后,要回调此对象,告诉对象任务已完成,而此时如果对象已摧毁,那么回调就会出错。
    在dealloc里也不要调用属性的存取方法,因为这些方法会覆写,并于其中做一些无法在回收阶段安全执行的操作。此外,属性可能正处于“键值观测”(KVO)机制的监控之下,该属性的观察者可能会在属性值改变时“保留”或使用这个即将回收的对象。

2.编写“异常安全代码”时留意内存管理问题

异常安全:在发生异常时,不会导致内存泄露。

2.1手动管理引用计数时异常安全

     @try  {
        EOCSomeClass *object = [[EOCSomeClass alloc] init];
        [object soSomethingThatMayThrow];
        [object release];
    }
   
  @catch  (NSException *exception) {
       
  NSLog ( @"Whoops, there was an error. Oh well..." );
    }
如果 soSomethingThatMayThrow抛出异常,异常会令过程终止并跳转至catch块,因而其后的那行release代码不会运行。解决办法释放使用@finally,无论是否抛出异常,其中代码都保证会运行,且只运行一次。
    EOCSomeClass *object
   
  @try  {
        object = [[EOCSomeClass alloc] init];
        [object soSomethingThatMayThrow];
    }
   
  @catch  (NSException *exception) {
        NSLog(
@"Whoops, there was an error. Oh well..." );
    }
   
  @finally  {
        [object release];
    }

2.2ARC环境下异常安全

由于不能调用release,所以无法像手动管理引用计数时那样把释放操作移到@finally块中。若使用ARC且必须捕获异常,则需要打开编译器的-fobjc-arc-exceptions标志。ARC默认情况下是没有实现异常安全的,其原因如下:
1.ARC实现异常安全需要加入大量样板代码,以便跟踪清理的对象,从而在在抛出异常时将其释放。可是,这段代码会严重影响运行期的性能,即便不抛出异常也是如此。而且,添加进来的额外代码还会明显增加应用程序的大小。
2.Objective-C代码中,只有当应用程序因异常状况而终止时才抛出异常。

3.以“自动释放池块”降低内存峰值

释放对象有两种方式:一种是调用release方法,使其保留计数立即递减;另一种是调用autorelease方法,将其加入“自动释放池”中,自动释放池用于存放那些需要在稍后某个时刻释放的对象。清空自动释放池时,系统会向其中的对象发送release消息。
    一般情况下,无须担心自动释放池的创建问题。系统会自动为每个线程创建一个默认的自动释放池,每次执行“事件循环”时,就将其清空。

创建自动释放池的语法如下:
@autoreleasepool {
    //...
}

自动释放池于左花括号处创建,并于右花括号处自动清空。位于自动释放池范围内的对象,将在此范围末尾处收到release消息。自动释放池可以嵌套。自动释放池机制就像“栈”一样。系统创建好自动释放池之后,就将其推入栈中,而清空自动释放池,则相当于将其从栈中弹出。在对象上执行自动释放操作,就等于将其放入栈顶的那个池里。

    将自动释放池嵌套用的好处是,可以借此控制应用程序的内存峰值。
    NSArray  *databaseRecords =  /* ... */
    NSMutableArray *people = [NSMutableArray new];
   
  for  ( NSDictionary  *record  in  databaseRecords) {
       
  EOCPerson  *person = [[EOCPerson alloc] initWithRecord:record];
        [people addObject:person];
    }
EOCPerson的初始化函数也许会再创建出一些临时对象。若记录有很多条,则内存中也会有很多不必要的临时对象,它们本来应该提早回收的。

     NSArray  *databaseRecords =  /* ... */
    NSMutableArray *people = [NSMutableArray new];
   
  for  ( NSDictionary  *record  in  databaseRecords) {
       
  @autoreleasepool  {
           
  EOCPerson  *person = [[EOCPerson alloc] initWithRecord:record];
            [people addObject:person];

        }
    }
如果把循环内代码包裹在“自动释放池块”中,那么在循环中自动释放的对象就会放在这个池,而不是线程的主池里面。新增的自动释放池可以减少内存峰值,因为系统会在块的末尾把某些对象回收掉。

是否应该用池来优化效率,完全取决于具体的应用程序。首先得监控内存用量,判断其中有没有需要解决的问题,如果没有完成这一步,那就别着急优化。尽管自动释放池的开销不太大,但毕竟还是有的,所以尽量不要建立额外的自动释放池。

4.“用僵尸对象”调试内存管理

向已回收的对象发送消息是不安全的。这么做有时可以,有时不行。具体可行与否,完全取决于对象所占内存有没有为其他内容所覆写。而这块内存有没有移作他用,又无法确定,因此,应用程序只是偶尔崩溃。

    Cocoa提供了“僵尸对象”来调试内存管理问题。启用这项调试功能之后, 运行期系统会把所有已经回收的实例转化成特殊的“僵尸对象”,而不会正在回收它们。僵尸对象收到消息后,会抛出异常,其中准确说明了发送过来的消息,并描述了回收之前的那个对象。
设置NSZombieEnbled后:
void  PrintClassInfo( id  obj) {
    Class cls = object_getClass(obj);
    Class superCls = class_getSuperclass(cls);
    NSLog(
@"=== %s : %s ===" , class_getName(cls), class_getName(superCls));
}

int  main( int  argc,  char  *argc) {
   
  EOCClass  *obj = [[ EOCClass  alloc ]  init ];
   
  NSLog ( @"Before release" );
   
  PrintClassInfo (obj);        // === EOCClass : NSObject ===
   
    [obj
  release ];
   
  NSLog ( @"After release" );
   
  PrintClassInfo (obj);        // === _NSZombile_EOCClass : nil ===
}

对象所属的类已由EOCClass变为_NSZombie_EOCClass。_NSZombie_EOCClass实际上是在运行期生成的,当首次碰到EOCClass类的对象要变成僵尸对象时,就会创建这个类。创建过程如下:
     // 获取回收对象所属的类名
   
  const  char  *clsName =  object_getClassName ( self );
   
   
  // 生成僵尸对象名
   
  const  char  *zombieClsName =  "_ZSZombie_"  + clsName;
   
   
  // 判断这个僵尸对象是否已经存在
    Class zombieCls = object_lookUpClass(zombieClsName);
   
   
  // 如果不存在就创建
   
  if  (!zombieCls) {
       
  // 获得僵尸对象 _NSZombie_ 模板
        Class baseZobieCls = objc_lookUpClass(
"_NSZombie_" );
       
        zombieCls = object_duplicateClass(baseZobieCls,zombieClsName);
    }
   
   
  // 回收对象
    objc_destructInstance(
self );
   
    object_setClass(self,zombieCls);
     这个过程是NSObject的dealloc方法所做的事。运行期系统如果发现NSZombieEnabled环境变量已设置,那么就把dealloc方法“调配”成一个会执行上述代码的版本。执行到程序末尾时,对象所属的类已经变成_NSZombie_OriginalClass了。
    僵尸类是从名为_NSZombie_的模板类里复制出来的。创建新类的工作由运行期函数objc_duplicationClass( )来完成,它会把整个_NSZombie_类结构拷贝一份,并赋予其新的名字。副本的超类、实例变量以及方法都和复制前相同。
    _NSZombie_类是类似于NSObject的根类,并未实现任何方法,该类只有一个实例变量,叫做isa。 由于这个轻量级的类没有实现任何方法,所以发给它的全部消息都要经过“完整的消息转发机制”。在完整的消息转发机制中,___forwarding___是核心,它首先要做的事就包括检查接收消息的对象所属的类名。若名称前缀为_NSZombie_,则表明消息接收者是僵尸对象,需要特殊处理。此时会打印一条消息,其中指明了僵尸对象所收到的消息及原来所属的类,然后应用程序就中止了。

    虽说内存泄露了,但这只是调试手段,制作正式发行的应用程序时不会把这项功能打开,所以这种泄露无关要紧。 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值