放
p = nil; // 此时,p变为了空指针
[p release]; // 再给空指针p发送消息就不会报错了
[p release];
}
return 0;
}
5. 内存管理规律
单个对象内存管理规律
- 谁创建谁release :
- 如果你通过alloc、new、copy或mutableCopy来创建一个对象,那么你必须调用release或autorelease
- 谁retain谁release:
- 只要你调用了retain,就必须调用一次release
- 总结一下就是
- 有加就有减
- 曾经让对象的计数器+1,就必须在最后让对象计数器-1
多个对象内存管理规律
因为多个对象之间往往是联系的,所以管理起来比较复杂。这里用一个玩游戏例子来类比一下。
游戏可以提供给玩家(A类对象) 游戏房间(B类对象)来玩游戏。
- 只要一个玩家想使用房间(进入房间),就需要对这个房间的引用计数器+1
- 只要一个玩家不想再使用房间(离开房间),就需要对这个房间的引用计数器-1
- 只要还有至少一个玩家在用某个房间,那么这个房间就不会被回收,引用计数至少为1
下面来定义两个类 玩家类:Person 和 房间类:Room
房间类:Room,房间类中有房间号
#import <Foundation/Foundation.h>
@interface Room : NSObject
@property int no; // 房间号
@end
玩家类:Person
#import <Foundation/Foundation.h>
#import "Room.h"
@interface Person : NSObject
{
Room *_room;
}
- (void)setRoom:(Room *)room;
- (Room *)room;
@end
现在我们通过几个玩家使用房间的不同应用场景来逐步深入理解内存管理。
1. 玩家没有使用房间,玩家和房间之间没有联系的情况
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1.创建两个对象
Person *p = [[Person alloc] init]; // 玩家 p
Room *r = [[Room alloc] init]; // 房间 r
r.no = 888; // 房间号赋值
[r release]; // 释放房间
[p release]; // 释放玩家
}
return 0;
}
上述代码执行完前3行
// 1.创建两个对象
Person *p = [[Person alloc] init]; // 玩家 p
Room *r = [[Room alloc] init]; // 房间 r
r.no = 888; // 房间号赋值
之后在内存中的表现如下图所示:
可见,Room实例对象和Person实例对象之间没有相互联系,所以各自释放不会报错。执行完4、5行代码
[r release]; // 释放房间
[p release]; // 释放玩家
后,将房间对象和玩家对象各自释放掉,在内存中的表现如下图所示:
最后各自实例对象的内存就会被系统回收
2. 一个玩家使用一个游戏房间,玩家和房间之间相关联的情况
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1.创建两个对象
Person *p = [[Person alloc] init]; // 玩家 p
Room *r = [[Room alloc] init]; // 房间 r
r.no = 888; // 房间号赋值
// 将房间赋值给玩家,表示玩家在使用房间
// 玩家需要使用这间房,只要玩家在,房间就一定要在
p.room = r; // [p setRoom:r]
[r release]; // 释放房间
// 在这行代码之前,玩家都没有被释放,但是因为玩家还在,那么房间就不能销毁
NSLog(@"-----");
[p release]; // 释放玩家
}
return 0;
}
上边代码执行完前3行的时候和之前在内存中的表现一样,如图
当执行完第4行代码p.room = r;
时,因为调用了setter方法,将Room实例对象赋值给了Person的成员变量,不做其他设置的话,在内存中的表现如下图(做法不对):
在调用setter方法的时候,因为Room实例对象多了一个Person对象引用,所以应将Room实例对象的引用计数+1才对,即setter方法应该像下边一样,对room进行一次retain操作。
- (void)setRoom:(Room *)room // room = r
{
// 对房间的引用计数器+1
[room retain];
_room = room;
}
那么执行完第4行代码p.room = r;
,在内存中的表现为:
继续执行第5行代码
[r release];
,释放房间,Room实例对象引用计数-1,在内存中的表现如下图所示:
然后执行第6行代码[p release];
,释放玩家。这时候因为玩家不在房间里了,房间也没有用了,所以在释放玩家的时候,要把房间也释放掉,也就是在delloc里边对房间再进行一次release操作。
这样对房间对象来说,每一次retain/alloc操作都对应一次release操作。
- (void)dealloc
{
// 人释放了, 那么房间也需要释放
[_room release];
NSLog(@"%s", __func__);
[super dealloc];
}
那么在内存中的表现最终如下图所示:
最后实例对象的内存就会被系统回收
3. 一个玩家使用一个游戏房间r后,换到另一个游戏房间r2,玩家和房间相关联的情况
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1.创建两个对象
Person *p = [[Person alloc] init]; // 玩家 p
Room *r = [[Room alloc] init]; // 房间 r
r.no = 888; // 房间号赋值
// 2.将房间赋值给玩家,表示玩家在使用房间
p.room = r; // [p setRoom:r]
[r release]; // 释放房间 r
// 3. 换房
Room *r2 = [[Room alloc] init];
r2.no = 444;
p.room = r2;
[r2 release]; // 释放房间 r2
[p release]; // 释放玩家 p
}
return 0;
}
执行下边几行代码
// 1.创建两个对象
Person *p = [[Person alloc] init]; // 玩家 p
Room *r = [[Room alloc] init]; // 房间 r
r.no = 888; // 房间号赋值
// 2.将房间赋值给玩家,表示玩