• OC中最难的一部分内容:内存管理


    OC中最难的一部分内容:内存管理
    为什么说他难呢?因为内存如果需要我们程序员去管理的话,那个难度肯定是很大的,如果是Java,垃圾回收器会把这份工作给做了,我们不需要关心,但是就是因为如此,Android运行速度上会慢一下,原因很简单,Java的垃圾回收器有很多收集算法的,这个在回收的过程中是很浪费时间的,效率自然就低了,但是如果这份工作给程序员自己去做的话,效率上肯定会增加,但是对于程序员来说任务就比较繁重了,而且还要特别的小心,千万不能造成内存溢出和泄露。
    这里我们主要从四个方面来介绍一下内存管理
    1、简单的例子来了解引用计数的使用
    2set方法来控制引用计数问题
    3、销毁方法来控制引用计数问题
    4、初始化方法来控制引用计数问题


    下面就来简单看一下OC中的内存管理
    这个例子有点复杂,我们慢慢分析
    Dog.h
    [objc]  view plaincopy
    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
    [objc]  view plaincopy
    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
    [objc]  view plaincopy
    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
    [objc]  view plaincopy
    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
    [objc]  view plaincopy
    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、简单的例子
    [objc]  view plaincopy
    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方法

    2set方法来控制引用计数
    这个例子简单吧,没什么问题的,现在我们把问题复杂化
    [java]  view plaincopy
    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方法了
    [objc]  view plaincopy
    1. - (void)setDog:(Dog *)dog{  
    2.     //引用计数需要+1  
    3.     _dog = [dog retain];  
    4.       
    5.     //有时候可能需要替换Dog对象,所以这里还要注意释放Dog的引用  
    6. }  
    我们需要手动的增加dog的引用计数,这样就正常了。

    现在又有一个问题了,上面p1对象设置了第二条狗
    [objc]  view plaincopy
    1. [p1 setDog:dog1];//狗的引用替换成了大黄  
    按照上面的setDog方法,我们又发现一个问题,现在p1引用的狗是第二条狗了,不是第一条,那么这时候按照正常情况第一条狗的引用计数-1,因为他已经不再被p1引用了。所以setDog方法还得修改:
    [objc]  view plaincopy
    1. - (void)setDog:(Dog *)dog{  
    2.     //使用nil去调用方法是没有错误的  
    3.     //但是当一个对象被销毁的时候,指针就变成野指针了,这时候调用方法会出错的  
    4.     [_dog release];  
    5.     _dog = [dog retain];  
    6. }  
    这时候我们就在赋值之前,对_dog调用一次release方法,这样就解决了上面的那个问题。

    但是现在还有一个问题:
    [java]  view plaincopy
    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方法:
    [objc]  view plaincopy
    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方法:
    [objc]  view plaincopy
    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方法中修改一下:
    [objc]  view plaincopy
    1. - (void)dealloc{  
    2.     //对象类型的属性都需要在这里进行释放引用  
    3.     //对狗进行释放  
    4.     [_dog release];  
    5.     NSLog(@"dealloc is Executing");  
    6.     [super dealloc];  
    7. }  

    4、初始化方法控制引用计数
    现在还有一种情况,如果我们使用初始化方法来设置Dog属性值:
    [objc]  view plaincopy
    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中内存管理的概念和用法,后面会继续介绍内存管理的相关知识。
  • 相关阅读:
    spring事务传播机制实例讲解
    一篇关于交叉编译的文章
    Java中的泛型方法
    层序遍历打印二叉树
    寻找第K大 网易2016实习研发工程师编程题
    二叉树的非递归遍历
    二叉树 网易2016实习研发工程师编程题
    比较重量 网易2016实习研发工程师编程题
    网络地址为172.16.0.0,采用子网掩码255.255.224.0 .以下说法正确的是?
    2.7链表 回文链表
  • 原文地址:https://www.cnblogs.com/GhostKZShadow/p/5105427.html
Copyright © 2020-2023  润新知