设为首页 加入收藏

TOP

Objective-C中的内存管理(一)
2015-07-16 12:04:05 来源: 作者: 【 】 浏览:101
Tags:Objective-C 内存 管理

IOS开发中, 之前一直使用swift, 因此对于Objective-C的内存管理机制长期处于混乱的一知半解状态. 今天终于看到一篇讲得透彻的博客Objective-C内存管理教程和原理剖析, 感谢作者.
本文只是个人的一个学习摘要, 更详细内容请参考原文.

基本的内存分配

OC的对象是在堆里边生成, 需要一个指针指向它.

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

使用完之后不会自动销毁, 需执行dealloc来销毁, 否则会出现内存泄露.

[obj1 dealloc];

那么看如下代码:

ClassA *obj1 = [[ClassA alloc] init];
ClassA *obj2 = obj1;
[obj1 hello];
[obj1 dealloc];
// obj1, obj2都是指针, 指向同一对象. 而[obj1 dealloc]已经销毁了该对象. 因此下边的代码无法执行, obj2是无效指针.
[obj2 hello];
[obj2 dealloc];

引用计数

为了避免出现上边的无效指针, OC采用引用计数的方式. 引用计数加1的有:alloc, retain, strong, 减1的有release. 当对象的引用计数为0的时候, 会自动调用dealloc.

ClassA *obj1 = [[ClassA alloc] init]; // 计数为1
[obj1 release]; // 计数为0, 自动dealloc
ClassA *obj1 = [[ClassA alloc] init]; // 计数为1
ClassA *obj2 = obj1; // 计数为1, 指针赋值不会增加引用计数
[obj1 hello];
[obj1 release]; // 计数为0, 自动dealloc
// 因对象已被dealloc, 故以下代码不会执行, obj2仍然是无效指针.
[obj2 hello];
[obj2 release];

那么如何解决obj2的无效指针问题呢? 通过retain使得引用计数加1.

ClassA *obj1 = [[ClassA alloc] init]; // 计数为1
ClassA *obj2 = obj1; // 计数为1
[obj2 retain]; // 计数为2
[obj1 hello];
[obj1 release]; // 计数为1
[obj2 hello];
[obj2 release]; // 计数为0, 自动dealloc

所以, 引用计数的关键在于当对象的引用计数为0时, 会自动调用dealloc销毁该对象. 指针赋值时, retain count不会自动增加, 需要手动retain.
另外, release一个对象之后, 应立即将指针清空(release一个空指针是合法的). 如:

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

autorelease

自动释放池其实是NSAutoreleasePool, 可以自动释放对象.

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

NSAutoreleasePool内部包含一个NSMutableArray, 用于保存声明为autorelease的所有对象.

ClassA *obj1 = [[[ClassA alloc] init] autorelease]; // 引用计数为1, 把对象加到autorelease pool中

下边看一下Apple的例子:
首先不用autorelease,

- (NSString *)fullName {
    NSString *string = [[NSString alloc] initWithFormat:@"%@ %@", self.firstName, self.lastName];
    return string;
}

则, 该string不能找到合适的时机释放, 因此会出现内存泄露.
那么, 采用autorelease之后,

- (NSString *)fullName {
    NSString *string = [[[NSString alloc] initWithFormat:@"%@ %@", self.firstName, self.lastName] autorelease];
    return string;
}

虽然, string不会立即释放, 但autorelease会在其release的时机将其释放.
或者采用更直接简便的方式:

- (NSString *)fullName {
    NSString *string = [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
    return string;
}

NSAutoreleasePool自身在销毁的时候, 会遍历并试图release其中的所有autorelease对象. 如果该对象的引用计数为1, 则将其引用计数减1, 销毁该对象; 如果该对象的引用计数大于1, 则autorelease之后其引用计数仍大于0, 对象未销毁, 出现内存泄露.
如在iOS工程的main.m文件中

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
   }
}

其实可以自行使用NSAutoreleasePool,

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
for (int i=0;i<100;i++) {
    for (int j=0;j<10000;j++) {
        // 产生autorelease对象
        [NSString stringWithFromat:@"1234567890"];
    }
}
[pool release];
return 0;

可以看出, 所有autorelease对象都只能在NSAutoreleasePool执行release的时候销毁, 显然不能很好地利用内存. 那么可以使用内嵌的NSAutoreleasePool, 代码优化如下:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
for (int i=0;i<100;i++) {
    NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
    for (int j=0;j<10000;j++) {
        // 产生autorelease对象
        [NSString stringWithFromat:@"1234567890"];
    }
    [loopPool release];
}
[pool release];
return 0;

这样, 就可以做到及时地释放内存.
因oc对象是动态内存, 没有栈的概念, 所以不能像C那样返回一个对象到栈, 只能用autorelease对象.
所以, NSAutoreleasePool的关键在于, 放置到其中的autorelease对象, 会在该pool release的时候自动销毁(绝大多数情况下, 都是正常的引用计数为1的autorelease对象). 因此对象本身就没必要调用release方法. 同时NSAutoreleasePool的release时机很重要.
不要滥用autorelease, 如对象的生命周期很清晰, 则结束使用后立即release. 过多等待autorelease对象浪费内存.

property中的关键字

property中的关键字其实全都是用于设置getter/setter的操作特性.

assign

assign相当于指针赋值, 不对引用计数进行操作, 如原对象不用了, 一定要将其设置为nil.
即: 调用setter方法时直接赋值, 不进行retain操作. 不改变引用计数.

copy

内容拷贝. 复制一个对象

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇Objective-c 创建类的使用 下一篇OC基础:继承.初始化方法,便利构造..

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: