美文网首页iOS开发记录程序员
Objective-C内存管理——我所理解的ARC

Objective-C内存管理——我所理解的ARC

作者: 小宇先生 | 来源:发表于2015-11-25 23:11 被阅读0次

    ARC概要

    1、在Objective-C语言中,我们是通过控制一个对象的retainCount来管理一个对象的生命周期,当对象的retainCount为0时,该对象的内存将会被释放。因此,我们通常需要编写诸如

     [personObject retain]和
     if ([personObject retainCount]==1)
     {
      [personObject release];
      personObject = nil;
     }else{
      [personObject release];
     };
    

    这样的代码。
    <br />

    2、从LLVM编译器3.0开始,Apple为我们引入了ARC机制,这种机制对MRC(即手动内存管理),进行了封装,使我们不用再直接控制一个对象的retainCount来管理一个对象的生命周期,而是通过管理一个对象的"持有者"来管理一个对象的生命周期。

    <br />

    3、很多人误以为在ARC机制中,编译器会自动插入诸如上文的代码,这种理解是错误的,在ARC机制中,编译器的确会为我们的代码插入相关代码来对对象的retainCount进行管理,但是并不是插入直接管理retainCount的代码。正是因为如此,在ARC机制中,编译器不允许我们向对象发送retain和release消息,也不能发送retainCount消息来获取一个对象的retainCount。试想,如果编译器是直接插入retain和release来管理对象,其为什么不允许我们发送retainCount来获取对象的retainCount呢?

    <br />

    ARC中的持有者

    1、所有权修饰符

    在ARC机制中,声明一个对象指针时,出了指明指针指向对象的类型,还应指明该指针的所有权修饰符。所有权修饰符会告诉编译器,该指针是否会对对象进行持有。在ARC机制中的所有权修饰符如下:

    __strong
    __autoreleasing
    __unsafe_unretained
    __weak
    

    2、所有权修饰符之__strong

    声明一个指针的所有权修饰符为__strong

      Person _strong *personObject;
    

    所有权修饰符为__strong的指针,当其指向一个对象时,其会成为该对象的持有者。

      personObject = [Person new];
    

    上面的代码,编译器在编译时,会对其进行预处理,插入一些代码来控制该Person对象的“持有者数量“,在这里我们假设每一个NSObject对象内部都有一个referencingCount来保存该对象的“持有者”数量
    在Swift语言中,内存管理是通过自动引用计数来进行管理的,因此,我们可以通过Swift语言中Foundation框架对NSObject类提供的接口来获取一些信息

    public class NSObject : NSObjectProtocol {
       
       public class func load()
       
       public class func initialize()
       public init()
       
       public func finalize()
       
       public func copy() -> AnyObject
       public func mutableCopy() -> AnyObject
       
       public class func instancesRespondToSelector(aSelector: Selector) -> Bool
       public class func conformsToProtocol(`protocol`: Protocol) -> Bool
       public func methodForSelector(aSelector: Selector) -> IMP
       public class func instanceMethodForSelector(aSelector: Selector) -> IMP
       public func doesNotRecognizeSelector(aSelector: Selector)
       
       @available(OSX 10.5, *)
       public func forwardingTargetForSelector(aSelector: Selector) -> AnyObject?
       
       public class func isSubclassOfClass(aClass: AnyClass) -> Bool
       
       @available(OSX 10.5, *)
       public class func resolveClassMethod(sel: Selector) -> Bool
       @available(OSX 10.5, *)
       public class func resolveInstanceMethod(sel: Selector) -> Bool
       
       public class func hash() -> Int
       public class func superclass() -> AnyClass?
       
       public class func description() -> String
       public class func debugDescription() -> String
    }
    

    我们可以发现,对于NSObject实例对象,Apple并没有提供Objective-C语言中的retain和release方法来控制一个对象的retainCount方法,因此正如我在"ARC概述"中所说,在ARC机制下,编译器并不是通过插入向一个对象发送retain、release来直接管理对象的retainCount.
    因此我们可以对在ARC机制下,NSObject的类接口做一个简单的猜想

     @interface NSObject
     {
      int retainCount;
      int referencingCount;
     }
     @end;
    

    <strong>和retainCount不同,当我们通过[ClassObject alloc];创建一个对象时,该对象的referencingCount为0(retainCount为1)</strong>
    当编译器遇到“personObject = [Person new];”时,编译器可能会插入下面的代码来对对象的referencingCount进行管理

       objc_addReferencingCount(personObject);
    

    在这里,我假设objc_addReferencingCount()是一个C语言函数,对传入的参数的referencingCount进行+1操作。即其实现如下

        void objc_addReferencingCount(id* objc)
        {
         objc->referencingCount++;
        }
    

    后面,当编译器遇到"personObject = nil;"编译器可能会在该句代码之前插入下面的代码。

       objc_reduceReferencingCount(personObject)
    

    在这里,我同样假设objc_reduceReferencingCount是一个C语言函数,对传入的参数进行类似release的操作,即其实现如下

        void objc_reduceReferencingCount(id* objc)
        {
         objc->referencingCount--;
         if (objc->referencingCount==0)
         {
          objc_msgSend(objc,@selector(release));
         }
        }
    

    接着,当编译器遇到"personObject = otherPersonObject"时,编译器会插入的代码我们可以很容易猜出

       objc_reduceReferencingCount(personObject);
       objc_addReferencingCount(otherPersonObject);
    

    最后,当该指针变量的生命周期结束时,编译器会在插入objc_reduceReferencingCount来对其指向的对象的的referencingCount进行减1.

       if(YES){
        Person ben = [Person new];
        person jack = [Person new];
        ben = jack;
       }
    

    编译其进行预处理的结果为

       if(YES){
        Person ben = [Person new];
        objc_addReferencingCount(ben);
        
        Person jack = [Person new];
        objc_addReferencingCount(jack);
        
        objc_reduceReferencingCount(ben);
        objc_addReferencingCount(jack);
        ben = jack;
        
        objc_reduceReferencingCount(ben);
        objc_reduceReferencingCount(jack);
       }
    

    因此,编译器会根据所有者修饰符为__strong的指针来控制对象的referencingCount和objc_addReferencingCount的完整实现如下

        void objc_addReferencingCount(id* objc)
        {
         if(objc==nil)
         {
          objc->referencingCount++;
         }
        }
        void objc_reduceReferencingCount(id* objc)
        {
         if(objc==nil)
         {
          return;
         }
         objc->referencingCount--;
         if (objc->referencingCount==0)
         {
          objc_msgSend(objc,@selector(release));
         }
        }
    
    

    3、所有者修饰符之__autoreleasing

    上面,我们谈到了所有者修饰符为__strong的指针,编译器会对对其进行的相关操作。
    现在有一个问题

     if(YES){
        Person ben = [Person new];
       dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                [ben show];
                [ben walk];
            });
        
     }
    

    在if语句开启的局部作用域的默认,我们开启了一个异步线程,而在主线程中,ben指向的对象被的referencingCount已经为0,这时候该对象的内存已经被清空,且ben指针也已被清空,那么在异步线程中向指向[ben show];和[ben walk];肯定会造成运行时异常不是吗?
    对于ben指针变量被销毁,这个很容易解决,编译器只需要在Block中创建一个指针,将ben的值赋值给该新指针即可。

    为了解决,“ben指针指向的对象的referencingCount为0,导致对象被销毁,无法在异步线程中访问该对象”的这个问题,下面来介绍所有者修饰符__autoreleasing

    在MRC中,Apple为我们带了NSAutoReleasePool类,使得我们在开发中,只需要确保一个对象在程序执行结束前,其的retainCount<=1即可以被释放,以免造成内存泄漏。在ARC机制中,我们只需要确保一个对象的referencingCount>0,其就不会被释放。<strong>这种区别体现在:在MRC+NSAutoReleasePool我们需要控制其retainCount来使得对象及时地被释放,在ARC中,我们需要控制其referencingCount>0来使得对象不被释放,即MRC默认不释放对象,ARC默认释放对象。</strong>

    前面,我讲到ARC机制对对象的retainCount进行了封装,使得我们不能向NSObject对象发送retain和release消息。因此,在ARC机制中,我们想使用NSAutoReleasePool对象,但我们是不能编写[NSAutoReleasePoolObject release];这样的代码的。如何解决?
    使用@autoreleasepool Block。
    @autoreleasepool {
    //代码
    }

    使用@autoreleasepool Block在ARC机制中的作用

    我们可以像通过“向对象发送autorelease消息”使得对象的referencingCount在@autoreleasepool Block结束前始终>=1。
    如何实现呢?通过所有者修饰符为__autoreleasing的指针来实现。
    Person __autoreleasing *peronObject;
    在ARC机制中,当我们将一个对象赋值给一个所有者修饰符为__autoreleasing的指针时,该对象的referencingCount会+1。
    验证

          Person* __autoreleasing  kaka = [Person new];
          NSLog(@"kaka retainCount %ld ",_objc_rootRetainCount(kaka));
          代码的执行结果
          2015-11-25 14:54:16.821 Test_ARC[9110:93379] kaka retainCount 1 
    

    <strong>有一点我要讲清楚的是,将一个对象赋值给所有者修饰符为__autoreleasing的指针时,该指针并没有成为该对象的“持有者”,而是NSAutoReleasePool中的_objects类属性成为该对象的“持有者”。</strong>
    验证一:__autoreleasing指针不会成为任何对象的持有者

          Person* kaka = [Person new];
          Person* caicai = [Person new];
          Person* __autoreleasing  point1 = kaka;
          point1 = caicai;
          NSLog(@"kaka retainCount %ld ",_objc_rootRetainCount(kaka));
          代码执行结果:
            2015-11-25 14:59:35.268 Test_ARC[9411:96732] kaka retainCount 2 
    

    验证二:是NSAutoReleasingPool中的相关属性成为该对象的“持有者”

         Person* kaka = [Person new];
            Person* caicai = [Person new];
            Person* __autoreleasing  point1 = kaka;
            point1 = caicai;
            _objc_autoreleasePoolPrint();
            代码执行结果:
        objc[9788]: ##############
        objc[9788]: AUTORELEASE POOLS for thread 0x100080000
        objc[9788]: 3 releases pending.
        objc[9788]: [0x101800000]  ................  PAGE  (hot) (cold)
        objc[9788]: [0x101800038]  ################  POOL 0x101800038
        objc[9788]: [0x101800040]       0x100600230  Person
        objc[9788]: [0x101800048]       0x100600290  Person
        objc[9788]: ##############
        ```
     因此,对于之前我提到的问题,编译器可能会通过声明一个所有权为__autoreleasing的指针来解决。
     
     
    ###函数传值以及Block捕获的优化
    刚刚我谈到可以通过一个所有权为__autoreleasing的指针来解决我们的问题,但是该解决方案却还是有不足的地方。这个不足的体现在,当NSAutoReleasePool成为一个对象的持有者之后,需要等到autoreleasepool Block结束,其才会被释放对该对象的持有,该对象才会释放。
    但是对于我提出的那个问题,我希望能够在dispatch_asycn函数所带的Block执行完毕后该对象就被释放,如何解决?
    为此,ARC机制专门对此进行了优化。
    我们先来看一下其优化后的代码
    

    if(YES){
    Person ben = [Person new];
    objc_autoreleaseReturnValue(ben);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    objc_retainAutoreleaseRetrunValue(ben);
    [ben show];
    [ben walk];
    });

    }

    objc_autoreleaseReturnValue(ben);和objc_retainAutoreleaseRetrunValue(ben);是什么意思呢?简单理解,就是找个对象A成为ben的持有者(referencingCount++),然后再使该对象A释放对ben的持有(referencingCount--)。这样我们就可以实现不使NSAutoReleaePoll成为一个对象的持有者,也能安全地进行传递对象。
    <strong>事实上ARC机制在很多情况下都使用了这种优化。譬如,NSArray *arrayObjectB = [arrayObjectA copy];这种情况编译器也是通过这种优化,来确保NSArray对象的copy方法返回的参数不被立刻释放。</strong>
    
    ##4、所有者修饰符之__unsafe_unretained
    有时候,我们只是希望一个指针可以指向一个对象,但并不希望改变referencingCount,这时我们可以将该指针的所有者修饰符指定为__unsafe_unretained。
     示例代码:
    

    Person ben = [Person new];
    NSLog(@"ben retainCount:%d",_objc_rootRetainCount(ben));
    Person
    __unsafe_unretained wildPoint = ben;
    NSLog(@"ben retainCount:%d",_objc_rootRetainCount(ben));

    代码执行结果:
    2015-11-25 16:40:41.784 Test_ARC[2298:24597] ben retainCount:1
    2015-11-25 16:40:41.785 Test_ARC[2298:24597] ben retainCount:1

    ##5、所有者修饰符之__weak
    上面我讲到了所有者修饰符__unsafe_unretained,我们很容易发现使用这种所有者修饰符的指针有一个不安全的地方,那就是当其指向的对象被释放时,其依旧指向该对象被释放的地址,如果这时我们尝试通过该指针向对象发送消息,很容易造成程序运行异常。
    
    为了解决这个问题,ARC机制提供了另外一种所有者修饰符__weak。
    <strong>__weak的出现就是为了解决__unsafe_unretained所带了的不安全问题。</strong>
    在ARC机制中,所有者修饰符为__weak的指针,不会改变其指向对象的referencingCount,但是当其指向的对象被释放时,其为被赋值为nil。这就确保了其被不会像__unsafe_unretained一样,存在安全隐患。
    那ARC机制是如何实现当__weak指向的变量被释放时,该指针被赋值为nil。
    我是这么理解
    

    @implementation NSObject : NSObject
    {
    Set _weakTable;
    int retainCount;
    int referencingCount;
    }
    -(void)destoryWeak
    {
    for id* point in self->_weakTable
    {
    *point = nil;
    }
    }
    -(void)dealloc
    {
    [self destoryWeak];
    }
    @end

    void addWeakPoint(id* point,id object)
    {
    [object->_weakTable addObject:point];
    }
    void removeWeakPoint(id* point,id object)
    {
    [object->_weakTable removeOject:point];
    }

    即NSObject对象内部存在一张散列表,用于存储指向该对象的__weak指针,当该对象将要被释放时,在其dealloc方法中调用destoryWeak方法
    上面的addWeakPoint和removeWeakPoint两个C语言函数则是用于向一个NSObject对象的weakTable中进行添加和删除__weak指针。
    
    示例代码:
    
       Person *ben = [Person new];
       Person* ben2 = [Person new];
       Person* __weak weakPoint = ben;
       weakPoint = ben2;
    
     编译器可能会对其插入代码,使其变成这样:
    
       Person *ben = [Person new];
       objc_addReferencingCount(ben);
       
       Person* ben2 = [Person new];
       objc_addReferencingCount(ben2);
       
       Person* __weak weakPoint = ben;
       addWeakPoint(&weakPoint,ben);
       
       removeWeakPoint(&weak,ben);
       weakPoint = ben2;
       addWeakPoint(&weakPoint,ben2);
    ```
    

    ARC中关于__weak指针的一点优化

    当我们访问一个__weak指针指向的变量时,该__weak指针可能随时会被设置为nil。因此,编译器会将对其进行函数传值和Block捕值一样的优化

    示例:
    
            Person*  ben = [Person new];
            Person* __weak weakPoint = ben;
            NSLog(@"ben retainCount:%d",_objc_rootRetainCount(ben));
            NSLog(@"ben retainCount:%d",_objc_rootRetainCount(weakPoint));
            NSLog(@"ben retainCount:%d",_objc_rootRetainCount(ben));
           代码执行结果:
           2015-11-25 17:27:33.028 Test_ARC[4889:47866] ben retainCount:1
          2015-11-25 17:27:33.030 Test_ARC[4889:47866] ben retainCount:2
          2015-11-25 17:27:33.030 Test_ARC[4889:47866] ben retainCount:1
    

    Property

    前面我们了解了ARC中的所有者修饰符,由于我们常常在类的接口中使用@property来生成属性和属性的property。因此,我们需要了解在ARC中,如何指定使用@property生成的属性的所有者修饰符

     参数                           所有者修饰符
     assign                     __unsafe_unretained
     retain                     __strong
     copy                       __strong
     weak                       __weak
     unsafe_unretained          __unsafe_unretained
     strong                     __strong
    

    写在最后

    这篇文章只是我个人对ARC的一些理解,很多实现肯定与Apple的实现不同,请各位不同喷我。
    ARC机制的核心其实就是“持有者计数”。

    相关文章

      网友评论

        本文标题:Objective-C内存管理——我所理解的ARC

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