iPhone开发中的内存管理

 

本文转载自:http://blog.sina.com.cn/s/blog_6b9c53390100sawg.html

 

 

移动开发的特点:资源的有限性。作为手持设备,iphone的内存与传统的PC不可同日而语,这就要求我们在开发IOS程序的过程中,首要也是最重要的任务就是解决内存释放问题,本文将在网络上搜集的关于内存管理的经验予以分享。


    开发iPhone 应用程序并不难,基本上就是三个词 - “memory, memory, memory” iPhone OS 对内存的要求很严格,有memory leak ,杀掉; 内存使用超限额,杀掉。一个经过测试的程序,在使用过程中90%以上的崩溃都是内存问题造成的。在这里简单总结一下Object-C 内存管理。

 

一、基本概念

   Objective-C 的内存管理基于引用计数(Reference Count)这种非常常用的技术。简单讲,如果要使用一个对象,并希望确保在使用期间对象不被释放,需要通过函数调用来取得所有权,使用结束后再调用 函数释放所有权所有权的获得和释放,对应引用计数的增加和减少,为正数时代表对象还有引用,为零时代表可以释放。

 

1、函数

获得所有权的函数包括

  • alloc - 创建对象是调用alloc,为对象分配内存,对象引用计数加一。
  • copy - 拷贝一个对象,返回新对象,引用计数加一。
  • retain - 引用计数加一,获得对象的所有权。

另外,名字中带有alloc, copy, retain 字串的函数也都认为会为引用计数加一。

释放所有权的函数包括

  • release - 引用计数减一,释放所有权。如果引用计数减到零,对象会被释放。
  • autorelease - 在未来某个时机释放。下面具体解释。

autorelease

在某些情况下,并不想取得所有权,又不希望对象被释放。例如在一个函数中生成了一个新对象并返回,函数本身并不希望取得所有权,因为取得后再没有机 会释放(除非创造出新的调用规则,而调用规则是一切混乱的开始),又不可能在函数内释放,可以借助autorelease 。所谓autorelease , 可以理解为把所有权交给一个外在的系统(这个系统实际上叫autorelease pool),由它来管理该对象的释放。通常认为交给 autorelease 的对象在当前event loop 中都是有效的。也可以自己创建NSAutoreleasePool 来控制autorelease的过程。

据苹果的人说,autorelease效率不高,所以能自己release的地方,尽量自己release,不要随便交给autorelease来处理。

 

2、规则

引用计数系统有自己的引用规则,遵守规则就可以少出错:

  • 获得所有权的函数要和释放所有权的函数一一对应。
  • 保证只有带alloc, copy, retain 字串的函数才会让调用者获得所有权,也就是引用计数加一。
  • 在对象的 dealloc函数中释放对象所拥有的实例变量。
  • 永远不要直接调用dealloc来释放对象,完全依赖引用计数来完成对象的释放。

有很多类都提供便利构造函数(convenience constructors)”,它们创建对象但并不增加引用计数,意味着不需要调用release来释放所有权。很好辨认,它们的名字中不会有alloccopy

只要遵守这些规则,基本上可以消除所有Intrument可以发现的内存泄露问题。

 

3、容器

类似NSArray, NSDictionary, NSSet 等类,会在对象加入后引用计数加一获得所有权,在对象被移除或者整个容器对象被释放的时候释放容器内对象的所有权。类似的情况还有UIView subview的所有权关系,UINavigationController对其栈上的controller的所有权关系等等。

 

4、其他所有权的产生

还有一些用法会让系统拥有对象的所有权。比如NSObject performSelector:withObject:afterDelay 。如果有必要,需要显示的调用cancelPreviousPerformRequestsWithTarget:selector:object: ,否则有可能产生内存泄露。

因这种原因产生的泄露因为并不违反任何规则,是Intrument所无法发现的。

 

5、循环引用

所有的引用计数系统,都存在循环应用的问题。例如下面的引用关系:

  • 对象a创建并引用到了对象b.
  • 对象b创建并引用到了对象c.
  • 对象c创建并引用到了对象b.

这时候bc的引用计数分别是21。当a不再使用b,调用release释放对b的所有权,因为c还引用了b,所以b的引用计数为1b不会被释放。b不释放,c的引用计数就是1c也不会被释放。从此,bc永远留在内存中。

这种情况,必须打断循环引用,通过其他规则来维护引用关系。比如,我们常见的delegate往往是assign方式的属性而不是retain方式 的属性,赋值不会增加引用计数,就是为了防止delegation两端产生不必要的循环引用。如果一个UITableViewController 对象a通过retain获取了UITableView对象b的所有权,这个UITableView对象bdelegate又是a 如果这个delegateretain方式的,那基本上就没有机会释放这两个对象了。自己在设计使用delegate模式时,也要注意这点。

因为循环引用而产生的内存泄露也是Instrument无法发现的,所以要特别小心。

 

惯有规则:

规则1: 是不是我们在用对象时,只要不是自己alloc的,就不需要去release了?

(有很多类都提供便利构造函数(convenience constructors)”,它们创建对象但并不增加引用计数,意味着不需要调用release来释放所有权。很好辨认,它们的名字中不会有alloccopy)

通常,不是你alloc, copy或者retain过的,就不需要去release了。


规则2:关于循环引用中的例子,为什么将delegate赋为nil,就可以避免循环问题?

并不是将delegate赋值为nil就可以避免循环问题,实际上delegate往往都设计成assign方式的属性,而不是retain属性的。也就是说并没有获得所有权,这是保证不产生循环问题的条件。

我们在项目中,有时需要一些全局变量存储一些基本信息,那么这些全局的变量内存又是如何管理的呢?

下面是网友分享的经验,具体的网址我记不清了,我在这里直接拿过来放到我的博客了,在此向写本文的作者致敬!

正是由于众多具有开源精神的网友,技术才能更好的传播!

 

项目中有时需要一些全局变量存储应用程序运行过程中的一直存在的信息翻看了一些资料决定使用Apple官方文档推荐的Signaleton模式,使用过程很顺利但是随之而来的是关于内存管理的考虑官方文档的示例代码如下:

 

static MyGizmoClass *sharedGizmoManager = nil;

 

+ (MyGizmoClass*)sharedManager

{

    if (sharedGizmoManager == nil) {

        sharedGizmoManager = [[super allocWithZone:NULL] init];

        }

    return sharedGizmoManager;

}

 

+ (id)allocWithZone: (NSZone *)zone

{

     return [[self sharedManager] retain];

}

 

- (id)copyWithZone: (NSZone *)zone

{

     return self ;

}

 

- (id)retain

{

     return self;

}

 

- (NSUInteger)retainCount

{

     return NSUIntegerMax;  //denotes an object that cannot be released

}

 

- (void)release

{

     //do nothing

}

 

- (id)autorelease

{

     return self;

}

      可以看到上面的代码中除了为保证这个静态类的唯一性还有一个奇怪的地方则是这个类没有dealloc方法那么这个类实例化后所分配的内存是在什么时候释放的如果这个类中还含有一些其他的成员变量这些变量所分配的内存又是在何时释放的呢

      以之前C/C++平台的经验静态变量的内存被放在全局区(或称之为静态区),静态变量的内存在应用程序启动之前由系统分配,在应用程序退出之后又由系统自己回收。Cocoa基于C设计所以大致上应该也是这个样子。

      为了验证这个问题我尝试着给这个类添加了一个dealloc方法之后更加奇怪的事情就发生了我在这个类dealloc方法中设置断点并且尝试输出log,但当程序退出时不仅断点没有断下来甚至在console中连这段log的踪影都看不到我开始怀疑这段dealloc没有被调用之后我又在其他类的dealloc方法中输出log,发现一个规律dealloc方法在程序运行过程中随着类实例的释放会被调用但是当直接退出程序时(HOME),所有的dealloc都不被调用这就说明当应用退出的时候,Cocoa是不会调用实例的dealloc方法的这是意味着什么呢我猜想可能Cocoa会在应用程序退出时自己回收所有的内存并且不像C++那样去调用存在的类实例的析构方法为证实这个猜想,google最终在Cocoa With LoveApple的另一份官方文档中找到了答案并证明我的这个猜测是正确的:

      官方文档中是这么解释的: " When an application terminates, objects may not be sent a dealloc message since the process’s memory is automatically cleared on exit—it is more efficient simply to allow the operating system to clean up resources than to invoke all the memory management methods." 大致的意思就是当应用程序退出时对象不会接受到一个dealloc消息系统会自己清理所有的资源,Apple认为这样比去调用一个内存管理方法更有 效率

      Cocoa With LoveMatt关于我提问的解答是这样的: "You don't need to free data from a singleton -- it lasts until the program quits, so the dealloc method will never be invoked. If you need to close a network connection, or something else that actually needs to be ended, you should do this in a "close" method and invoke the "close" method on the singleton in your applicationWillTerminate: method of your application delegate." 意思差不多和官方一样,应用退出时,dealloc方法不会被调用,并且他建议我避免在dealloc中去作网络或其他类似的必须终止的操作,而应该将这 些操作放在applicationWillTerminate:中,以确定这些操作被执行。

     呵呵,谢谢Matt,他的这个建议确实是非常有效果的,否则我的下一个问题肯定的是:那么一定得终止的操作放在哪里执行?

所以关于这个问题的研究暂时就告一段落了,得出的结论是
1.不用担心静态全局变量的内存的问题,系统会在应用程序结束之后,回收这些内存;
2.应用程序结束时会直接回收所有的程序运行中的资源,而不调用对象的dealloc方法;
3.不要将类似网络或文件的关闭(应该是任何)操作放在类的dealloc方法中执行;

 

当然这也只是告一段落了,由此我又产生了两个问题:
1.如果系统会在程序结束后回收所有的资源,那么是否基本不用考虑什么内存泄露的问题?内存泄露只会在程序运行过程中发生?即使发生了内存泄露,应用程序退出时,系统也会回收这些内存;
2.Cocoa如何实现这一套资源管理机制的呢?只以内存说,难道系统给应用程序指定一段内存,你应用程序分配的内存只会在这一段区域里,当应用程序退出之后,我系统就直接抹了这段内存?

这两个问题想请教tinyfool  robinlu不知他们二位有没有时间。不过我的这个blog太丑了,我先把字弄大点.

文中参考资料:
1.Apple推荐Sinaleton方法的文档Cocoa Fundamental Guide:Cocoa ObjectsCreating a Singleton Instance一节 这里还有中文的
2.Cocoa With Love中关于top-level data的话题:Singletons, AppDelegates and top-level data这里讨论还了你需要全局数据的条件,还提供了另外一个方法存放全局的数据:放置在AppDelegates,并且讨论了这种方法的不妥之处,建议和我一样的新手可以拜读一下.
3.Apple解释应用程序退出时不调用dealloc方法的文档:Memory Management Programming Guide for Cococa:Object Ownship and DisposalDeallocating an Object一节的important:一段


在iphone程序中,属性合成中的retain/copy/assign有什么区别?

 

1)assign就不用说了,因为基本上是为简单数据类型准备的,原子类类型,例如CGPoint、CGFloat等,而不是NS对象们;

2)retain VS copy

  • copy 建立一个索引计数为1的对象,然后释放旧对象
  • retain:释放旧的对象,将旧对象的值赋予输入对象,再提高输入对象的索引计数为1

Copy其实是建立了一个相同的对象,而retain不是:

比如一个NSString对象,地址为0×1111,内容为@”STR”

Copy到另外一个NSString之后,地址为0×2222,内容相同,新的对象retain1,旧有对象没有变化

retain到另外一个NSString之后,地址相同(建立一个指针,指针拷贝),内容当然相同,这个对象的retain+1

也就是说,retain是指针拷贝,copy是内容拷贝。

 

ObjectiveC中的copyc++的一样分深拷贝和浅拷贝,怎样区分这两个对象呢?我的理解是:

   (1)深拷贝,就是新拷贝一块内存交给对象使用。

   (2)浅拷贝,就是觉得拷贝内存太浪费,直接给你我的地址吧,相当于retain

 

3)怎么区分这两种对象呢?

    ObjectiveC里面只有一种情况是浅拷贝,那就是不可变对象的copy,其它的都是深拷贝(包括不可变对象mutableCopy、可变对象的的copymutableCopy)。

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值