OC学习篇之---内存管理介绍和使用

在之前的一片文章我们说了OC中谓词操作,从今天开始我们就来看一下OC中最难的一部分内容:内存管理

为什么说他难呢?因为内存如果需要我们程序员去管理的话,那个难度肯定是很大的,如果是Java,垃圾回收器会把这份工作给做了,我们不需要关心,但是就是因为如此,Android运行速度上会慢一下,原因很简单,Java的垃圾回收器有很多收集算法的,这个在回收的过程中是很浪费时间的,效率自然就低了,但是如果这份工作给程序员自己去做的话,效率上肯定会增加,但是对于程序员来说任务就比较繁重了,而且还要特别的小心,千万不能造成内存溢出和泄露。

这里我们主要从四个方面来介绍一下内存管理

1、简单的例子来了解引用计数的使用

2、set方法来控制引用计数问题

3、销毁方法来控制引用计数问题

4、初始化方法来控制引用计数问题

 

下面就来简单看一下OC中的内存管理

这个例子有点复杂,我们慢慢分析

Dog.h

 1 //  

 2 //  Dog.h  

 3 //  24_MemeryManager  

 4 //  

 5 //  Created by jiangwei on 14-10-12.  

 6 //  Copyright (c) 2014年 jiangwei. All rights reserved.  

 7 //  

 8   

 9 #import <Foundation/Foundation.h>  

10   

11 @interface Dog : NSObject{  

12     NSString *_name;  

13 }  

14   

15 - (void) setName:(NSString *)name;  

16   

17 @end

Dog.m

 1 //  

 2 //  Dog.m  

 3 //  24_MemeryManager  

 4 //  

 5 //  Created by jiangwei on 14-10-12.  

 6 //  Copyright (c) 2014年 jiangwei. All rights reserved.  

 7 //  

 8   

 9 #import "Dog.h"  

10   

11 @implementation Dog  

12   

13 - (void) setName:(NSString *)name{  

14     _name = name;  

15 }  

16   

17 @end  

Dog类中定义了name属性,并且给他提供了一个set方法

Person.h

 1 //  

 2 //  Person.h  

 3 //  24_MemeryManager  

 4 //  

 5 //  Created by jiangwei on 14-10-12.  

 6 //  Copyright (c) 2014年 jiangwei. All rights reserved.  

 7 //  

 8   

 9 #import <Foundation/Foundation.h>  

10   

11 @class Dog;  

12 @interface Person : NSObject{  

13     Dog *_dog;  

14     NSString * _name;  

15 }  

16   

17 - (id)initWithDog:(Dog*)dog;  

18 - (void)setName:(NSString *)name;  

19 - (void)setDog:(Dog *)dog;  

20 - (void)playDog;  

21 - (Dog *)dog;  

22   

23 - (void)dealloc;  

24 @end

Person.m

 1 //  

 2 //  Person.m  

 3 //  24_MemeryManager  

 4 //  

 5 //  Created by jiangwei on 14-10-12.  

 6 //  Copyright (c) 2014年 jiangwei. All rights reserved.  

 7 //  

 8   

 9 #import "Person.h"  

10   

11 @implementation Person  

12   

13 - (id)initWithDog:(Dog*)dog{  

14     //使用初始化的时候传入dog  

15     self = [super init];  

16     if(self != nil){  

17         //因为初始化方法只会调用一次,所以这里就没有做判断了  

18         [_dog release];  

19         _dog = [dog retain];  

20     }  

21     return self;  

22 }  

23   

24 - (void)setName:(NSString *)name{  

25     //这里name也是对象,所以也是需要进行改写  

26     _name = name;  

27       

28     /* 

29      //这里的判断是因为setName方法可能会被多次调用 

30      if(_name != name){ 

31         [_name release]; 

32         [name copy];//这里使用了copy,而没有使用retain,这个是字符串独有的,其他对象类型都是使用retain的 

33      } 

34      */  

35 }  

36   

37 //第一种方式  

38 /* 

39 - (void)setDog:(Dog *)dog{ 

40     //引用计数需要+1 

41     _dog = [dog retain]; 

42      

43     //有时候可能需要替换Dog对象,所以这里还要注意释放Dog的引用 

44 } 

45  */  

46   

47 //第二种方式  

48 /* 

49 - (void)setDog:(Dog *)dog{ 

50     //使用nil去调用方法是没有错误的 

51     //但是当一个对象被销毁的时候,指针就变成野指针了,这时候调用方法会出错的 

52     [_dog release]; 

53     _dog = [dog retain]; 

54 } 

55  */  

56   

57 //第三种方式  

58 - (void)setDog:(Dog *)dog{  

59     //这里的判断是因为setName方法可能会被多次调用  

60     if(_dog != dog){  

61         [_dog release];  

62         _dog = [dog retain];  

63     }  

64 }  

65   

66 - (void)playDog{  

67     NSLog(@"playDog");  

68 }  

69   

70 - (Dog *)dog{  

71     return _dog;  

72 }  

73   

74 - (void)dealloc{  

75     //对象类型的属性都需要在这里进行释放引用  

76     //对狗进行释放  

77     [_dog release];  

78     NSLog(@"dealloc is Executing");  

79     [super dealloc];  

80 }  

81   

82 @end

Person类中有一个Dog的属性,然后提供了set方法。代码有点复杂,我们后面会详细说明 。

下面来看一下测试代码:

main.m

 1 //  

 2 //  main.m  

 3 //  24_MemeryManager  

 4 //  

 5 //  Created by jiangwei on 14-10-12.  

 6 //  Copyright (c) 2014年 jiangwei. All rights reserved.  

 7 //  

 8   

 9 #import <Foundation/Foundation.h>  

10   

11 #import "Person.h"  

12 #import "Dog.h"  

13   

14 //内存管理  

15 //alloc用来创建对象,创建完成之后,引用计数为1,只调用一次  

16 //retain使引用计数+1,可以多调用几次  

17 //release使引用计数-1,可以多调用几次  

18 //当引用计数为0的时候会调用dealloc  

19 //最新的Xcode版本默认情况下会开启ARC机制的,当开启这个机制之后,我们就不能手动的显示调用这些方法,编译器会报错  

20 //所以我们可以将这个默认状态的ARC关闭,但是这个只是为了测试使用  

21   

22 int main(int argc, const charchar * argv[]) {  

23   

24     /* 

25     Person *person = [[Person alloc] init];//引用计数为1 

26     NSLog(@"引用计数:%ld",[person retainCount]); 

27      

28     //引用计数加1 

29     [person retain]; 

30      

31     [person release]; 

32      

33     NSLog(@"引用计数:%ld",[person retainCount]); 

34     [person release]; 

35      */  

36       

37       

38     Dog *dog = [[Dog alloc] init];  

39     [dog setName:@"小黑"];  

40       

41     Dog *dog1 = [[Dog alloc] init];//引用计数为1  

42     [dog setName:@"大黄"];  

43       

44     Person *p1 = [[Person alloc] init];  

45     [p1 setName:@"张三"];  

46     [p1 setDog:dog];  

47     [p1 setDog:dog1];//狗的引用替换成了大黄  

48       

49     Person *p2 = [[Person alloc] init];  

50     [p2 setName:@"李四"];  

51     [p2 setDog:dog];  

52       

53     //这里引用计数为1,这个和我们之前说的引用计数管理有矛盾,所以我们在使用的时候需要手动的retain  

54     NSLog(@"引用计数为:%ld",[dog retainCount]);  

55       

56     [dog1 release];//因为alloc的时候引用计数就为1了  

57       

58     //这里就有一个问题了,dog1对象已经被销毁了,但是setDog对象还是用了dog1对象调用方法了,这就会报错了  

59     //所以又对set方法进行改进了  

60     [p1 setDog:dog1];  

61       

62     //当人销毁的时候,还需要对狗的引用-1  

63     //在人的dealloc方法中实现  

64       

65     Person *p3 = [[Person alloc] initWithDog:dog1];  

66     [dog1 release];//dog1的引用计数:0  

67       

68     [p3 playDog];  

69       

70     [p3 release];  

71       

72     return 0;  

73 }

下面我们来详细说明一下:

首先如果想演示这个例子的话,需要修改一下设置:



最新的XCode默认是会自动选上ARC(Automatic Reference Counting),如果我们不把这个手动关闭的话,代码中会报错的。

alloc用来创建对象,创建完成之后,引用计数为1,只调用一次

retain使引用计数+1,可以多调用几次

release使引用计数-1,可以多调用几次

当引用计数为0的时候会调用dealloc

黄金法则:每次调用alloc一次,都需要调用release一次,他们两是成对出现的

最新的Xcode版本默认情况下会开启ARC机制的,当开启这个机制之后,我们就不能手动的显示调用这些方法,编译器会报错。所以我们可以将这个默认状态的ARC关闭,但是这个只是为了测试使用。同时我们会发现main.m文件中没有自动释放池了@autoreleasepool了。

 

1、简单的例子

 1 Person *person = [[Person alloc] init];//引用计数为1  

 2 NSLog(@"引用计数:%ld",[person retainCount]);  

 3   

 4 //引用计数加1  

 5 [person retain];  

 6   

 7 [person release];  

 8   

 9 NSLog(@"引用计数:%ld",[person retainCount]);  

10 [person release];
 

我们创建了一个Person类,然后可以打印一下的引用计数值:1

当我们调用retain方法的时候,引用计数就会+1,当我们调用release方法的时候引用计数会-1

一旦系统发现引用计数为0的时候,就会销毁这个对象,调用dealloc方法。

2、set方法来控制引用计数

这个例子简单吧,没什么问题的,现在我们把问题复杂化

 1 Dog *dog = [[Dog alloc] init];  

 2 [dog setName:@"小黑"];  

 3   

 4 Dog *dog1 = [[Dog alloc] init];//引用计数为1  

 5 [dog setName:@"大黄"];  

 6   

 7 Person *p1 = [[Person alloc] init];  

 8 [p1 setName:@"张三"];  

 9 [p1 setDog:dog];  

10 [p1 setDog:dog1];//狗的引用替换成了大黄  

11   

12 Person *p2 = [[Person alloc] init];  

13 [p2 setName:@"李四"];  

14 [p2 setDog:dog];  

15   

16 //这里引用计数为1,这个和我们之前说的引用计数管理有矛盾,所以我们在使用的时候需要手动的retain  

17 NSLog(@"引用计数为:%ld",[dog retainCount]);  

18   

19 [dog1 release];//因为alloc的时候引用计数就为1了  

20   

21 //这里就有一个问题了,dog1对象已经被销毁了,但是setDog对象还是用了dog1对象调用方法了,这就会报错了  

22 //所以又对set方法进行改进了  

23 [p1 setDog:dog1];  

24   

25 //当人销毁的时候,还需要对狗的引用-1  

26 //在人的dealloc方法中实现

我们定义了两条狗,然后将这两条狗通过setDog方法设置到p1对象上,同时将第一条狗设置到p2对象上,这时候我们打印一下第一条狗的引用计数,发现他的引用计数是1,原因很简单,这个1是在alloc时候有的,但是现在是有问题的,因为第一条狗被p1和p2引用者,按照正常情况,第一条狗的引用计数为3的。所以这时候我们就需要修改一下Person类中setDog方法了。

1 - (void)setDog:(Dog *)dog{  

2     //引用计数需要+1  

3     _dog = [dog retain];  

4       

5     //有时候可能需要替换Dog对象,所以这里还要注意释放Dog的引用  

6 }  

我们需要手动的增加dog的引用计数,这样就正常了。

现在又有一个问题了,上面p1对象设置了第二条狗

1 [p1 setDog:dog1];//狗的引用替换成了大黄  

按照上面的setDog方法,我们又发现一个问题,现在p1引用的狗是第二条狗了,不是第一条,那么这时候按照正常情况第一条狗的引用计数-1,因为他已经不再被p1引用了。所以setDog方法还得修改:

1 - (void)setDog:(Dog *)dog{  

2     //使用nil去调用方法是没有错误的  

3     //但是当一个对象被销毁的时候,指针就变成野指针了,这时候调用方法会出错的  

4     [_dog release];  

5     _dog = [dog retain];  

6 }  

这时候我们就在赋值之前,对_dog调用一次release方法,这样就解决了上面的那个问题。

但是现在还有一个问题:

1 [dog1 release];//因为alloc的时候引用计数就为1了  

2 NSLog(@"dog1:%ld",[dog1 retainCount]);  

3 //这里就有一个问题了,dog1对象已经被销毁了,但是setDog对象还是用了dog1对象调用方法了,这就会报错了  

4 //所以又对set方法进行改进了  

5 [p1 setDog:dog1];

在执行release代码之后,dog1的引用计数为1,这是继续调用setDog方法:

1 - (void)setDog:(Dog *)dog{  

2     //使用nil去调用方法是没有错误的  

3     //但是当一个对象被销毁的时候,指针就变成野指针了,这时候调用方法会出错的  

4     [_dog release];  

5     _dog = [dog retain];  

6 } 

这时候_dog是dog1,继续调用release方法,执行完之后,引用继续为0了,对象被释放了,但是这时候dog参数的值还是dog1,那么在执行retain方法就会报错了,这个原因很简单,就是我们两次调用了setDog方法,将dog1设置了两次。中间调用一次release方法。所以解决这样的问题,我们在修改一下setDog方法:

1 //第三种方式  

2 - (void)setDog:(Dog *)dog{  

3     //这里的判断是因为setName方法可能会被多次调用  

4     if(_dog != dog){  

5         [_dog release];  

6         _dog = [dog retain];  

7     }  

8 }  

我只需要判断一下,之前的属性值和当前需要设置的值是否相同。这种方式就是没有问题了,所以我们以后在写set方法的时候,这就是模板了。

3、销毁方法dealloc控制引用计数

现在假如p1被销毁了,那么对于dog1来说引用计数应该-1的,所以需要在Person类中的dealloc方法中修改一下:

1 - (void)dealloc{  

2     //对象类型的属性都需要在这里进行释放引用  

3     //对狗进行释放  

4     [_dog release];  

5     NSLog(@"dealloc is Executing");  

6     [super dealloc];  

7 }  

4、初始化方法控制引用计数

现在还有一种情况,如果我们使用初始化方法来设置Dog属性值:

 1 - (id)initWithDog:(Dog*)dog{  

 2     //使用初始化的时候传入dog  

 3     self = [super init];  

 4     if(self != nil){  

 5         //因为初始化方法只会调用一次,所以这里就没有做判断了  

 6         [_dog release];  

 7         _dog = [dog retain];  

 8     }  

 9     return self;  

10 }  

我们这里的处理和setDog方法的处理方式一样,但是这里不需要做一次判断了,因为初始化方法只会调用一次。所以不会出现setDog的第三种情况。

 

总结

这一篇主要介绍了OC中内存管理的概念和用法,后面会继续介绍内存管理的相关知识。

你可能感兴趣的:(内存管理)