内存学习

文章目录
  1. 1. 概述
  2. 2. 内存管理的思考方式
  3. 3. 所有权修饰符
    1. 3.1. __weak修饰符
    2. 3.2. __unsafe_unretained修饰符
    3. 3.3. __autorelease
  4. 4. 规则
  5. 5. ARC 实现

本文记录内存学习的关键内容

概述

对照明设备所做的动作 对Objective-C对象所做的动作
开灯 生产对象
需要照明 持有对象
不需要照明 释放对象
关灯 废弃对象

内存管理的思考方式

  • 自己生成的对象,自己持有
  • 非自己生成的对象,自己也能持有
  • 不再需要自己持有的对象时释放
  • 非自己持有的对象无法释放

对象操作与Objective-C方法的对应

对象操作 Objective-C方法
生成并持有对象 alloc/ new/ copy/ mutableCopy
持有对象 retain方法
是否对象 release 方法
废弃对象 dealloc方法

Cocoa框架中的Foundation框架中的NSObject类担负内存管理的职责。

生成对象的内部实现

1
2
3
4
5
6
7
-(id) allocObect
{
id obj = {[NSObject alloc] init];

///自己持有对象
return obj;
}
1
2
3
4
5
6
7
8
9
10
11
-(id) allocObect
{
id obj = {[NSObject alloc] init];

///自己持有对象

[obj autorelease];

///取得对象的存在,单自己不持有对象
return obj;
}

autorelease 使对象在超出指定的生存范围时,能够自动正确的释放。

GNU引用计数器是在对象的头部存储。

GNU实现总结:

  • 在Objective-C对象中存在引用计数器这一个整数值
  • 在调用alloc或者是retain方法后,应用计数器加1
  • 在调用release方法后,引用计数器减1
  • 引用计数器为0时,调用dealloc方法废弃对象。

苹果的实现:

保存在引用计数器表的记录中。好处可以追溯到各个对象的内存

autorelease的使用方法:

  • 生成并持有NSAutoreleasePool对象
  • 调用已分配对象的autorelease方法
  • 废弃NSAutoreleasePool对象

NSAutoreleasePool对象的生命周期相当于C语言变量的作用域

NSRunloop每次循环过程中NSAutoreleasePool对象被生成或者释放

所有权修饰符

所谓对象类型就是指向NSObject这样的Objective-C类的指针。id类型用于隐藏对象类型的类名部分。

  • __strong
  • __weak
  • __unsafe_unretained
  • __autoreleasing

__strong修饰符是id类型和对象类型默认的所有权修饰符

1
2
///arc有效
id __strong obj = [[NSObject alloc] init];

上面代码相当于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
///arc无效
id obj = [[NSObject alloc] init];

///变量obj 持有NSObject对象。
///一些操作
...



[obj release];
///释放对象, NSObject对象没有持有着,所以废弃

}

持有强引用的变量在超出作用域时被废弃,随着强引用的失效,引用的对象会随之释放。

__strong修饰符的变量,不仅只在变量的作用域中,在赋值上也能正确的管理其对象的所有者。

strong、weak、__autoreleasing 可以保证附加这些属性的自动变量初始化为nil.

一个非常经典的例子,专门收敲的啊。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
id __strong obj0 = [[NSObject alloc] init];  /*对象A*/
id __strong obj1 = [[NSObject alloc] init]; /*对象B*/
id __strong obj2 = nil;
/*变量obj0持有对象A
变量obj1持有对象B
变量obj2不持有任何对象
*/

obj0 = obj1;
/*
obj0持有obj1持有的对象B,由于obj0被赋值,所以原先对A对象的强引用失效
A对象不存在持有这,A对象释放。并废弃。
此时,持有B的的强引用变量为obj0 、obj1
*/

obj2 = obj0;
/*
obj2持有 由obj0赋值的对象B的强引用。
此时持有对象B的强引用变量为obj0、obj1、obj2
*/

obj1 = nil;
/*nil被赋予了obj1,所以对对象B的强引用失效,可以设想为对象B的引用计数目前为2
此时持有对象B的强引用变量为obj0、obj2
*/

obj0 = nil;
/*nil被赋予了obj0,所以对对象B的强引用失效,可以设想为对象B的引用计数目前为1
此时持有对象B的强引用变量为obj2
*/

obj2 = nil;
/*nil被赋予了obj2,所以对对象B的强引用失效,可以设想为对象B的引用计数目前为0
对象B不存在持有着,废弃对象B
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@interface Test : NSObject
{
id __strong obj_;
}

-(void) setObject:(id __strong) obj;
@end

@implementation Test
-(id)init
{
self = [super init];
return self;
}

-(void) setObject:(id __strong) obj
{
obj_ = obj;
}

@end

@implementation ViewController

- (void)viewDidLoad
{
[super viewDidLoad];

id test0 = [[Test alloc] init]; /*对象A*/
/*test0 持有Test对象A的强引用*/


id test1 = [[Test alloc] init]; /*对象B*/
/*test1 持有Test对象B的强引用变量*/

[test0 setObject:test1];
/*
Test对象A的obj_成员变量持有Test对象B的强应用,
此时持有对象B的强引用变量为:
test1、test0.obj_
*/

[test1 setObject:test0];
/*
对象B的obj_成员变量持有对象A的强引用
此时持有对象A的强引用变量为 test0、test1.obj_*/
}

/*
test0 超出作用域,强引用失效
所以自动释放对象A

test1 变量超出作用于,强引用失效
自动释放对象B

此时持有对象A的变量有test1.obj_

此时持有对象B的变量有test0.obj_

内存泄漏
*/

初始内存关系
上图是对象A和对象B内存持有状态

强引用

test0 超出作用域,强引用失效,所以自动释放对象A,A对象还有B的obj_持有,不能废弃。

test1 变量超出作用于,强引用失效,自动释放对象B(计数器减2->1)。对象B还有对象A的obj_属性持有,引用计数器为1,不能废弃

最后对象A、B 在超出作用域没有释放,内存泄漏。

__weak修饰符

__weak修饰符不能持有对象实例。

weak修饰符还有一个优点,在持有某对象的弱引用时,若改对象废弃,则次弱引用将自动失效且被赋值为nil。 通过检查附有weak修饰符的变量是否为nil,可以判断被赋值的对象是否已经被废弃。

__unsafe_unretained修饰符

尽管ARC式的内存管理是编译器的工作,但附有unsafe_unretained修饰符的变量不属于编译器的内存管理对象 unsafe_unretained

__autorelease

ARC有效时,autorelease功能也是起作用的。

1
2
3
@autoreleasepool{
id __weak obj = [[NSObject alloc] init];
}

对象赋值给附有__autorelease修饰符的变量等价于在ARC无效是调用对象的autorelease方法。即对象被注册到autoreleasepool中。用@autoreleasepool快代替NSAutoreleasePool

显示地附加autorelease修饰符同显示地附加strong修饰符一样罕见

id指针或对象的指针的指针在没有显示指定时,会被附加上__autoreleasing修饰符。

1
2
3
4
 - (BOOL) performOperationWithError:(NSError **) error;

///等同于
- (BOOL) performOperationWithError:(NSError * __autoreleasing*) error;

无论ARC是否有效,@autoreleasepool和 调试用的非公开函数_obj_autoreleasePoolPrint()都可以使用。

规则

  1. 不能使用retain/release/retainCount/autorelease
  2. 不能使用NSAllocateObject/NSDeallocateObject
  3. 必须准守内存内存管理的方法命名规则
  4. 不能显示调用dealloc
  5. 使用@autoreleasepool快代替NSAutoreleasePool
  6. 不能使用区域NSZone
  7. 对象型变量不能作为C语言结构体(struct/union)成员
  8. 显示的转化id和void*

内存管理命名方法规则

  1. alloc
  2. new
  3. copy
  4. mutableCopy

在arc有效的时候,追加一条 init

以init开始的方法规则要不alloc/new/copy/mutableCopy更严格,改方法必须是实例方法并且必须返回对象,返回的对象应为id类型或者该方法声明类的对象类型,亦或该类的超类或者子类,返回的对象并不注册到autoreleasepool中,基本上是对alloc方法返回的对象进行初始化并返回该对象。

在arc无效的时候在dealloc方法中需要显示的调用[super dealloc] ,arc有效的时候不能调用,如果调用了,违反了第4条规则。

struct Data
{
NSObject __unsafe_unretained *ary;
};

要把对象添加到结构体成员中时,可强制转换为void* 或者加上unsafe_unretained修饰符。unsafe_unretained修饰符修饰的变量不属于编译器内存管理对象。

ARC 实现

ARC是由编译器进行内存管理的