12.Object-C--浅谈OC的内存管理机制
昨天学习了OC的内存管理机制,今天想总结一下,所以接下来我要在这里bibi一下:OC的内存管理。
首先我要说的是,内存管理的作用范围。
内存管理的作用范围:
任何继承了NSObject的对象,对其他基本数据类型无效,例如:int ,float,BOOL等。
每个OC对象都有自己的引用计数器,用来表示对象被引用的次数。
每个OC对象内部都有4个字节的存储空间用来存储引用计数器。
在使用alloc、new或者copy创建新对象的时候,新对象的引用计数器默认值为1
当一个对象的引用计数器的值为0时,对象占用的内存就会被系统回收。
下面说说,引用计数器的用法
1> retain:计数器+1,会返回对象本身(id)。
2> release:计数器-1,没有返回值(void)。
3> retainCount:获取对象当前的引用计数器的值。
对象销毁
当一个对象的引用计数器的值为0时,该对象将被销毁,其占用的内存被系统回收。当一个对象被回收时,调用dealloc方法。一般情况下会重写dealloc,来释放相关资源。重写dealloc方法时,有一定要在最后面调用[super dealloc]。对象被回收之后,其占用的内存不可再用。
如下代码,简单说一下内存管理操作
1 int main()
2 {
3 Person *p = [[Person alloc] init]; // 创建对象,此时引用计数器值为:1
4
5 [p retain]; // 对对象p做一次retain操作 引用计数器+1,此时为:2
6
7 [p release]; // 对对象p做一次release操作 引用计数器-1,此时为:1
8
9 [p release]; // 对对象p做一次release操作 引用计数器-1,此时为:0
10
11 // p.name = @"jack"; // 这时p为野指针,p指向的对象为僵尸对象
12 p = nil; // 将p指针的值清空
13
14 return 0;
15 }
OC中有一个名词叫僵尸对象,下面说说,什么是僵尸对象?
僵尸对象:所占用内存已经被回收的对象,僵尸对象不能使用。
野指针:指向僵尸对象的指针,给野指针发送消息会报错(EXC_BAD_ACCESS)。
空指针:没有指向任何东西的指针(存储的内容是nil、NULL、0),给空指针发送消息不会报错。
之前说的是单个对象,那么多个对象之前的内存管理是怎么样子的呢?
多对象之间的内存管理
使用某个对象,则对该对象做一次retain操作。结束使用时,对该对象做一次release操作。
原则是:谁retain,谁release;谁alloc,谁release。
示例代码如下:
1 @class Dog; // 因为Dog类还没有声明,要先告诉编译器Dog是一个类
2 @interface Person : NSObject
3 @property(nonatomic,assign) NSString *name;
4 @property(nonatomic,assign) int age;
5 // Person对象包含一个Dog对象,当调用Dog对象时,要对dog对象进行一次retain操作
6 @property(nonatomic,retain) Dog *dog;
7 @end
8 @implementation Person
9 - (void)setDog:(Dog *)dog
10 { // 判断dog是否为新dog
11 if (dog != _dog) {
12 // 是新dog则对老dog进行一次release操作
13 [_dog release];
14 // 对新dog进行一次retain操作
15 _dog = [dog retain];
16 }
17 }
18 // dealloc重写,用来release Person的Dog
19 - (void)dealloc
20 {
21 // 谁retain谁release,当_dog的值为nil、NULL、0时 对它发送release消息,没有任何意义
22 [_dog release];
23 // 重写dealloc要在最后面调用父类的dealloc方法[super dealloc]
24 [super dealloc];
25 }
26 @end
@property参数
retain:release旧值,retain新值(适用于OC对象类型)。
assign:直接赋值(默认、适用于非OC对象类型)。
copy:release旧值,copy新值(一般用于NSString *)。
set、get方法的生成
readwrite:同时生成set方法和get方法的声明与实现(默认)。
readonly:只生成get方法的声明与实现。
多线程管理
nonatomic:性能高 (一般使用这个值)。
atomic :性能低 (默认值)。
set方法和get方法的名称
setter:决定set方法的名称,必须有冒号:
getter:决定get方法的名称 (一般用在BOOL类型 isXX)。
对象循环引用中的内存管理
当两个对象互相引用时,在一个对象的@property中使用retain,另一个对象的@property中使用assign。
1 @class Dog; // 因为Dog类还没有声明,要先告诉编译器Dog是一个类
2 @interface Person : NSObject
3 @property(assign,nonatomic) NSString *name;
4 @property(assign,nonatomic) int age;
5 // Person对象包含一个Dog对象,当调用Dog对象时,要对dog对象进行一次retain操作
6 @property(retain,nonatomic) Dog *dog;
7 @end
8 @implementation Person
9 - (void)setDog:(Dog *)dog
10 { // 判断dog是否为新dog
11 if (dog != _dog) {
12 // 是新dog则对老dog进行一次release操作
13 [_dog release];
14 // 对新dog进行一次retain操作
15 _dog = [dog retain];
16 }
17 }
18 // dealloc重写,用来release Person的Dog
19 - (void)dealloc
20 {
21 // 谁retain谁release,当_dog的值为nil、NULL、0时 对它发送release消息,没有任何意义
22 [_dog release];
23 // 重写dealloc要在最后面调用父类的dealloc方法[super dealloc]
24 [super dealloc];
25 }
26 @end
27 @interface Dog : NSObject
28 // Person和Dog互相调用,Person中用retain,Dog中用assign
29 @property(assign,nonatomic) Person *person;
30 @property(assign,nonatomic) NSString *name;
31 @end
32 @implementation Dog
33 // Dog没有对person进行retain操作,不用对它进行release
34 @end
35 int main()
36 {
37 // 对象的循环调用
38 Person *p1 = [[Person alloc] init]; // 引用计数器值为:1
39 Dog *d1 = [[Dog alloc] init]; // 引用计数器值为:1
40 Dog *d2 = [[Dog alloc] init]; // 引用计数器值为:1
41
42 p1.dog = d1; // d1引用计数器值为:2
43 d1.person = p1; // p1引用计数器值为:2
44 // p1的dog换成d2,在p1的set方法里对d1进行一次release,对d2进行一次retain。
45 p1.dog = d2; // d1引用计数器值为:1 d2引用计数器值为:2
46 [d1 release]; // d1引用计数器值为:0
47 [d2 release]; // d2引用计数器值为:1
48 [p1 release]; // p1引用计数器值为:0,d2引用计数器值为:0
49 return 0;
50 }
@class的使用
通常引用一个类有两种办法:
一种是通过#import方式引入;
1 #import <Foundation/Foundation.h>
2 #import "Book.h"
3
4 @interface Person : NSObject
5 {
6 Book *_book;
7 }
8
9 - (void)setBook:(Book *)book;
10 - (Book *)book;
11
12 @end
一种是通过@class引入
1、#import方式会包含被引用类的所有信息,包括被引用类的变量和方法;
2、@class方式只是告诉编译器在A.h文件中B *b只是类的声明,具体这个类里有什么信息,这里不需要知道,等实现文件中真正要用到时,才会真正去查看B类中信息;
3、使用@class方式由于只需要知道被引用类(B类)的名称就可以了,而在实现类由于要用到被引用类中的实体变量和方法,所以在.m文件中需要使用#import来包含被引用类的头文件;
4、如果有上百个头文件都#import了同一个文件,或者这些文件依次被#improt,那么一旦最开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍,这样的效率也是可想而知的,而相对来 讲,使用@class方式就不会出现这种问题了。
5.对于循环依赖关系来说,比方A类引用B类,同时B类也引用A类,B类的代码:
由上可知,@class是放在.h中的,只是在引用一个类,将这个被引用类作为一个类型,在.m实现文件中,如果需要引用到被引用类的实体变量或者方法时,还需要使用#import方式引入被引用类。
下来说一说,autorelease方法
1> 会将对象放到一个自动释放池中。
2> 当自动释放池被销毁时,会对池内所有对象做一次release操作。
3> autorelease方法会返回对象本身。
4> 调用autorelease方法之后,对象的计数器不变。
自动释放池
在程序运行过程中,可以创建多个自动释放池。自动释放池以栈的形式存在内存中(先进后出)。
当对象调用autorelease方法时,会将该对象放到栈顶的释放池。
创建自动释放池
1 int main()
2 {
3 // 创建自动释放池
4 @autoreleasepool{
5 // 创建对象,并调用autorelease方法
6 Person *p = [[[Person alloc] init] autorelease];
7 } // 自动释放池销毁,对象销毁
8
9 return 0;
10 }
一般当对象占用内存较大时,不要使用autorelease。调用autorelease之后,不用再使用release。
一般来说,除了alloc、new或copy之外的方法创建的对象都被声明了autorelease。
开发中可以提供一些类方法来快速创建已经autorelease的对象。
例如:
1 + (id)person
2 {
3 // 创建对象时,不直接使用类名,建议使用self
4 return [[[self alloc] init] autorelease]
5 }
ARC(Automatic Reference Counting)
在ARC模式下把指针分为两种
强指针:默认情况下,所有指针都是强指针__strong。
弱指针:__weak 。
ARC的判断准则:只要没有强指针指向对象,就会释放对象。
1 @interface Person : NSObject
2 @property(strong,nonatomic) Dog *dog;
3 @end
4
5 @implementation Person
6 // 重写dealloc
7 -(void)dealloc
8 {
9 NSLog(@"Person被杀死了");
10 // [super dealloc];不允许调用
11 }
12 @end
13
14 @interface Dog : NSObject
15 @property(weak,nonatomic) Person *person;
16 @end
17
18 @implementation Dog
19 - (void)dealloc
20 {
21 NSLog(@"Dog被杀死了");
22 }
23 @end
24
25 int main()
26 {
27 // ARC模式下的循环调用
28 Person *p = [[Person alloc] init];
29 Dog *d = [[Dog alloc] init];
30
31 p.dog = d;
32 d.person = p;
33
34 return 0;
35 }// 程序结束,所有对象被销毁
ARC特点
1> 不允许调用release、retain、retainCount。
2> 允许重写dealloc,但是不允许调用[super dealloc]。
3> @property参数
strong:成员变量是强指针(适用于OC对象类型)。
weak:成员变量是弱指针(适用于OC对象类型)。
assign:适用于非OC对象类型。
4> 用strong替代retain。
ARC中的循环引用:strong替代retain,weak替代assign 。
好了,对于OC内存管理的初步总结先写到这里,因为是初次学习,有错误请指正,感谢!