美文网首页
[内存管理进阶系列一] - 内存管理命名规则与修饰符

[内存管理进阶系列一] - 内存管理命名规则与修饰符

作者: coder_clownfish | 来源:发表于2019-04-17 16:18 被阅读0次

    细节决定成败,iOS移动开发发展已经多年,相信内存管理这个概念已经被说烂了,但是相信对于很多小伙伴来说,只是了解了内存管理的大概知识体系,对于一些边缘知识点掌握的并不是很好,边缘不代表不重要.
    互联网环境寒冬将至,为了应对目前市场越来越高的需求,iOS开发不得不去提升自己的能力去匹配更高的需求,当每个人都学会了的时候,你比别人更好的体现就是你会的内容更深入,进入正题,下面是总结的一些内存管理方面的使用注意事项.

    1. 内存管理方法名命名规则
      • alloc/new/copy/mutable
        以上名称开头的方法名,返回自己生成并持有的对象,如果没有引用的变量则会释放并废弃.

            // 示例
            // main.m实现如下
            #import <Foundation/Foundation.h>
            #import "CFObject.h"    
        
            int main(int argc, const char * argv[]) {
                @autoreleasepool {
                    
                    id __weak obj;
                    @autoreleasepool {
                        obj = [CFObject newObject];
                        
                        NSLog(@"%@", obj);
                    }
                    
                    NSLog(@"%@", obj);
                }
                return 0;
            }   
        
            //CFObject.h中做函数声明 
            // CFObject.m实现如下
            + (id)allocObject {
                return [[CFObject alloc] init];
            }   
        
            + (id)newObject {
                return [[CFObject alloc] init];
            }   
        
            // 打印结果为null null
            /**
                结果分析:
                由于obj指针为__weak弱指针,所以并不持有生成的对象,编译器判断没有持有当前对象的变量,所以在newObject方法执行结束后将对象释放并废弃,obj此时指向nil,第一个log打印null      
                第二个log打印时obj也不再指向对象,所以obj指针指向nil,打印结果为null
            */ 
        

        你可能会说,这不就是内存管理最基本的原则吗,不用了就释放.这进阶什么了,刚学的时候就已经理解了.别急,继续看下面的例子

            // 在CFObject.m中增加一个createObject方法,返回实例对象
            + (id)createObject {
                return [[CFObject alloc] init];
            }   
        
            // main.m中和上一个示例一样调用并打印
            int main(int argc, const char * argv[]) {
                @autoreleasepool {
                    
                    id __weak obj;
                    
                    @autoreleasepool {
                        obj = [CFObject createObject];
                        
                        NSLog(@"%@", obj);
                    }
                    
                    NSLog(@"%@", obj);
                }
                return 0;
            }   
        
            /**
                猜一下打印结果是什么,还是null, null 吗? 
                结果是 : <CFObject: 0x102805ef0> 和 null 
                为什么返回实例的实现都一样,更换了方法名就打印不一样了呢,这就引出了第二条命名规则
            */
        
      • 非第一条提到的名称命名的方法名
        除了第一条以外命名的方法名,例如createObject NSMutablArray的 array方法 [NSMutablArray array],这一类的方法返回实例时并不持有对象,但是为了能达到使用对象的目的,使用了autorelease自动释放池管理对象内存.
        ```
        // 所以上面示例中的createObject方法实现可以转换为返回autorelease 对象.类似以下实现(仅逻辑类比,并非真正实现)
        + (id)createObject {
        return [[[CFObject alloc] init] autorelease];
        }

              /**
              结果分析 : 由于createObject返回实例对象被自动释放池管理,所以不会立马释放对象,当一个log打印时,当前对象存在,可以有打印结果,
              当autoreleasepool{}结束作用域时,对象被释放,所以第二个打印为null.
              */
          ```
        
      • ARC下第三条规则(引用自Objective-C高级编程一书中Page 52 内容)
        init:init方法规则比alloc/new/copy/mutableCopy 还要严格,该方法:
        1. 必须返回的是实例方法
        2. 返回对象类型为id或者声明类的对象类型,或者是该类的父类或子类
        3. 返回对象不注册到autoreleaspool上,只是对alloc返回的对象初始化并返回该对象,init方法源码中调用runtime的_objc_rootInit直接将返回的对象返回,未作处理.(参考runtime源码NSObject.mm的init方法实现)

    2. 内存管理修饰符详解
      • 修饰符
        • __strong : 强引用指针,用于ARC下对变量修饰并产生强引用持有赋值对象,默认环境下的修饰符,一般 id object 书写格式默认就是id __strong object.使用非常常见.
        • __weak:弱引用指针,用于ARC下对变量修饰产生弱引用,不持有对象,常用于解决循环引用问题.当指向对象不存在时被置为nil.
          • 不支持__weak的情景:
            • 大多数重写了retain/release等内存管理方法的类,由于其自己实现了引用机制,而__weak需要用到一些runtime运行时库的函数,所以此种情况的类不支持__weak修饰,一般声明中会有提示,并且编译器编译时报错
            • 实现了allowsWeakReference或者retainWeakReference方法并且返回NO的情况
              • 当实现allowsWeakReference方法返回No时表示不支持弱引用,在赋值给__weak修饰的变量时,程序会异常终止
              • 当实现retainWeakReference方法返回NO时,__weak修饰的指针变量会被置为nil.
                  // 还是用以上示例中的CFObject类实现方法如下
                  - (BOOL)retainWeakReference  {
                      return YES;
                  }
              
                  // main.m做以下测试
                  int main(int argc, const char * argv[]) {
                      @autoreleasepool {
                          id object = [[CFObject alloc] init];
                          
                          id __weak obj = object;
                      
                          
                          NSLog(@"%@", obj);
                      }
                      return 0;
                  }
              
                  /**
                      当return NO时,打印null;
                      当return YES时,打印对象
                  */
              
          • __weak修饰的变量使用时会调用objc_loadWeakRetained()/objc_release()方法对做retain/release操作.大量使用__weak会产生性能损耗(大量做retain/release操作),
        • __autorelease
          • C中动态数组不支持autorelease(参考OC高编 P65)
          • autorelease修饰auto自动变量限制(仅限于局部变量,函数以及方法参数)
          • 非显示使用__autorelease修饰也生效的两种情况:
            • 第一种就是命名规则提到的第二条,虽然没显示的__autorelease修饰,但是编译器会做添加逻辑处理
            • 类似id obj 是默认 __strong修饰一样,如果是对象的指针id *obj 或者NSObject ** object 这样,默认是__autorelease.示例就是当传入参数为对象指针类型,例如NSError的使用情景下,(该情况本人未通过代码验证,参考文章是OC 高级编程一书中的介绍)
                NSError *error;
            
                // 声明
                method:(NSError **)error;
            
                // 调用
                method:&error
            
                // 此时method:(NSError **)error;声明时,可以看做是method:(NSError * __autorelease *)error;
            
          • autorelease 和__strong 共同使用时优化方案;
            当遇到命名规则中的第二条情况时,方法返回的对象需要做autorelease处理,如果对象赋值给__strong修饰的变量则又会做一个retain处理,示例如下:
                + (id)createObject {
                    return [[CFObject alloc] init];
                }  
            
                // main.m
                id obj = [CFObject createObject];
            
                clang编译成c++代码之后,
            
                // CFObject.m
                + (id)createObject {
                    id tem1 = ((id(*)(id, SEL))objc_msgSend)([NSObject class], @selector(alloc));
                    id tem2 = ((id(*)(id, SEL))objc_msgSend)(tem1, @selector(init));
            
                    return objc_autoreleaseReturnValue(id2);
                }
            
                // main.m
                id obj_tmp = ((id(*)(id, SEL))objc_msgSend)(self, @selector(obj));
                id obj     = objc_retainAutoreleasedReturnValue(obj_tmp);
                /**
                    main.m中,编译器会在返回为autorelease对象赋值后自动插入objc_retainAutoreleasedReturnValue()方法, 此时先autorelease,然后再retain放到自动释放池对象的操作会显得很没效率.
                    apple其实是对这个做了优化处理的,处理逻辑如下(参考OC 高级编程 P67):
                        1. 在类似命名规则第二条情况时,返回autorelease对象是通过函数objc_autoreleaseReturnValue()函数实现的.
                        使用到objc_autoreleaseReturnValue()函数时,编译器并不会决定立马将对象放入自动释放池,而是去查看调用方的执行命令列表,是否会有objc_retainAutoreleasedReturnValue()操作,如果没有,则按照正常的流程将对象放入自动释放池,如果有调用,则不再方式释放池,而是通过修改某个全局数据结构标志位的方式,当调动到objc_retainAutoreleasedReturnValue()方法时,会去检测该标志位,如果已经设置修改过,则不会再做retain操作,
                        通过修改标志位的方式效率比retain/release 会更高
                */
            
        • unsafe_unretain : 同__weak一样,都是弱引用,差异区别会放在下一知识点说明.
      • __weak 和unsafe_unretain 修饰符的差异
        • 空指针、野指针、悬垂指针的差异
          空指针很好理解,就是指向NUll的指针,但是针对野指针(Wild pointer)和悬垂指针(Dangling pointer)则有不同的理解.
          在某些语言中,是不区分野指针和悬垂指针的,因为会Wild pointer 会被导向至 Dangling pointer ;对悬垂指针的理解是指向了已经被释放的内存地址,但是指针未被置空.对野指针的理解是未初始化的指针或者是前面悬垂指针的情况,所以一般OC里也会说指向被释放内存地址的指针未野指针.对二者没有明显的区分.
        • 二者差异以及使用场景
          __weak 和unsafe_unretained都是弱引用指针,区别在于__weak是ARC下的弱引用,对不存在ARC机制以前使用unsafe_unretained修饰,从名字也可以看出后者是不安全的,即当对象被释放废弃后,指针不会被置为nil,还指向原来的内存空间,此时使用有可能会导致程序异常.
    3. 如何获取引用计数值
      • 因为在ARC环境下不允许使用ratainCount(MRC支持),所以如何获取引用计数来调试内存管理呢,
      • OC对象获取方式:_objc_rootRetainCount()
      • C对象获取方式:CFGetRetainCout()
        获取引用计数是苹果思想里面不推荐的,因为引用计数并不是非常明显的代表着内存管理的结果.例如autorelease下的引用计数,会默认在autoreleasepool pop时 做release操作,但是当你打印时还未执行pop操作就会导致和你认为的数值不符,我们应该换一种角度看待引用计数的思想,他并不是真的想让开发者根据数值去控制内存管理,真正的思想是根据是否存在强引用的思想去理解内存管理.通过成对的retain/release去控制引用.所以此处的引用计数只是作为debug的一个参考,不可太过依赖.

    另外知其然,知其所以然.对于内存管理相关的源码分析会放在以后几章节详细说明.
    还在学习的路上奔波,所以有的知识理解还存在很大误区,非常欢迎指出共同进步...

    相关文章

      网友评论

          本文标题:[内存管理进阶系列一] - 内存管理命名规则与修饰符

          本文链接:https://www.haomeiwen.com/subject/ktfgwqtx.html