Objective-C内存管理[iOS]

来源:互联网 发布:mac怎么装win10 编辑:程序博客网 时间:2024/06/05 02:03

版权声明:本文为博主原创,如需转载请注明出处。

新博客文章地址:Objective-C内存管理



1 基本原理

1.1 什么是内存管理

  • 移动设备的内存极其有限,每个 app 所能占用的内存是有限制的
  • 当app所占用的内存较多时,系统会发出内存警告,这时得回收一些不需要再使用的内存空间。比如回收一些不需要使用的对象、变量等。
  • 管理范围:任何继承了 NSObject 的对象,对其他基本数据类型(如int, char, float, double, struct, enum 等)无效

1.2 对象的基本结构

  • 每个对象都有一个引用计数器,是一个整数,表示“对象被引用的次数”,即有多少人正在使用这个Objective-C对象。
  • 每个Objective-C对象内部专门有4个字节的存储空间来存储引用计数器。

1.3 引用计数器的作用

  • 当使用 alloc, new 或者 copy 创建一个新对象时,新兑现的引用计数器默认就是 1。
  • 当一个对象的引用计数器值为 0 时,对象占用的内存就是被系统回收,换句话说,如果对象的计数器不为 0,那么在整个程序运行过程中,它占用的内存就不可能被回收,除非整个程序已经退出。

1.4 引用计数器的操作

  • 给对象发送一个 reatin 消息,可以使引用计数器值 +1 (retain 方法返回对象本身)
  • 给对象发送一个 release 消息,可以使引用计数器值 -1
  • 可以给对象发送 retainCount 消息,获得当前的引用计数器值

1.5 对象的销毁

  • 当一个对象的引用计数器值为 0 时,那么它将被销毁,其占用的内存被系统回收
  • 当一个对象被销毁时,系统会自动向对象发送一条 dealloc 消息
  • 一般会重写 dealloc 方法,在这里释放相关资源,dealloc 就像对象的遗言
  • 一旦重写了 dealloc 方法,就必须调用 [super dealloc],并且放在最后面调用
  • 不要直接调用 dealloc 方法

2 Xcode 设置

对野指针发送消息 这种错误行为在进行时进行报错,需要设置 Xcode





为了确定你的代码在编译时出现问题,则可以使用 Clang Static Analyzer ,内置在Xcode中。

  • 许多工具和技术的技术说明在 Technical Note TN2239 中有描述,iOS Debugging Magic ,特别是使用的 NSZombie ,以帮助找到还未释放的对象。
  • 您可以使用仪器来跟踪引用计数的事件,并查找内存泄漏。请参阅 Collecting Data on Your App

3 内存管理原则

3.1 原则分析

  • 只要还有人在用某个对象,那么这个对象就不会被回收
  • 只要你想用这个对象,就让对象的计数器 +1
  • 当你不再使用这个对象时,就让对象的计数器 -1

3.2 谁创建,谁 release

  • 如果你通过 alloc, new 或 [mutable]copy 来创建一个对象,那么你必须调用 releaseautorelease
  • 换句话说,不是你创建的,就不用你去 [auto]release

3.3 谁 retain ,谁 release

  • 只要你调用了 retain ,无论这个对象是如何生成的,你都要调用 relaese

3.4 总结

  • 有始有终, 有加就有减
  • 曾经让对象的计数器+1, 就必须在最后让对象计数器-1

4 set 方法的内存管理

如果你有个Objective-C对象类型的成员变量 ,就必须管理这个成员变量的内容,比如有个 NSNumber *count

4.1 set 方法的实现

@interface Counter : NSObject@property (nonatomic, retain) NSNumber *count;@end;
- (NSNumber *)count {    return _count;}
- (void)setCount:(NSNumber *)newCount {    [newCount retain];    [_count release];    // Make the new assignment.    _count = newCount;}

4.2 reset 方法的实现

- (void)reset {    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];    [self setCount:zero];    [zero release];}
- (void)reset {    NSNumber *zero = [NSNumber numberWithInteger:0];    [self setCount:zero];}

4.3 init 方法的实现

- init {    self = [super init];    if (self) {        _count = [[NSNumber alloc] initWithInteger:0];    }    return self;}
- initWithCount:(NSNumber *)startingCount {    self = [super init];    if (self) {        _count = [startingCount copy];    }    return self;}

4.4 dealloc方法的实现

- (void)dealloc {    [_count release];    [super dealloc];}

5 @property 的内存管理

5.1 @property 参数

1.set方法内存管理相关的参数

  • retain : release旧值,retain新值(适用于OC对象类型)
  • assign : 直接赋值(默认,适用于非OC对象类型)
  • copy : release旧值,copy新值

2.是否要生成set方法

  • readwrite : 同时生成setter和getter的声明、实现(默认)
  • readonly : 只会生成getter的声明、实现

3.多线程管理

  • nonatomic : 性能高 (一般就用这个)
  • atomic : 性能低(默认)

4.setter和getter方法的名称

  • setter : 决定了set方法的名称,一定要有个冒号 :
  • getter : 决定了get方法的名称(一般用在BOOL类型)
@interface Person : NSObject// 返回BOOL类型的方法名一般以is开头@property (getter = isRich) BOOL rich;//   @property (nonatomic, assign, readwrite) int weight;// setWeight:// weight// @property (readwrite, assign) int height;@property (nonatomic, assign) int age;@property (retain) NSString *name;@end

6 循环引用

6.1 @class

  • 使用场景

对于循环依赖关系来说,比方A类引用B类,同时B类也引用A类

#import "B.h"@interface A : NSObject {    B *b;}@end
#import "A.h"@interface B : NSObject {    A *a;}@end

这种代码编译会报错,当使用@class 在两个类相互声明,就不会出现编译报错

  • 用法概括

使用 @class 类名;就可以引用一个类,说明一下它是一个类

  • 和 #import区别

#import 方法会包含被引用类的所有信息,包括被引用类的变量和方法;@class 方式只是告诉编译器在 A.h 文件中 B *b 只是类的声明,具体这个类有什么信息,这里不需要知道,等实现文件中真正要用到的时候,才会真正去查看B类中的信息。

如果有上百个头文件都 #import 了同一个文件,或者这些文件依次被#import,那么一旦最开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍,这样的效率比较差,相对来说,使用 @class 方式就不会出现这种问题了。

.m 实现文件中,如果需要引用到被引用类的实体变量或者方法时,还需要使用 #import 方式引入被引用类。

6.2 循环 retain

  • 比如 A 对象 retain 了 B 对象, B 对象 retain 了 A 对象
  • 这样会导致 A 对象 和 B 对象永远无法释放

6.3 解决方案

  • 当两端互相引用时,应该一端用 retain , 一端用 assign

7 autorelease

7.1 autorelease 的基本用法

  • 给某个对象发送一条 autorelease 消息时,就会将这个对象加到一个自动释放池中
  • 当自动释放池销毁时,会给池子里面的所有对象发送一条 release 消息
  • 调用 autorelease 方法时并不会改变对象的计数器,并且会返回对象本身
  • autorelease 实际上只是把对 release 的调用延迟了,对于每一次 autorelease ,系统只是把该对象放入了当前的 autorelease pool 中,当该 pool 被释放时,该 pool 中的所有对象会被调用 release

7.2 自动释放池的创建

  • iOS 5.0前
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];[pool release]; // [pool drain];
  • iOS 5.0后
@autoreleasepool{}
  • 在程序运行过程中,可以创建多个自动释放池,它们是以栈的形式存在内存中
  • Objective-C对象只需要发送一条 autorelease 消息,就会把这个对象添加到最近的自动释放池中(栈顶的释放池)

7.3 应用实例

  • release 的对比

以前:

Book *book = [[Book alloc] init];[book release];

现在:

Book *book = [[[Book alloc] init] autorelease];// 不要再调用 [book release];
  • 一般可以为类添加一个快速创建对象的类方法
+ (id)book {    return [[[self alloc] init] autorelease];    // 上面这行本来可以写成 return [[[Book alloc] init] autorelease];    // 但是这样无法满足Book子类的创建需求,所以最好都写成 self }

外界调用 [Book book] 时,根本不用考虑在什么时候释放返回的 Book 对象。

7.4 规律

  • 一般来说除了 alloc, newcopy 之外的方法创建的对象都被声明了 autorelease
  • 比如下面的对象都已经是 autorelease 的,不需要再 release
NSNumber *n = [NSNumber numberWithInt:100];NSString *s = [NSString stringWithFormat:@"jack"];

7.5 autorelease 的好处

  • 不用再关心对象释放的时间
  • 不用再关心什么时候调用release

7.6 autorelease 的使用注意

  • 占用内存较大的对象不要随便使用 autorelease
  • 占用内存较小的对象使用 autorelease,没有太大影响

7.7 错误写法

1. alloc之后调用了 autorelease,又调用 release

@autoreleasepool{   // 1   Person *p = [[[Person alloc] init] autorelease];   // 0   [p release];}

2. 连续调用多次 autorelease

@autoreleasepool{   Person *p = [[[[Person alloc] init] autorelease] autorelease];}
0 0
原创粉丝点击