Objective-C内存管理[iOS]

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

新博客文章地址:Objective-C内存管理
CSDN文章地址: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 方法的实现

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

4.2 reset 方法的实现

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

4.3 init 方法的实现

1
2
3
4
5
6
7
- init {
self = [super init];
if (self) {
_count = [[NSNumber alloc] initWithInteger:0];
}
return self;
}
1
2
3
4
5
6
7
- initWithCount:(NSNumber *)startingCount {
self = [super init];
if (self) {
_count = [startingCount copy];
}
return self;
}

4.4 dealloc方法的实现

1
2
3
4
- (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类型)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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类

1
2
3
4
5
#import "B.h"
@interface A : NSObject {
B *b;
}
@end
1
2
3
4
5
#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前
1
2
3
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

[pool release]; // [pool drain];
  • iOS 5.0后
1
2
3
4
@autoreleasepool
{

}
  • 在程序运行过程中,可以创建多个自动释放池,它们是以栈的形式存在内存中
  • Objective-C对象只需要发送一条 autorelease 消息,就会把这个对象添加到最近的自动释放池中(栈顶的释放池)

7.3 应用实例

  • release 的对比

以前:

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

现在:

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

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

7.4 规律

  • 一般来说除了 alloc, newcopy 之外的方法创建的对象都被声明了 autorelease
  • 比如下面的对象都已经是 autorelease 的,不需要再 release
1
2
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

1
2
3
4
5
6
7
8
@autoreleasepool
{
// 1
Person *p = [[[Person alloc] init] autorelease];

// 0
[p release];
}

2. 连续调用多次 autorelease

1
2
3
4
@autoreleasepool
{
Person *p = [[[[Person alloc] init] autorelease] autorelease];
}
文章目录
  1. 1. 1 基本原理
    1. 1.1. 1.1 什么是内存管理
    2. 1.2. 1.2 对象的基本结构
    3. 1.3. 1.3 引用计数器的作用
    4. 1.4. 1.4 引用计数器的操作
    5. 1.5. 1.5 对象的销毁
  2. 2. 2 Xcode 设置
  3. 3. 3 内存管理原则
    1. 3.1. 3.1 原则分析
    2. 3.2. 3.2 谁创建,谁 release
    3. 3.3. 3.3 谁 retain ,谁 release
    4. 3.4. 3.4 总结
  4. 4. 4 set 方法的内存管理
    1. 4.1. 4.1 set 方法的实现
    2. 4.2. 4.2 reset 方法的实现
    3. 4.3. 4.3 init 方法的实现
    4. 4.4. 4.4 dealloc方法的实现
  5. 5. 5 @property 的内存管理
    1. 5.1. 5.1 @property 参数
      1. 5.1.1. 1.set方法内存管理相关的参数
      2. 5.1.2. 2.是否要生成set方法
      3. 5.1.3. 3.多线程管理
      4. 5.1.4. 4.setter和getter方法的名称
  6. 6. 6 循环引用
    1. 6.1. 6.1 @class
    2. 6.2. 6.2 循环 retain
    3. 6.3. 6.3 解决方案
  7. 7. 7 autorelease
    1. 7.1. 7.1 autorelease 的基本用法
    2. 7.2. 7.2 自动释放池的创建
    3. 7.3. 7.3 应用实例
    4. 7.4. 7.4 规律
    5. 7.5. 7.5 autorelease 的好处
    6. 7.6. 7.6 autorelease 的使用注意
    7. 7.7. 7.7 错误写法
      1. 7.7.1. 1. alloc之后调用了 autorelease,又调用 release
      2. 7.7.2. 2. 连续调用多次 autorelease