标签:
转自:http://www.jianshu.com/p/48665652e4e4
所以,我们需要对内存进行合理的分配内存、清除内存,回收那些不需要再使用的对象。从而保证程序的稳定性。
那么,那些对象才需要我们进行内存管理呢?
这是因为
堆
里边。堆
:一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,分配方式类似于链表栈
里面栈
:由操作系统自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈(先进后出)int main(int argc, const char * argv[])
{
@autoreleasepool {
int a = 10; // 栈
int b = 20; // 栈
// p : 栈
// Person对象(计数器==1) : 堆
Person *p = [[Person alloc] init];
}
// 经过上面代码后, 栈里面的变量a、b、p 都会被回收
// 但是堆里面的Person对象还会留在内存中,因为它是计数器依然是1
return 0;
}
提供给Objective-C程序员的基本内存管理模型有以下3种:
系统是根据对象的引用计数器来判断什么时候需要回收一个对象所占用的内存
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 只要创建一个对象默认引用计数器的值就是1
Person *p = [[Person alloc] init];
NSLog(@"retainCount = %lu", [p retainCount]); // 1
// 只要给对象发送一个retain消息, 对象的引用计数器就会+1
[p retain];
NSLog(@"retainCount = %lu", [p retainCount]); // 2
// 通过指针变量p,给p指向的对象发送一条release消息
// 只要对象接收到release消息, 引用计数器就会-1
// 只要一个对象的引用计数器为0, 系统就会释放对象
[p release];
// 需要注意的是: release并不代表销毁\回收对象, 仅仅是计数器-1
NSLog(@"retainCount = %lu", [p retainCount]); // 1
[p release]; // 0
NSLog(@"--------");
}
// [p setAge:20]; // 此时对象已经被释放
return 0;
}
- (void)dealloc
{
NSLog(@"Person dealloc");
// 注意:super dealloc一定要写到所有代码的最后
// 一定要写在dealloc方法的最后面
[super dealloc];
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init]; // 执行完引用计数为1
[p release]; // 执行完引用计数为0,实例对象被释放
[p release]; // 此时,p就变成了野指针,再给野指针p发送消息就会报错
[p release];
}
return 0;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init]; // 执行完引用计数为1
[p release]; // 执行完引用计数为0,实例对象被释放
p = nil; // 此时,p变为了空指针
[p release]; // 再给空指针p发送消息就不会报错了
[p release];
}
return 0;
}
因为多个对象之间往往是联系的,所以管理起来比较复杂。这里用一个玩游戏例子来类比一下。
游戏可以提供给玩家(A类对象) 游戏房间(B类对象)来玩游戏。
下面来定义两个类 玩家类: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
现在我们通过几个玩家使用房间的不同应用场景来逐步深入理解内存管理。
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]; // 释放玩家
后,将房间对象和玩家对象各自释放掉,在内存中的表现如下图所示:
最后各自实例对象的内存就会被系统回收
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];
}
那么在内存中的表现最终如下图所示:
最后实例对象的内存就会被系统回收
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.将房间赋值给玩家,表示玩家在使用房间
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
内存的表现为:
可以看出房间 r 并没有被释放,这是因为在进行换房的时候,并没有对房间 r 进行释放。所以应在调用setter方法的时候,对之前的变量进行一次release操作。具体setter方法代码如下:
- (void)setRoom:(Room *)room // room = r
{
// 将以前的房间释放掉 -1
[_room release];
// 对房间的引用计数器+1
[room retain];
_room = room;
}
}
这样在执行完p.room = r2;
之后就会将 房间 r 释放掉,最终内存表现为:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1.创建两个对象
Person *p = [[Person alloc] init];
Room *r = [[Room alloc] init];
r.no = 888;
// 2.将房间赋值给人
p.room = r; // [p setRoom:r]
[r release]; // 释放房间 r
// 3.再次使用房间 r
p.room = r;
[r release]; // 释放房间 r
[p release]; // 释放玩家 p
}
return 0;
}
执行下面代码
// 1.创建两个对象
Person *p = [[Person alloc] init];
Room *r = [[Room alloc] init];
r.no = 888;
// 2.将房间赋值给人
p.room = r; // [p setRoom:r]
[r release]; // 释放房间 r
之后的内存表现为:
然后再执行p.room = r;
,因为setter方法会将之前的Room实例对象先release掉,此时内存表现为:
此时_room、r 已经变成了一个野指针。之后再对野指针 r 发出retain消息,程序就会崩溃。所以我们在进行setter方法的时候,要先判断一下是否是重复赋值,如果是同一个实例对象,就不需要重复进行release和retain。换句话说,如果我们使用的还是之前的房间,那换房的时候就不需要对这个房间再进行release和retain。则setter方法具体代码如下:
- (void)setRoom:(Room *)room // room = r
{
// 只有房间不同才需用release和retain
if (_room != room) { // 0ffe1 != 0ffe1
// 将以前的房间释放掉 -1
[_room release];
// 对房间的引用计数器+1
[room retain];
_room = room;
}
}
因为retain不仅仅会对引用计数器+1, 而且还会返回当前对象,所以上述代码可最终简化成:
- (void)setRoom:(Room *)room // room = r
{
// 只有房间不同才需用release和retain
if (_room != room) { // 0ffe1 != 0ffe1
// 将以前的房间释放掉 -1
[_room release];
_room = [room retain];
}
}
以上就是setter方法的最终形式。
@property (nonatomic) int val;
@property(nonatomic, retain) Room *room;
@property(nonatomic, retain) int val;
当我们不再使用一个对象的时候应该将其空间释放,但是有时候我们不知道何时应该将其释放。为了解决这个问题,Objective-C提供了autorelease方法。
所有对象做一次release操作
注意,这里只是发送release消息,如果当时的引用计数(reference-counted)依然不为0,则该对象依然不会被释放。
Person *p = [Person new];
p = [p autorelease];
NSLog(@"count = %lu", [p retainCount]); // 计数还为1
autorelease实际上只是把对release的调用延迟了,对于每一个autorelease,系统只是把该对象放入了当前的autorelease pool中,当该pool被释放时,该pool中的所有对象会被调用release。
使用NSAutoreleasePool来创建
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // 创建自动释放池
[pool release]; // [pool drain]; 销毁自动释放池
使用@autoreleasepool创建
@autoreleasepool
{ //开始代表创建自动释放池
} //结束代表销毁自动释放池
NSAutoreleasePool *autoreleasePool = [[NSAutoreleasePool alloc] init];
Person *p = [[[Person alloc] init] autorelease];
[autoreleasePool drain];
@autoreleasepool
{ // 创建一个自动释放池
Person *p = [[Person new] autorelease];
// 将代码写到这里就放入了自动释放池
} // 销毁自动释放池(会给池子中所有对象发送一条release消息)
@autoreleasepool {
// 因为没有调用 autorelease 方法,所以对象没有加入到自动释放池
Person *p = [[Person alloc] init];
[p run];
}
@autoreleasepool {
}
// 没有与之对应的自动释放池, 只有在自动释放池中调用autorelease才会放到释放池
Person *p = [[[Person alloc] init] autorelease];
[p run];
// 正确写法
@autoreleasepool {
Person *p = [[[Person alloc] init] autorelease];
}
// 正确写法
Person *p = [[Person alloc] init];
@autoreleasepool {
[p autorelease];
}
栈顶就是离调用autorelease方法最近的自动释放池
@autoreleasepool { // 栈底自动释放池
@autoreleasepool {
@autoreleasepool { // 栈顶自动释放池
Person *p = [[[Person alloc] init] autorelease];
}
Person *p = [[[Person alloc] init] autorelease];
}
}
// 内存暴涨
@autoreleasepool {
for (int i = 0; i < 99999; ++i) {
Person *p = [[[Person alloc] init] autorelease];
}
}
// 内存不会暴涨
for (int i = 0; i < 99999; ++i) {
@autoreleasepool {
Person *p = [[[Person alloc] init] autorelease];
}
}
@autoreleasepool {
// 错误写法, 过度释放
Person *p = [[[[Person alloc] init] autorelease] autorelease];
}
@autoreleasepool {
Person *p = [[[Person alloc] init] autorelease];
[p release]; // 错误写法, 过度释放
}
定义两个类Person类和Dog类
#import <Foundation/Foundation.h>
@class Dog;
@interface Person : NSObject
@property(nonatomic, retain)Dog *dog;
@end
#import <Foundation/Foundation.h>
@class Person;
@interface Dog : NSObject
@property(nonatomic, retain)Person *owner;
@end
执行以下代码:
int main(int argc, const char * argv[]) {
Person *p = [Person new];
Dog *d = [Dog new];
p.dog = d; // retain
d.owner = p; // retain assign
[p release];
[d release];
return 0;
}
就会出现A对象要拥有B对象,而B对应又要拥有A对象,此时会形成循环retain,导致A对象和B对象永远无法释放
那么如何解决这个问题呢?
ARC判断一个对象是否需要释放不是通过引用计数来进行判断的,而是通过强指针
来进行判断的。那么什么是强指针
?
Person *p1 = [[Person alloc] init];
__strong Person *p2 = [[Person alloc] init];
__weak Person *p = [[Person alloc] init];
ARC如何通过强指针来判断?
int main(int argc, const char * argv[]) {
// 不用写release, main函数执行完毕后p会被自动释放
Person *p = [[Person alloc] init];
return 0;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
} // 执行到这一行局部变量p释放
// 由于没有强指针指向对象, 所以对象也释放
return 0;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
p = nil; // 执行到这一行, 由于没有强指针指向对象, 所以对象被释放
}
return 0;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// p1和p2都是强指针
Person *p1 = [[Person alloc] init];
__strong Person *p2 = [[Person alloc] init];
}
return 0;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// p是弱指针, 对象会被立即释放
__weak Person *p1 = [[Person alloc] init];
}
return 0;
}
@interface Person : NSObject
// MRC写法
//@property (nonatomic, retain) Dog *dog;
// ARC写法
@property (nonatomic, strong) Dog *dog;
@end
@interface Person : NSObject
@property (nonatomic, strong) Dog *dog;
@end
@interface Dog : NSObject
// 错误写法, 循环引用会导致内存泄露
//@property (nonatomic, strong) Person *owner;
// 正确写法, 当如果保存对象建议使用weak
@property (nonatomic, weak) Person *owner;
@end
标签:
原文地址:http://www.cnblogs.com/wanglizhi/p/5725897.html