Objective C 内存管理[转]

1  配对原则

alloc – release

new – release

retain - release

copy – release

2  new和alloc-init的区别

(1)区别只在于alloc分配内存的时候使用了zone.

这个zone是个什么呢?

它是给对象分配内存的时候,把关联的对象分配到一个相邻的内存区域内,以便于调用时消耗很少的代价,提升了程序处理速度。

(2)为什么不推荐使用new

因为若用了new,则初始化方法只能是init。这样,假如你想调用initWithFrame,initWithVideoId, initWithPlayItem是无法做到的。另一个原因是习惯和风格的问题。

3  强引用与弱引用(retain, assign,strong, weak)

retain为强引用,会导致引用计数+1。

assign为弱引用,不会引起引用计数的变化。

4 retain和copy区别

copy重新分配一片内存

5 release autorelease

release            对象引用计数-1 如果为0释放内存
    autorelease      对象引用计数-1 如果为0不马上释放,最近一个个pool时释放

objective-C的内存管理机制琢磨不透

前言

初学objectice-C的朋友都有一个困惑,总觉得对objective-C的内存管理机制琢磨不透,程序经常内存泄漏或莫名其妙的崩溃。我在这里总结了自己对objective-C内存管理机制的研究成果和经验,写了这么一个由浅入深的教程。希望对大家有所帮助,也欢迎大家一起探讨。

此文涉及的内存管理是针对于继承于NSObject的Class。

一 基本原理

Objective-C的内存管理机制与.Net/Java那种全自动的垃圾回收机制是不同的,它本质上还是C语言中的手动管理方式,只不过稍微加了一些自动方法。

1. Objective-C的对象生成于堆之上,生成之后,需要一个指针来指向它。

ClassA *obj1 = [[ClassA init];

2.  Objective-C的对象在使用完成之后不会自动销毁,需要执行dealloc来释放空间(销毁),否则内存泄露。

[obj1 dealloc];

这带来了一个问题。下面代码中obj2是否需要调用dealloc?

ClassA *obj1 = [[ClassA alloc] init];

ClassA *obj2 = obj1;

[obj1 hello]; //输出hello

[obj1 dealloc];

[obj2 hello]; //能够执行这一行和下一行吗?

[obj2 dealloc];

不能,因为obj1和obj2只是指针,它们指向同一个对象,[obj1 dealloc]已经销毁这个对象了,不能再调用[obj2 hello]和[obj2 dealloc]。obj2实际上是个无效指针。

如何避免无效指针?请看下一条。

3           Objective-C采用了引用计数(ref count或者retain count)。对象的内部保存一个数字,表示被引用的次数。例如,某个对象被两个指针所指向(引用)那么它的retain count为2。需要销毁对象的时候,不直接调用dealloc,而是调用release。release会让retain count减1,只有retain count等于0,系统才会调用dealloc真正销毁这个对象。

ClassA *obj1 = [[ClassA alloc] init]; //对象生成时,retain count = 1

[obj1 release]; //release使retain count减1,retain count = 0,dealloc自动被调用,对象被销毁

我们回头看看刚刚那个无效指针的问题,把dealloc改成release解决了吗?

ClassA *obj1 = [[ClassA alloc] init]; //retain count = 1

ClassA *obj2 = obj1; //retain count = 1

[obj1 hello]; //输出hello

[obj1 release]; //retain count = 0,对象被销毁

[obj2 hello];

[obj2 release];

[obj1 release]之后,obj2依然是个无效指针。问题依然没有解决。解决方法见下一条。

4 . Objective-C指针赋值时,retain count不会自动增加,需要手动retain。

ClassA *obj1 = [[ClassA alloc] init]; //retain count = 1

ClassA *obj2 = obj1; //retain count = 1

[obj2 retain]; //retain count = 2

[obj1 hello]; //输出hello

[obj1 release]; //retain count = 2 – 1 = 1

[obj2 hello]; //输出hello

[obj2 release]; //retain count = 0,对象被销毁

问题解决!注意,如果没有调用[obj2 release],这个对象的retain count始终为1,不会被销毁,内存泄露。(1-4可以参考附件中的示例程序memman-no-pool.m)

这样的确不会内存泄露,但似乎有点麻烦,有没有简单点的方法?见下一条。

5. Objective-C中引入了autorelease pool(自动释放对象池),在遵守一些规则的情况下,可以自动释放对象。(autorelease pool依然不是.Net/Java那种全自动的垃圾回收机制)

5.1  新生成的对象,只要调用autorelease就行了,无需再调用release!

ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1 但无需调用release

5.2 对于存在指针赋值的情况,代码与前面类似。

ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1

ClassA *obj2 = obj1; //retain count = 1

[obj2 retain]; //retain count = 2

[obj1 hello]; //输出hello

//对于obj1,无需调用(实际上不能调用)release

[obj2 hello]; //输出hello

[obj2 release]; //retain count = 2-1 = 1

细心的读者肯定能发现这个对象没有被销毁,何时销毁呢?谁去销毁它?(可以参考附件中的示例程序memman-with-pool.m)请看下一条。

6. autorelease pool原理剖析。(其实很简单的,一定要坚持看下去,否则还是不能理解Objective-C的内存管理机制。)

6.1 autorelease pool不是天生的,需要手动创立。只不过在新建一个iphone项目时,xcode会自动帮你写好。autorelease pool的真名是NSAutoreleasePool。

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

6.2 NSAutoreleasePool内部包含一个数组(NSMutableArray),用来保存声明为autorelease的所有对象。如果一个对象声明为autorelease,系统所做的工作就是把这个对象加入到这个数组中去。

ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1,把此对象加入autorelease pool中

6.3 NSAutoreleasePool自身在销毁的时候,会遍历一遍这个数组,release数组中的每个成员。如果此时数组中成员的retain count为1,那么release之后,retain count为0,对象正式被销毁。如果此时数组中成员的retain count大于1,那么release之后,retain count大于0,此对象依然没有被销毁,内存泄露。

6.4 默认只有一个autorelease pool,通常类似于下面这个例子。

int main (int argc, const char *argv[])

{

NSAutoreleasePool *pool;

pool = [[NSAutoreleasePool alloc] init];

// do something

[pool release];

return (0);

} // main

所有标记为autorelease的对象都只有在这个pool销毁时才被销毁。如果你有大量的对象标记为autorelease,这显然不能很好的利用内存,在iphone这种内存受限的程序中是很容易造成内存不足的。例如:

int main (int argc, const char *argv[])

{

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

int i, j;

for (i = 0; i < 100; i++ )

{

for (j = 0; j < 100000; j++ )

[NSString stringWithFormat:@"1234567890"];//产生的对象是autorelease的。

}

[pool release];

return (0);

} // main

(可以参考附件中的示例程序memman-many-objs-one-pool.m,运行时通过监控工具可以发现使用的内存在急剧增加,直到pool销毁时才被释放)你需要考虑下一条。

7. Objective-C程序中可以嵌套创建多个autorelease pool。在需要大量创建局部变量的时候,可以创建内嵌的autorelease pool来及时释放内存。(感谢网友hhyytt和neogui的提醒,某些情况下,系统会自动创建autorelease pool, 请参见第四章)

int main (int argc, const char *argv[])

{

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

int i, j;

for (i = 0; i < 100; i++ )

{

NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];

for (j = 0; j < 100000; j++ )

[NSString stringWithFormat:@"1234567890"];//产生的对象是autorelease的。

[loopPool release];

}

[pool release];

return (0);

} // main

(可以参考附件中的示例程序memman-many-objs-many-pools.m,占用内存的变化极小)

二 口诀与范式

1 口诀。

1.1 谁创建,谁释放(类似于“谁污染,谁治理”)。如果你通过alloc、new或copy来创建一个对象,那么你必须调用release或autorelease。换句话说,不是你创建的,就不用你去释放。
例如,你在一个函数中alloc生成了一个对象,且这个对象只在这个函数中被使用,那么你必须在这个函数中调用release或autorelease。如果你在一个class的某个方法中alloc一个成员对象,且没有调用autorelease,那么你需要在这个类的dealloc方法中调用release;如果调用了autorelease,那么在dealloc方法中什么都不需要做。

1.2 除了alloc、new或copy之外的方法创建的对象都被声明了autorelease。

1.3 谁retain,谁release。只要你调用了retain,无论这个对象是如何生成的,你都要调用release。有时候你的代码中明明没有retain,可是系统会在默认实现中加入retain。不知道为什么苹果公司的文档没有强调这个非常重要的一点,请参考范式2.7和第三章。

2   范式。
范式就是模板,就是依葫芦画瓢。由于不同人有不同的理解和习惯,我总结的范式不一定适合所有人,但我能保证照着这样做不会出问题。

2.1 创建一个对象。

ClassA *obj1 = [[ClassA alloc] init];

2.2创建一个autorelease的对象。

ClassA *obj1 = [[[ClassA alloc] init] autorelease];

2.3 Release一个对象后,立即把指针清空。(顺便说一句,release一个空指针是合法的,但不会发生任何事情)

[obj1 release];

obj1 = nil;

2.4 指针赋值给另一个指针。

ClassA *obj2 = obj1;

[obj2 retain];

//do something

[obj2 release];

obj2 = nil;

2.5 在一个函数中创建并返回对象,需要把这个对象设置为autorelease

ClassA *Func1()

{

ClassA *obj = [[[ClassA alloc]init]autorelease];

return obj;

}

2.6 在子类的dealloc方法中调用基类的dealloc方法

-(void) dealloc

{

[super dealloc];

}

2.7  在一个class中创建和使用property。

2.7.1     声明一个成员变量。

ClassB *objB;

2.7.2     声明property,加上retain参数。

@property (retain) ClassB* objB;

2.7.3     定义property。(property的默认实现请看第三章)

@synthesize objB;

2.7.4     除了dealloc方法以外,始终用.操作符的方式来调用property。

self.objB 或者objA.objB

2.7.5     在dealloc方法中release这个成员变量。

[objB release];

示例代码如下(详细代码请参考附件中的memman-property.m,你需要特别留意对象是在何时被销毁的。):

@interface ClassA : NSObject

{

ClassB* objB;

}

@property (retain) ClassB* objB;

@end

@implementation ClassA

@synthesize objB;

-(void) dealloc

{

[objB release];

[super dealloc];

}

@end

2.7.6     给这个property赋值时,有手动release和autorelease两种方式。

void funcNoAutorelease()

{

ClassB *objB1 = [[ClassB
alloc]init];

ClassA *objA = [[ClassA
alloc]init];

objA.objB = objB1;

[objB1 release];

[objA release];

}

void funcAutorelease()

{

ClassB *objB1 = [[[ClassB
alloc]init] autorelease];

ClassA *objA = [[[ClassA
alloc]init] autorelease];

objA.objB = objB1;

}

三 @property (retain)和@synthesize的默认实现

在这里解释一下@property
(retain) ClassB* objB;和@synthesize objB;背后到底发生了什么(retain property的默认实现)。property实际上是getter和setter,针对有retain参数的property,背后的实现如下(请参考附件中的memman-getter-setter.m,你会发现,结果和memman-property.m一样):

@interface ClassA : NSObject

{

ClassB *objB;

}

-(ClassB *) getObjB;

-(void) setObjB:(ClassB *) value;

@end

@implementation ClassA

-(ClassB*) getObjB

{

return objB;

}

-(void) setObjB:(ClassB*) value

{

if (objB != value)

{

[objB release];

objB = [value retain];

}

}

在setObjB中,如果新设定的值和原值不同的话,必须要把原值对象release一次,这样才能保证retain count是正确的。

由于我们在class内部retain了一次(虽然是默认实现的),所以我们要在dealloc方法中release这个成员变量。

-(void) dealloc

{

[objB release];

[super dealloc];

}

四 系统自动创建新的autorelease
pool

在生成新的Run
Loop的时候,系统会自动创建新的autorelease pool(非常感谢网友hhyytt和neogui的提醒)。注意,此处不同于xcode在新建项目时自动生成的代码中加入的autorelease pool,xcode生成的代码可以被删除,但系统自动创建的新的autorelease pool是无法删除的(对于无Garbage Collection的环境来说)。Objective-C没有给出实现代码,官方文档也没有说明,但我们可以通过小程序来证明。

在这个小程序中,我们先生成了一个autorelease pool,然后生成一个autorelease的ClassA的实例,再在一个新的run loop中生成一个autorelease的ClassB的对象(注意,我们并没有手动在新run loop中生成autorelease pool)。精简的示例代码如下,详细代码请见附件中的memman-run-loop-with-pool.m。

int main(int argc, char**argv)

{

NSLog(@"create an
autorelasePool\n");

NSAutoreleasePool *pool =
[[NSAutoreleasePool alloc] init];

NSLog(@"create an
instance of ClassA and autorelease\n");

ClassA *obj1 = [[[ClassA
alloc] init] autorelease];

NSDate *now = [[NSDate
alloc] init];

NSTimer *timer = [[NSTimer
alloc] initWithFireDate:now

interval:0.0

target:obj1

selector:@selector(createClassB)

userInfo:nil

repeats:NO];

NSRunLoop *runLoop =
[NSRunLoop currentRunLoop];

[runLoop addTimer:timer
forMode:NSDefaultRunLoopMode];

[timer release];

[now release];

[runLoop run]; //在新loop中调用一函数,生成ClassB的autorelease实例???

NSLog(@"releasing
autorelasePool\n");

[pool release];

NSLog(@"autorelasePool
is released\n");

return 0;

}

输出如下:

create an autorelasePool

create an instance of ClassA and autorelease

create an instance of ClassB and autorelease

ClassB destroyed

releasing autorelasePool

ClassA destroyed

autorelasePool is released

注意在我们销毁autorelease pool之前,ClassB的autorelease实例就已经被销毁了。

有人可能会说,这并不能说明新的run loop自动生成了一个新的autorelease pool,说不定还只是用了老的autorelease pool,只不过后来drain了一次而已。我们可以在main函数中不生成autorelease pool。精简的示例代码如下,详细代码请见附件中的memman-run-loop-without-pool.m。

int main(int argc, char**argv)

{

NSLog(@"No
autorelasePool created\n");

NSLog(@"create an
instance of ClassA\n");

ClassA *obj1 = [[ClassA
alloc] init];

NSDate *now = [[NSDate
alloc] init];

NSTimer *timer = [[NSTimer
alloc] initWithFireDate:now

interval:0.0

target:obj1

selector:@selector(createClassB)

userInfo:nil

repeats:NO];

NSRunLoop *runLoop =
[NSRunLoop currentRunLoop];

[runLoop addTimer:timer
forMode:NSDefaultRunLoopMode];

[timer release];

[now release];

[runLoop run]; //在新loop中调用一函数,生成ClassB的autorelease实例

NSLog(@"Manually
release the instance of ClassA\n");

[obj1 release];

return 0;

}

输出如下:

No autorelasePool created

create an instance of ClassA

create an instance of ClassB and autorelease

ClassB destroyed

Manually release the instance of ClassA

ClassA destroyed

我们可以看出来,我们并没有创建任何autorelease pool,可是ClassB的实例依然被自动销毁了,这说明新的run loop自动创建了一个autorelease pool,这个pool在新的run loop结束的时候会销毁自己(并自动release所包含的对象)。

补充说明

在研究retain
count的时候,我不建议用NSString。因为在下面的语句中,

NSString *str1 = @”constant string”;

str1的retain count是个很大的数字。Objective-C对常量字符串做了特殊处理。

当然,如果你这样创建NSString,得到的retain count依然为1

NSString *str2 = [NSString stringWithFormat:@”123”];

涉及的示例程序代码(已去除隐藏,觉得有用的话请顶一下此文):http://files.cnblogs.com/VinceYuan/objective-c-memman.zip

Objective C 内存管理[转]

时间: 2024-08-07 16:58:40

Objective C 内存管理[转]的相关文章

objective C 内存管理及属性方法详解

oc为每个对象提供一个内部计数器,这个计数器跟踪对象的引用计数,当对象被创建或拷贝时,引用计数为1,每次保持对象时,调用retain接口,引用计数加1,如果不需要这个对象时调用release,引用计数减1,当对像的引用计数为0时,系统就会释放掉这块内存,释放对象调用dealloc 当对象包含其他对象时,就得在dealloc中自己释放他们 NSObject是IOS所有类的基类 有两个基本函数,alloc和dealloc alloc类似于C++的new,dealloc类似于delete 当对象的re

iOS核心语言Objective C语言 —— 内存管理

本分享是面向有意向从事iOS开发的伙伴以及苹果产品的发烧友们,或者已经从事了iOS的开发者,想进一步提升者.如果您对iOS开发有极高的兴趣,可以与我一起探讨iOS开发,一起学习,共同进步.如果您是零基础,建议您先翻阅我之前分享的iOS开发分分钟搞定C语言系列,然后在开始Objective C语言的学习,如果您遇到问题也可以与我探讨,另外将无偿分享自己整理出来的大概400G iOS学习视频及学习资料,都是干货哦!可以新浪微博私信?关注极客James,期待与您的共同学习和探讨!!由于时间有限,每天在

Objective -C Memory Management 内存管理 第一部分

Objective -C Memory Management??内存管理??第一部分 Memory management is part of a more general problem in programming called resource management. 内存管理是资源管理的一部分. Every computer system has finite resources for your program to use. These include memory, open fi

Objective-C(内存管理)

引用计数器 每个OC对象都有一个占4个字节存储空间的引用计数器 当使用或创建一个对象时,新对象的引用计数器默认是1 retain:可以使引用计数器+1 release:可以是引用计数器-1 retainCount:获得当前的引用计数器的值 当对象被销毁时,会重写dealloc方法 -(void)dealloc { // 这句必须放在最后面 [super dealloc]; } 僵尸对象:所占内存已经被回收的对象,僵尸对象不能再使用 野指针:指向僵尸对象(不可用的内存)的指针 错误:EXC_BAD

IOS学习笔记3—Objective C—简单的内存管理

今天简述一下简单的内存管理,在IOS5.0以后Apple增加了ARC机制(Automatic Reference Counting),给开发人员带来了不少的方便,但是为了能更好的理解IOS内存管理机制,还是需要对其比较了解. 1.在OC中,每个对象都有一个保留计数,创建时每个对象都有一个初始值为1的保留计数,释放时,保留计数都为0 2.创建自动释放的对象 要求以一个方法创建对象时,以自动释放的形式返回该对象是一个很好的编程实践 +(Car *)car { Car *myCar = [[Car a

iOSDay17之内存管理

1.内存管理的方式 1> iOS应用程序出现Crash(闪退),90%的原因是因为内存问题. 2> 内存问题 野指针异常:访问没有所有权的内存,如果想要安全的访问,必须确保空间还在 内存泄露:空间使用完之后没有及时释放 过度释放:对同一块存储空间释放多次,立刻crash 内存溢出:所有存储空间被占用 3> 管理内存的三种方式 垃圾回收机制:程序员只需要开辟存储空间,系统会自动回收内存.java采用的该机制 MRC:手动引用计数机制,由开发人员开辟空间,手动添加影响引用计数增加或减少的方法

Objective-C----MRC内存管理 、 自动释放池 、 面向对象三大特性及封装 、 继承 、 组合与聚合

1 MRC练习 1.1 问题 引用计数是Objective-C语言采用的一种内存管理技术,当一个对象被创建在堆上后,该对象的引用计数就自动设置为1,如果在其它对象中的对象成员需要持有这个对象时,则该对象的引用计数被加上1,此时如果该对象被释放,内存管理程序将首先把该对象的引用计数减1,然后判断该对象的引用计数是否为0,由于其它对象在持有该对象时将引用计数加了1,所以此时该对象的引用计数减1后不为0,则内存管理程序将不会释放该对象.直到持有该对象的其它对象也被释放时,该对象的引用计数再次减1,变为

&lt;Linux内核源码&gt;内存管理模型

题外语:本人对linux内核的了解尚浅,如果有差池欢迎指正,也欢迎提问交流! 首先要理解一下每一个进程是如何维护自己独立的寻址空间的,我的电脑里呢是8G内存空间.了解过的朋友应该都知道这是虚拟内存技术解决的这个问题,然而再linux中具体是怎样的模型解决的操作系统的这个设计需求的呢,让我们从linux源码的片段开始看吧!(以下内核源码均来自fedora21 64位系统的fc-3.19.3版本内核) <include/linux/mm_type.h>中对于物理页面的定义struct page,也

objective-c(内存管理)

本文主要记录objective-c 内存管理的知识点: 1.objective-c的对象都是分配内存在堆上,与C的mallock和C++的new类似,只有int等系统变量分配内存在栈上: 2.objective-c没有java这般复杂的垃圾回收机制,它用的是引用计数,可以理解为创建该对象后,指向该对象首地址的指针是否在其他地方被引用,若增加引用则引用数加一,反之减一,当引用数为零时系统清除该变量.内部应该是堆上分配的地址重新设置为有效,也就是说可以再次分配给其他对象,存储该对象首地址的指针清除,