iOS-OC内存管理

2022-12-26,,

目标

1.【理解】内存管理

2.【掌握】第一个MRC程序

3.【掌握】内存管理的原则

4.【理解】野指针与僵尸对象

5.【理解】单个对象的内存管理

6.【理解】多个对象的内存管理

7.【掌握】set方法的内存管理

8.【掌握】@property参数

9.【掌握】@class关键字

10.【理解】循环retain

一、内存管理

程序在运行过程中会在堆空间创建大量的对象,当对象不再使用的时候,系统并不会自动释放堆空间中的对象(基本数据类型是由系统自动管理的)。如果一个对象创建并使用后没有得到及时释放,那么这对象会直到程序结束才会被释放,这样就会占用大量内存空间。其他高级语言如C#、Java都是通过垃圾回收机制(GC)来解决这个问题的,但在OC中并没有类似的垃圾回收机制,因此它的内存管理就需要由程序员手动维护。并且栈空间、BSS段、数据段、代码段中的数据都是由系统自动管理的,所以这些区域的数据不需要由程序员来管理,我们只需要管理堆空间中的数据,也就是OC对象的释放需要我们来管理。

引用计数器

OC中内存的管理是依赖对象引用计数器来进行的,在OC中每个对象内部都有一个与之对应的整数(retainCount),叫“引用计数器”。当一个对象在创建之后它的引用计数器默认值为1,如果一个对象的引用计数器为0,则系统会自动调用这个对象的dealloc方法来销毁这个对象。

内存泄露

一个对象在使用完没有回收,直到程序结束的时候才被回收,这种现象就叫做内存泄露。

如何操作对象的引用计数器?

调用对象的retain方法,对象的引用计数器就会+1。

调用对象的release方法,对象的引用计数器就会-1。

调用对象的retainCount方法,就会得到这个对象的引用计数器的值。

总结:

当我们要使用一个对象的时候,应该为这个对象发送一条retain消息。当我们不再使用一个对象的时候,就应该为这个对象发送一条release消息。当对象的引用计数器为0的时候,系统就会调用这个对象的dealloc方法来销毁这个对象,所以我们一般会重写dealloc方法来监控对象的销毁。

二、第一个MRC程序

OC内存管理的分为MRC(Manual Reference Counting)手动引用计数ARC(Automatic Reference Counting)自动引用计数。见名知意,MRC是程序员自己手动记录对象的引用计数,而ARC则是系统自动记录对象的引用计数。由于ARC机制是在Xcode4.2后推出的,所以默认情况下Xcode是自动开启ARC机制的,我们要使用MRC就必须关闭ARC。

为了便于监控对象释放,我们经常需要重写dealloc方法。并且重写dealloc方法需要遵循一些规范,在子类的dealloc方法最后必须调用父类的dealloc方法。

 

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

//Person.h文件
#import <Foundation/Foundation.h>
 
@interface Person : NSObject
 
@end
 
//Person.m文件
#import "Person.h"
 
@implementation Person
 
//重写dealloc方法
- (void)dealloc {
    NSLog(@"人挂了...");
    
    //在子类dealloc方法最后必须调用父类dealloc方法
    [super dealloc];
}
@end
 
//main.m文件
#import <Foundation/Foundation.h>
#import "Person.h"
 
int main(int argc, const char * argv[]) {
    
    //使用alloc创建对象,默认对象引用计数器的值为 1
    Person *p = [[Person alloc] init];
    
    //给对象发送release消息只是让引用计数器 -1,并不是一定会让对象引用计数器变为0
    [p release];
    //调用对象的dealloc方法,输出
    //人挂了...
    return 0;
}

三、内存管理的原则

只在有人使用这个对象的时候才为对象发送retain消息,只在一个人不再使用对象的时候才为对象发送release消息。遵守谁创建谁release,谁retain谁release的原则。

 

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

//Person.h文件
#import <Foundation/Foundation.h>
 
@interface Person : NSObject
 
@end
 
//Person.m文件
#import "Person.h"
 
@implementation Person
 
//重写dealloc方法
- (void)dealloc {
    NSLog(@"人挂了...");
    
    //在子类dealloc方法最后必须调用父类dealloc方法
    [super dealloc];
}
@end
 
//main.m文件
#import <Foundation/Foundation.h>
#import "Person.h"
 
int main(int argc, const char * argv[]) {
    
    //使用alloc创建对象,引用计数器 +1
    Person *p = [[Person alloc] init];
    
    //给对象发送一条retain消息,引用计数器 +1
    [p retain];
    
    //给p对象发送一条retainCount消息,返回引用计数器的值
    NSUInteger count = [p retainCount];
    NSLog(@"count = %lu",count);
    
    //给对象发送release消息只是让引用计数器 -1,并不是一定会让对象引用计数器变为0
    [p release];
    
    //再发送一条release,此时引用计数器就为0了
    [p release];
    //调用对象的dealloc方法,输出
    //人挂了...了...
    return 0;
}

注意:

1.为对象发送release消息不是让对象释放,而是让对象的引用计数器-1。

2.当对象引用计数器的值为0时,系统会立即调用对象的dealloc方法释放对象。

3.创建谁release,谁retain谁release。

四、野指针与僵尸对象

C语言中的野指针:一个指针变量指向一块随机的内存空间,也就是未初始化的指针变量,这个指针就叫做野指针。

OC语言中的野指针:指针指向的对象已经被释放了,这个指针就叫做野指针。

僵尸对象:已经被释放的对象,叫做僵尸对象,也就是通过野指针访问的对象就是僵尸对象。注意一旦一个指针成为了野指针,就不要通过野指针去访问对象的成员了。

每次通过一个指针去访问一个对象的时候,都会去检查这个对象是否是僵尸对象,如果是就报错。默认情况下Xcode是不会自动检测僵尸对象,那么如何开启Xcode对僵尸对象的自动检测?

当一个对象被回收后,就为指向这个对象的指针赋值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
36
37
38
39

//Person.h文件
#import <Foundation/Foundation.h>
 
@interface Person : NSObject
 
- (void)eat;
@end
 
//Person.m文件
#import "Person.h"
 
@implementation Person
- (void)eat {
    NSLog(@"人吃饭");
}
//重写dealloc方法
- (void)dealloc {
    NSLog(@"人挂了...");
    //调用父类dealloc方法
    [super dealloc];
}
@end
 
//main.m文件
#import <Foundation/Foundation.h>
#import "Person.h"
 
int main(int argc, const char * argv[]) {
    Person *p = [[Person alloc] init];
    //release让引用计数器 -1,此时对象引用计数器为0.对象被销毁
    [p release];
    
    //p指针赋值nil
    p = nil;
    
    //给p对象发送消息不会报错,只会啥都不发生
    [p eat];
    return 0;
}

五、单个对象的内存管理

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

#import <Foundation/Foundation.h>
#import "Person.h"
 
int main(int argc, const char * argv[]) {
    Person *p = [[Person alloc] init];//retainCount == 1
    [p retain];//retainCount == 2
    [p retain];//retainCount == 3
 
//    //只有retain没有release
    [p release];//retainCount == 2
    [p release];//retainCount == 1
    
//    //在不适当的时候为指针赋值nil
//    p = nil;//对象还没释放,就将指针赋值nil
    [p release];//retainCount == 0
    return 0;
}

六、多个对象的内存管理

当一个对象的属性是另一个对象的时候,在这个属性的setter方法中,将传入的对象先retain一次,再赋值给属性。并在dealloc方法中对属性进行一次release。

 

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

//Car.h文件
#import <Foundation/Foundation.h>
 
@interface Car : NSObject
 
- (void)run;
@end
 
//Car.m文件
#import "Car.h"
 
@implementation Car
-(void)run {
    NSLog(@"车启动");
}
 
- (void)dealloc {
    NSLog(@"车挂了...");
    
    //调用父类dealloc方法
    [super dealloc];
}
@end
 
//Person.h文件
#import <Foundation/Foundation.h>
#import "Car.h"
 
@interface Person : NSObject
@property Car *car;
 
- (void)drive;
@end
 
//Person.m文件
#import "Person.h"
 
@implementation Person
 
- (void)setCar:(Car *)car {
    //retain传入的对象,因为多了一个指针(_car属性)指向这个传入的对象
    //retain返回值是对象本身
    _car = [car retain];
}
 
- (void)drive {
    NSLog(@"车启动");
    //调用_car的run方法
    [self.car run];
}
 
- (void)dealloc {
    NSLog(@"人挂了...");
    //在人挂的时候让车也挂
    [self.car release];
    
    //调用父类dealloc方法
    [super dealloc];
}
@end
 
//main.m文件
#import <Foundation/Foundation.h>
#import "Person.h"
 
int main(int argc, const char * argv[]) {
    //实例化人和车对象
    Person *p = [[Person alloc] init];
    Car *bmw = [[Car alloc] init];
    
    //给人一辆车
    p.car = bmw;
    
    //后创建的对象先release
    [bmw release];
    bmw = nil;
 
    [p release];
    p = nil;
    return 0;
}

七、set方法的内存管理

 

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104

//Car.h文件
#import <Foundation/Foundation.h>
 
@interface Car : NSObject
@property int speed;
 
- (void)run;
@end
 
//Car.m文件
#import "Car.h"
 
@implementation Car
-(void)run {
    NSLog(@"车启动");
}
 
- (void)dealloc {
    NSLog(@"时速为%d车挂了...",_speed);
    
    [super dealloc];
}
@end
 
//Person.h文件
#import <Foundation/Foundation.h>
#import "Car.h"
 
@interface Person : NSObject
@property Car *car;
@property NSString *name;
- (void)drive;
@end
 
//Person.m文件
#import "Person.h"
 
@implementation Person
 
- (void)setCar:(Car *)car {
    //判断传入对象是否和属性的旧值是同一对象
    if (_car != car) {
        //不是同一对象就release旧值
        [_car release];
        //retain新值
        _car = [car retain];
    }
}
 
- (void)drive {
    //调用_car的run方法
    [_car run];
}
 
- (void)dealloc {
    NSLog(@"%@挂了...",_name);
    //对象释放前将属性指向的对象release一次,表示不指向那个对象了
    [_car release];
    
    [super dealloc];
}
@end
 
//main.m文件
#import <Foundation/Foundation.h>
#import "Person.h"
 
int main(int argc, const char * argv[]) {
    //实例化两个人对象
    Person *p1 = [[Person alloc] init];//p1的retainCount == 1
    Person *p2 = [[Person alloc] init];//p2的retainCount == 1
    //分别为两个人对象的_name赋值
    p1.name = @"jack";
    p2.name = @"rose";
    
    //实例化两个车对象
    Car *bmw = [[Car alloc] init];//bmw的retainCount == 1
    Car *benz = [[Car alloc] init];//benz的retainCount == 1
    //分别为两个车对象的_speed赋值
    bmw.speed = 100;
    benz.speed = 80;
    
    //p1指向bmw
    p1.car = bmw;//bmw的retainCount == 2
    
    //p2也指向bmw
    p2.car = bmw;//bmw的retainCount == 3
    
    //p2不指向bmw了,指向benz
    p2.car = benz;//bmw的retainCount == 2 , benz的retainCount == 2
    
    [benz release];//benz的retainCount == 1
    [p2 release];//p2的retainCount == 0 调用了p2的delloc方法,再调用benz的release. benz的retainCount == 0  所以rose挂了,benz也挂了
    [bmw release];//bmw的retainCount == 1
    [p1 release];//p1的retainCount == 0 调用了p1的delloc方法,再调用bmw的release. bmw的retainCount == 0  所以jack挂了,bmw也挂了
    
    /* 输出
    rose挂了...
    时速为80车挂了...
    jack挂了...
    时速为100车挂了...
     */
    return 0;
}

八、@property参数

@property的参数分为三类,中间用逗号分隔,每类参数可以从上图三类参数中任选一个。如果不进行设置或者只设置其中一类参数,程序会使用三类中的各类的默认参数,默认参数:(atomic,readwrite,assign)。

格式:@property ([参数1,参数2,参数3]) 数据类型 名称;    // []里表示可选

一般情况下如果在多线程开发中一个属性可能会被两个及两个以上的线程同时访问,此时可以考虑atomic属性,否则建议使用nonatomic,不加锁,效率较高。readwirte方法会生成getter、setter两个方法,如果使用readonly则只生成getter方法。关于setter方法处理需要特别说明,假设我们定义一个属性a,这里列出三种方式的生成代码:

assign,用于基本数据类型

 

1
2
3

- (void)setA:(int)a {
    _a = a;
}

retain,通常用于非字符串对象

 

1
2
3
4
5
6

- (void)setA:(Car *)a {
    if(_a != a){
        [_a release];
        _a = [a retain];
    }
}

copy,通常用于字符串对象

 

1
2
3
4
5
6

- (void)setA:(NSString *)a{
    if(_a != a){
        [_a release];
        _a = [a copy];
    }
}

我们可以在参数中指定@property生成的setter、getter的方法名称,一般情况下,当属性的类型是一个BOOL类型的时候,我们可以指定getter方法名以is开头,这样增强代码可阅读性。

 

1
2
3
4
5
6
7
8
9
10
11
12
13

@property (nonatomic,assign,readwrite,getter=isRich) int rich;
 
//生成的setter、getter方法声明
- (void)setRich:(BOOL)rich;
- (BOOL)isRich;
 
//生成的setter、getter方法实现
- (void)setRich:(BOOL)rich {
    
}
- (BOOL)isRich {
 
}

实际应用案例

 

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

//Book.h文件
#import <Foundation/Foundation.h>
 
@interface Book : NSObject
@property (nonatomic, copy) NSString *bookName;
@end
 
//Book.m文件
#import "Book.h"
 
@implementation Book
- (void)dealloc {
    NSLog(@"<<%@>>被烧了",_bookName);
    [_bookName release];
    
    [super dealloc];
}
@end
 
//Student.h文件
#import <Foundation/Foundation.h>
#import "Book.h"
 
@interface Student : NSObject
@property (nonatomic, retain) Book *book;
@end
 
//Student.m文件
#import "Student.h"
 
@implementation Student
- (void)dealloc {
    
    NSLog(@"<<%@>>",_book.bookName);
    [_book release];
    
    [super dealloc];
}
@end
 
//main.m文件
#import <Foundation/Foundation.h>
#import "Student.h"
 
int main(int argc, const char * argv[]) {
    Student *stu = [[Student alloc] init];//stu的retainCount == 1
    Book *book = [[Book alloc] init];//book的retainCount == 1
    
    stu.book = book;//book的retainCount == 2
    stu.book.bookName = @"风流少女";
    
    [book release];//book的retainCount == 1
    [stu release];//stu的retainCount == 0,调用stu的dealloc方法,再调用book的release,则book的retainCount == 0
    
    /*
     输出
     学生挂了
     <<风流少女>>被烧了
     */
    return 0;
}

九、@class关键字

当两个类相互包含的时候,就会造成死循环。在.h文件中不能包含和自己关联的那个类的头文件,因为一旦包含就会造成死循环。应该使用@class来声明一个类存在,如果在.m文件中需要使用用@class声明的类的成员时,就在.m文件中包含即可。

 

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

//Book.h文件
#import <Foundation/Foundation.h>
@class Student;//如果不需要使用类中的成员可以使用@class告诉编译器,Student是一个类
 
@interface Book : NSObject
@property (nonatomic, copy) NSString *bookName;
@property (nonatomic, assign) Student *owner;
 
- (void)showOwner;
@end
 
//Book.m文件
#import "Book.h"
#import "Student.h"
 
@implementation Book
- (void)showOwner {
    //使用了Student类的成员_name,直接包含头文件即可
    NSLog(@"%@的主人是%@",self.bookName,self.owner.name);
}
- (void)dealloc {
    NSLog(@"书挂");
    [_bookName release];
    [super dealloc];
}
@end
 
//Student.h文件
#import <Foundation/Foundation.h>
@class Book;//如果不需要使用类中的成员可以使用@class告诉编译器,Book是一个类
 
@interface Student : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, retain) Book *book;
 
- (void)showBook;
@end
 
//Student.m文件
#import "Student.h"
#import "Book.h"
 
@implementation Student
- (void)showBook {
    //使用了Book类的成员_bookName,直接包含头文件即可
    NSLog(@"%@的书名叫%@",self.name,self.Book.bookName);
}
- (void)dealloc {
    NSLog(@"学生挂");
    [_name release];
    [_book release];
    [super dealloc];
}
@end

十、循环retain

当两个类互相引用的时候,如果两个类的@property的参数都是retain就会出现循环引用造成内存泄露。解决办法是一个类使用retain,另一个类使用assign。使用assign的类不用在dealloc方法中对那个属性release。

 

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
62
63
64
65
66
67
68
69
70

//Book.h文件
#import <Foundation/Foundation.h>
@class Student;//告诉Book,Student是一个类
 
@interface Book : NSObject
@property (nonatomic, copy) NSString *bookName;
@property (nonatomic, assign) Student *owner;//使用assign
 
@end
 
//Book.m文件
#import "Book.h"
 
@implementation Book
 
- (void)dealloc {
    NSLog(@"<<%@>>释放",self.bookName);
    [super dealloc];
}
@end
 
//Student.h文件
#import <Foundation/Foundation.h>
@class Book;//告诉Student,Book是一个类
 
@interface Student : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, retain) Book *book;//使用retain
 
@end
 
//Student.m文件
#import "Student.h"
 
@implementation Student
 
- (void)dealloc {
    NSLog(@"<<%@>>释放了",self.name);
    [_book release];
    [super dealloc];
}
@end
 
//main.m文件
#import <Foundation/Foundation.h>
#import "Book.h"
#import "Student.h"
 
int main(int argc, const char * argv[]) {
    Book *book = [[Book alloc] init];//book的retainCount == 1
    book.bookName = @"风流少女";
    
    Student *student = [[Student alloc] init];//student的retainCount == 1
    student.name = @"牛逼哥";
    
    book.owner = student;//因为是assign,所以不会retain传入的student,则student的retainCount == 1
    student.book = book;//因为是retain,会retain传入的book,则book的retainCount == 2
    
    [student release];//student的retainCount == 0,会调用student的dealloc方法,再调用一次book的release。则book的retainCount == 1
    student = nil;
    
    [book release];//book的retainCount == 0
    book = nil;
    
    /* 输出
     <<牛逼哥>>释放了
     <<风流少女>>释放
     */
    return 0;
}

iOS-OC内存管理的相关教程结束。

《iOS-OC内存管理.doc》

下载本文的Word格式文档,以方便收藏与打印。