欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

iOS开发之MRC(手动内存管理)详解,

来源: 开发者 投稿于  被查看 10703 次 评论:124

iOS开发之MRC(手动内存管理)详解,


目录
  • 前言:
    • 内存管理的经验总结
  • 一、 MRC 手动管理内存(Manual Reference Counting)
    • 1、引用计数器
    • 2、引用计数器操作
    • 3、dealloc 方法
    • 4、野指针和空指针
  • 二、内存管理思想
    • 1、单个对象内存管理思想
      • 思想一:自己创建的对象,自己持有,自己负责释放
      • 思想二:非自己创建的对象,自己也能持有
    • 2、多个对象内存管理思想
    • 三、 @property 参数
      • 四、自动释放池
        • 1、自动释放池
          • 2、使用 autorelease 有什么好处呢?
            • 3、autorelease 的原理实质上是什么?
              • 4、autorelease 的创建方法
                • 5、autorelease 的使用方法
                  • 6、autorelease 的注意事项
                    • 7、自动释放池的嵌套使用
                      • 8、autorelease 错误用法
                      • 五、 MRC 中避免循环引用
                        • 那么如何解决这个问题呢?

                        前言:

                        在iOS中,使用引用计数来管理OC对象内存

                        一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间。

                        调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1。

                        内存管理的经验总结

                        当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease释放它。
                        想拥有某个对象,就让他的引用计数+1;不想再拥有某个对象,就让他的引用计数-1。

                        一、 MRC 手动管理内存(Manual Reference Counting)

                        1、引用计数器

                        引用计数器:

                        一个整数,表示为「对象被引用的次数」。系统需要根据对象的引用计数器来判断对象是否需要被回收。

                        关于「引用计数器」,有以下几个特点:

                        • 每个 OC 对象都有自己的引用计数器。
                        • 任何一个对象,刚创建的时候,初始的引用计数为 1。
                        • 即使用 alloc、new 或者 copy 创建一个对象时,对象的引用计数器默认就是 1。
                        • 当没有任何人使用这个对象时,系统才会回收这个对象。也就是说:
                        • 当对象的引用计数器为 0 时,对象占用的内存就会被系统回收。
                        • 如果对象的引用计数器不为 0 时,那么在整个程序运行过程,它占用的内存就不可能被回收(除非整个程序已经退出)。

                        2、引用计数器操作

                        • 为保证对象的存在,每当创建引用到对象需要给对象发送一条 retain 消息,可以使引用计数器值 +1 ( retain 方法返回对象本身)。
                        • 当不再需要对象时,通过给对象发送一条 release 消息,可以使引用计数器值 -1。
                        • 给对象发送 retainCount 消息,可以获得当前的引用计数器值。
                        • 当对象的引用计数为 0 时,系统就知道这个对象不再需要使用了,所以可以释放它的内存,通过给对象发送 dealloc 消息发起这个过程。
                        • 需要注意的是:release 并不代表销毁 / 回收对象,仅仅是将计数器 -1。
                        // 创建一个对象,默认引用计数器是 1
                        RHPerson *person1 = [[RHPerson alloc] init];
                        NSLog(@"retainCount = %zd", [person1 retainCount]);
                        
                        // 只要给对象发送一条 retain 消息,引用计数器加1
                        [person1 retain];
                        NSLog(@"retainCount = %zd", [person1 retainCount]);
                        
                        // 只要给对象发送一条 release 消息,引用计数器减1
                        [person1 release];
                        NSLog(@"retainCount = %zd", [person1 retainCount]);
                        
                        // 当 retainCount 等于0时,对象被销毁
                        [person1 release];
                        
                        NSLog(@"--------------");
                        2022-07-11 16:09:24.102850+0800 Interview01-内存管理[8035:264221] retainCount = 1
                        2022-07-11 16:09:24.103083+0800 Interview01-内存管理[8035:264221] retainCount = 2
                        2022-07-11 16:09:24.103126+0800 Interview01-内存管理[8035:264221] retainCount = 1
                        2022-07-11 16:09:24.103231+0800 Interview01-内存管理[8035:264221] -[RHPerson dealloc]
                        2022-07-11 16:09:24.103259+0800 Interview01-内存管理[8035:264221] --------------
                        Program ended with exit code: 0

                        3、dealloc 方法

                        • 当一个对象的引用计数器值为 0 时,这个对象即将被销毁,其占用的内存被系统回收。
                        • 对象即将被销毁时系统会自动给对象发送一条 dealloc 消息(因此,从 dealloc 方法有没有被调用,就可以判断出对象是否被销毁)
                        • dealloc 方法的重写(注意是在 MRC 中)

                        一般会重写 dealloc 方法,在这里释放相关资源,dealloc 就是对象的遗言
                        一旦重写了 dealloc 方法,就必须调用 [super dealloc],并且放在最后面调用

                        - (void)dealloc {
                            NSLog(@"%s", __func__);
                            [super dealloc];
                        }

                        dealloc 使用注意:

                        不能直接调用 dealloc 方法。

                        一旦对象被回收了, 它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)。

                        4、野指针和空指针

                        只要一个对象被释放了,我们就称这个对象为「僵尸对象(不能再使用的对象)」。

                        当一个指针指向一个僵尸对象(不能再使用的对象),我们就称这个指针为「野指针」。

                        只要给一个野指针发送消息就会报错(EXC_BAD_ACCESS 错误)。

                        RHPerson *person1 = [[RHPerson alloc] init];
                        [person1 release];
                        [person1 release];
                        [person1 release];
                        

                        为了避免给野指针发送消息会报错,一般情况下,当一个对象被释放后我们会将这个对象的指针设置为空指针。

                        空指针:

                        没有指向存储空间的指针(里面存的是 nil, 也就是 0)。

                        给空指针发消息是没有任何反应的。

                        RHPerson *person1 = [[RHPerson alloc] init];
                        [person1 release];
                        person1 = nil;
                        [person1 release];

                        二、内存管理思想

                        1、单个对象内存管理思想

                        思想一:自己创建的对象,自己持有,自己负责释放

                        通过 alloc、new、copy 或 mutableCopy 方法创建并持有对象。
                        当自己持有的对象不再被需要时,必须调用 release 或 autorelease 方法释放对象。

                        id obj1 = [[NSObject alloc] init];
                        [obj1 release];
                        
                        id obj2 = [NSObject new];
                        [obj2 release];

                        思想二:非自己创建的对象,自己也能持有

                        除了用上面方法(alloc / new / copy / mutableCopy 方法)所取得的的对象,因为非自己生成并持有,所以自己不是该对象的持有者。
                        通过调用 retain 方法,即便是非自己创建的对象,自己也能持有对象。
                        同样当自己持有的对象不再被需要时,必须调用 release 方法来释放对象。

                        id obj3 = [NSArray array];
                        [obj3 retain];
                        [obj3 release];

                        无论是否是自己创建的对象,自己都可以持有,并负责释放。
                        计数器有加就有减。
                        曾经让对象的计数器 +1,就必须在最后让对象计数器 -1。

                        2、多个对象内存管理思想

                        多个对象之间往往是通过 setter 方法产生联系的,其内存管理的方法也是在 setter 方法、dealloc 方法中实现的。所以只有了解了 setter 方法是如何实现的,我们才能了解到多个对象之间的内存管理思想。

                        #import <Foundation/Foundation.h>
                        
                        #import "RHRoom.h"
                        
                        NS_ASSUME_NONNULL_BEGIN
                        
                        @interface RHPerson : NSObject
                        
                        {
                            RHRoom *_room;
                        }
                        
                        - (void)setRoom:(RHRoom *)room;
                        
                        - (RHRoom *)room;
                        @end
                        
                        NS_ASSUME_NONNULL_END
                        #import "RHPerson.h"
                        
                        @implementation RHPerson
                        
                        - (void)setRoom:(RHRoom *)room {
                            if (_room != room) {
                                [_room release];
                                _room = [room retain];
                            }
                        }
                        
                        - (RHRoom *)room {
                            return _room;
                        }
                        
                        - (void)dealloc {
                            [_room release];
                            [super dealloc];
                            NSLog(@"%s", __func__);
                        }
                        
                        @end

                        三、 @property 参数

                        在成员变量前加上 @property,系统就会自动帮我们生成基本的 setter / getter 方法,但是不会生成内存管理相关的代码。
                        @property(nonatomic) int val;
                        同样如果在 property 后边加上 assign,系统也不会帮我们生成 setter 方法内存管理的代码,仅仅只会生成普通的 getter / setter 方法,默认什么都不写就是 assign。
                        @property(nonatomic, assign) int val;
                        如果在 property 后边加上 retain,系统就会自动帮我们生成 getter / setter 方法内存管理的代码,但是仍需要我们自己重写 dealloc 方法。
                        @property(nonatomic, retain) RHRoom *room;

                        四、自动释放池

                        1、自动释放池

                        当我们不再使用一个对象的时候应该将其空间释放,但是有时候我们不知道何时应该将其释放。为了解决这个问题,Objective-C 提供了 autorelease 方法。

                        autorelease 是一种支持引用计数的内存管理方式,只要给对象发送一条 autorelease 消息,会将对象放到一个自动释放池中,当自动释放池被销毁时,会对池子里面的「所有对象」做一次 release 操作。
                        注意:这里只是发送 release 消息,如果当时的引用计数(reference-counted)依然不为 0,则该对象依然不会被释放。

                        autorelease 方法会返回对象本身,且调用完 autorelease 方法后,对象的计数器不变。

                        NSObject *obj = [NSObject new];
                        [obj autorelease];
                        NSLog(@"obj.retainCount = %zd", obj.retainCount);
                        

                        2、使用 autorelease 有什么好处呢?

                        不用再关心对象释放的时间
                        不用再关心什么时候调用release

                        3、autorelease 的原理实质上是什么?

                        ​ autorelease 实际上只是把对 release 的调用延迟了,对于每一个 autorelease,系统只是把该对象放入了当前的 autorelease pool 中,当该 pool 被释放时,该 pool 中的所有对象会被调用 release 方法。

                        4、autorelease 的创建方法

                        // 第一种方式:使用 NSAutoreleasePool 创建
                        // 创建自动释放池
                        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
                        // 销毁自动释放池
                        [pool release];
                        // 第二种方式:使用 @autoreleasepool 创建
                        @autoreleasepool {
                        // 开始代表创建自动释放池
                        // 结束代表销毁自动释放池
                        }
                        

                        5、autorelease 的使用方法

                        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
                        RHPerson *p = [[[RHPerson alloc] init] autorelease];
                        [pool release];
                        @autoreleasepool {
                        // 开始代表创建自动释放池
                        RHPerson *p = [[[RHPerson alloc] init] autorelease];
                        // 结束代表销毁自动释放池
                        }
                        

                        6、autorelease 的注意事项

                        并不是放到自动释放池代码中,都会自动加入到自动释放池

                        @autoreleasepool {
                        // 因为没有调用 autorelease,所以没有加入到自动释放池中
                        RHPerson *p = [[RHPerson alloc] init];
                        // 结束代表销毁自动释放池
                        }

                        在自动释放池的外部发送 autorelease 不会被加入到自动释放池中
                        autorelease 是一个方法,只有在自动释放池中调用才有效。

                        @autoreleasepool {
                        }
                        // 没有与之对应的自动释放池, 只有在自动释放池中调用autorelease才会放到释放池
                        Person *p = [[[Person alloc] init] autorelease];
                        [p run];
                         
                        // 正确写法
                        @autoreleasepool {
                            Person *p = [[[Person alloc] init] autorelease];
                         }
                         
                        // 正确写法
                        Person *p = [[Person alloc] init];
                        @autoreleasepool {
                            [p autorelease];
                        }
                        
                        

                        7、自动释放池的嵌套使用

                        自动释放池是以栈的形式存在。
                        由于栈只有一个入口,所以调用 autorelease 会将对象放到栈顶的自动释放池。
                        栈顶就是离调用 autorelease 方法最近的自动释放池。

                        @autoreleasepool { // 栈底自动释放池
                            @autoreleasepool {
                                @autoreleasepool { // 栈顶自动释放池
                                    Person *p = [[[Person alloc] init] autorelease];
                                }
                                Person *p = [[[Person alloc] init] autorelease];
                            }
                        }
                        
                        

                        自动释放池中不适宜放占用内存比较大的对象。
                        尽量避免对大内存使用该方法,对于这种延迟释放机制,还是尽量少用。
                        不要把大量循环操作放到同一个 @autoreleasepool 之间,这样会造成内存峰值的上升。

                        // 内存暴涨
                        @autoreleasepool {
                            for (int i = 0; i < 99999; ++i) {
                                Person *p = [[[Person alloc] init] autorelease];
                            }
                        }
                        // 内存不会暴涨
                        for (int i = 0; i < 99999; ++i) {
                            @autoreleasepool {
                                Person *p = [[[Person alloc] init] autorelease];
                            }
                        }

                        8、autorelease 错误用法

                        不要连续调用 autorelease。

                        @autoreleasepool {
                         // 错误写法, 过度释放
                            Person *p = [[[[Person alloc] init] autorelease] autorelease];
                         }

                        调用 autorelease 后又调用 release(错误)。

                        @autoreleasepool {
                            Person *p = [[[Person alloc] init] autorelease];
                            [p release]; // 错误写法, 过度释放
                        }

                        五、 MRC 中避免循环引用

                        定义两个类 Person 类和 Dog 类

                        Person 类:

                        #import <Foundation/Foundation.h>
                        @class Dog;
                         
                        @interface Person : NSObject
                        @property(nonatomic, retain)Dog *dog;
                        @end
                        

                        Dog 类:

                        #import <Foundation/Foundation.h>
                        @class Person;
                         
                        @interface Dog : NSObject
                        @property(nonatomic, retain)Person *owner;
                        @end

                        执行以下代码:

                        int main(int argc, const char * argv[]) {
                            Person *p = [Person new];
                            Dog *d = [Dog new];
                         
                            p.dog = d; // retain
                            d.owner = p; // retain  assign
                         
                            [p release];
                            [d release];
                         
                            return 0;
                        }

                        就会出现 A 对象要拥有 B 对象,而 B 对应又要拥有 A 对象,此时会形成循环 retain,导致 A 对象和 B 对象永远无法释放。

                        那么如何解决这个问题呢?

                        不要让 A retain B,B retain A。
                        让其中一方不要做 retain 操作即可。
                        当两端互相引用时,应该一端用 retain,一端用 assign。

                        到此这篇关于 iOS开发之MRC(手动内存管理)详解的文章就介绍到这了,更多相关MRC 手动内存管理内容请搜索3672js教程以前的文章或继续浏览下面的相关文章希望大家以后多多支持3672js教程!

                        您可能感兴趣的文章:
                        • iOS MRC 下 block 循环引用问题实例讲解
                        • vim配置显示行号和语法高亮 即.vimrc文件的配置

                        相关文章

                        用户评论