iOS底层-内存管理(二)

OC对象的内存管理

  1. 在iOS中,使用引用计数来管理OC对象的内存
  2. 一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
  3. 调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
  4. 内存管理的经验总结
    1. 当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它
    2. 想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1
  5. 以上的示例以及详解请到OC语法-内存管理部分,这里不再详细讲
  6. 可以通过以下私有函数来查看自动释放池的情况
    1. extern void _objc_autoreleasePoolPrint(void);

引用计数的存储

  1. 前面讲runtime的isa的本质的时候讲过,isa使用位域技术存储一些数据,其中就有引用计数器的值

     uintptr_t has_sidetable_rc  : 1;
     uintptr_t extra_rc          : 19;
    
    1. 里面存储的值是引用计数器减1
    2. 当引用计数器是否过大无法存储在isa中时,has_sidetable_rc至1,引用计数会存储在一个叫SideTable的类的属性中
  2. 在64bit中,引用计数可以直接存储在优化过的isa指针中,也可能存储在SideTable类中

     struct SideTable {
         spinlock_t slock;
         //map容器,相当于字典,用于存储引用计数器的值
         RefcountMap refcnts;
         //用于存储当前对象的弱引用计数器值
         weak_table_t weak_table;
         ...
     }
    
    1. refcnts是一个存放着对象引用计数的散列表
  3. retainCount方法的源码

     //返回引用计数器的值
     - (NSUInteger)retainCount {
         return ((id)self)->rootRetainCount();
     }
     //获取引用计数器的值
     inline uintptr_t 
     objc_object::rootRetainCount()
     {
         //1. 如果是TaggedPointer(当前不是对象类型),直接返回自己
         if (isTaggedPointer()) return (uintptr_t)this;
            
         //可能会多线程同时访问一个容器,因此需要加锁
         //加锁
         sidetable_lock();
            
         //2. 拿到isa的bits
         isa_t bits = LoadExclusive(&isa.bits);
            
         ClearExclusive(&isa.bits);
         //3. 判断是否采用位域技术优化,即isa位域存储优化
         if (bits.nonpointer) {
             //3.1 是isa优化,那么就到isa的位中分离出来
                
             // extra_rc+1 = 引用计数器的值
             uintptr_t rc = 1 + bits.extra_rc;
                
             //3.2 判断extra_rc是否不够存储,即是否存满,足够存储,直接返回r
             if (bits.has_sidetable_rc) {
                 //3.3 不够存储,到sidetable找,此时的引用计数器=extra_rc+1 + sidetable存储的值
                 rc += sidetable_getExtraRC_nolock();
             }
             //解锁
             sidetable_unlock();
             return rc;
         }
         //解锁
         sidetable_unlock();
         //4. 没有采用位域技术,即isa仅仅存储的是地址,那么就直接到sidetable类中找
         return sidetable_retainCount();
     }
        
     /**
      额外的引用计数器值
     当前isa的extra_rc位不够存储,即has_sidetable_rc为1时,多余的计数器值会存储到这里
      @return 额外的计数器值
      */
     size_t
     objc_object::sidetable_getExtraRC_nolock()
     {
         assert(isa.nonpointer);
         SideTable& table = SideTables()[this];
         //map容器查找,采用指示器
         RefcountMap::iterator it = table.refcnts.find(this);
         //没有找到
         if (it == table.refcnts.end()) return 0;
         //找到了,返回: it->second值,右移2位
         else return it->second >> SIDE_TABLE_RC_SHIFT;
     }
        
     /**
      isa是非位域优化指针时
      直接到sidetable类中查找引用计数器的值
        
      @return 值
      */
     uintptr_t
     objc_object::sidetable_retainCount()
     {
         //拿到table
         SideTable& table = SideTables()[this];
           
         size_t refcnt_result = 1;
            
         table.lock();
         RefcountMap::iterator it = table.refcnts.find(this);
         if (it != table.refcnts.end()) {//指示器不是最后一位,即计数器的值不为0
             //返回引用计数器的值:it->second 右移2位,然后+1
             // this is valid for SIDE_TABLE_RC_PINNED too
             refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
         }
         table.unlock();
         return refcnt_result;
     }
    

weak指针的原理

  1. 示例

     __strong Person *person1;
     //弱引用,安全,一旦指向的对象销毁了,会将这个指针设置为nil
     __weak Person *person2;
     //弱引用,不安全,一旦指向的对象销毁了,这个指针仍然指向那个已经销毁的内存,会出现野指针错误
     __unsafe_unretained Person *person3;
     NSLog(@"1111");
     {
            
     Person *person = [[Person alloc] init];
            
         person3 = person;
            
     }
     //当为person3时会崩溃
     NSLog(@"222222==%@",person3);
    
  2. 打印:

     //person1指向
     1111
     222222
     -[Person dealloc]
         
     //person2指向
     1111
     -[Person dealloc]
     222222
         
     //person3指向
     1111
     -[Person dealloc]
     //崩溃
    
  3. weak的实现原理:就是想知道,weak指针如何办到,当他指向的对象销毁时,将当前的指针至nil的

    1. 从上面的代码可以看到,当weak时,对象会调用dealloc,那么我们分析一下dealloc的原理
    2. 当一个对象要释放时,会自动调用dealloc,接下的调用轨迹是
    3. 在objc源码中的NSObject.mm文件中
      1. dealloc方法

         //对象的dealloc方法
         // Replaced by NSZombies
         - (void)dealloc {
             _objc_rootDealloc(self);
         }
        
      2. _objc_rootDealloc

         void
         _objc_rootDealloc(id obj)
         {
             assert(obj);
                    
             obj->rootDealloc();
         }
        
      3. rootDealloc

         /**
          对象dealloc方法的本质
          */
         inline void
         objc_object::rootDealloc()
         {
             //如果是TaggedPointer直接返回,不需要dealloc
             if (isTaggedPointer()) return;  // fixme necessary?
                        
             //如果是isa位域优化指针 && 没有被弱引用指向过(!isa.weakly_referenced)&& 没有设置关联对象(!isa.has_assoc) && 没有C++析构函数(!isa.has_cxx_dtor)&& 引用计数器没有溢出(isa内部够存储)(!isa.has_sidetable_rc)
             if (fastpath(isa.nonpointer  &&  
                          !isa.weakly_referenced  &&  
                          !isa.has_assoc  &&  
                          !isa.has_cxx_dtor  &&  
                          !isa.has_sidetable_rc))
             {
                 //直接释放
                 assert(!sidetable_present());
                 free(this);
             } 
             else {
                 //反之,需要调用这个函数释放
                 object_dispose((id)this);
             }
         }
        
      4. object_dispose

         id 
         object_dispose(id obj)
         {
             if (!obj) return nil;
                    
             objc_destructInstance(obj);    
             free(obj);
                    
             return nil;
         }
        
      5. objc_destructInstance

         /**
          dealloc的本质
          */
         void *objc_destructInstance(id obj) 
         {
             if (obj) {
                 //是否有C++析构函数
                 // Read all of the flags at once for performance.
                 bool cxx = obj->hasCxxDtor();
                 //是否有关联对象
                 bool assoc = obj->hasAssociatedObjects();
                    
                 // This order is important.
                 //调用C++析构函数,消除成员变量
                 if (cxx) object_cxxDestruct(obj);
                 //移除关联对象
                 if (assoc) _object_remove_assocations(obj);
                 //将指向当前对象的弱指针至为nil
                 obj->clearDeallocating();
             }
                    
             return obj;
         }
        
      6. clearDeallocating

        1. 这个方法内部会找到当前对象的sidetable属性的weak_table集合存储的弱引用指针
        2. 然后将这个弱引用指针设置为nil

自动释放池

  1. autorelease对象在什么时机会被调用release ?
  2. 示例代码

     #import <Foundation/Foundation.h>
     #import "Person.h"
     int main(int argc, const char * argv[]) {
         @autoreleasepool {
             Person *person = [[[Person alloc] init] autorelease];
         }
         return 0;
     }
    
  3. 编译成C++后

     { 
         //1. 定义一个__AtAutoreleasePool类型的变量,会调用他的构造函数 __AtAutoreleasePool();
         __AtAutoreleasePool __autoreleasepool;
            
         //2. 调用
         Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
         //等价于
         //Person *person = [[[Person alloc] init] autorelease];
            
         //3. 这时__autoreleasepool这个局部变量将要销毁,会调用__AtAutoreleasePool的析构函数~__AtAutoreleasePool()
     }
        
     //__AtAutoreleasePool结构体对象
     struct __AtAutoreleasePool {
         //构造函数
       __AtAutoreleasePool() {
           atautoreleasepoolobj = objc_autoreleasePoolPush();
              
       }
         //析构函数
       ~__AtAutoreleasePool() {
           objc_autoreleasePoolPop(atautoreleasepoolobj);
              
       }
       //成员变量
       void * atautoreleasepoolobj;
     };
    
    
  4. 代码简化后,第2步源码的本质就是如下:

     //等价于:  @autoreleasepool {
     oatautoreleasepoolobj = objc_autoreleasePoolPush();
        
     Person *person = [[[Person alloc] init] autorelease];
        
     //等价于:  }
     objc_autoreleasePoolPop(atautoreleasepoolobj);
    
  5. 源码分析(objc4源码:NSObject.mm)
    1. 搜索objc_autoreleasePoolPush方法,会看到

       void *
       objc_autoreleasePoolPush(void)
       {
           return AutoreleasePoolPage::push();
       }
      
      1. 本质是调用AutoreleasePoolPage类的成员函数push();
    2. 结论:
      1. 自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage
      2. 调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的
    3. AutoreleasePoolPage类:

       class AutoreleasePoolPage 
       {
           magic_t const magic;
           id *next;
           pthread_t const thread;
           AutoreleasePoolPage * const parent;
           AutoreleasePoolPage *child;
           uint32_t const depth;
           uint32_t hiwat;
           ...略
       }
      

AutoreleasePoolPage的结构

  1. 每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址
  2. 所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起
    1. 因为一个pool中可以有n个对象调用Autorelease,那么一个AutoreleasePoolPage对象是存放不完的,那么就需要新增AutoreleasePoolPage对象存储
    2. 新增的对象必须之间有关联,那就是双向链表

    图4

  3. 调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址
  4. 调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
  5. id *next指向了下一个能存放autorelease对象地址的区域 

Runloop和Autorelease

  1. iOS在主线程的Runloop中注册了2个Observer
    1. 第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
    2. 第2个Observe
      1. 监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()objc_autoreleasePoolPush()
      2. 监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()
  2. 分析:
    1. 情况1:手动创建自动释放池

       - (void)test1 {
                  
           NSLog(@"111111");
           //情况1: 手动创建自动释放池
           @autoreleasepool {
               Person *person = [[[Person alloc] init] autorelease];
                      
           } //会在大括号即“}”处释放,即底层调用objc_autoreleasePoolPop()释放
           NSLog(@"222222");
       }
       //打印
       111111
       -[Person dealloc]
       222222
      
      1. 会在autoreleasepool大括号即“}”处释放,即底层调用objc_autoreleasePoolPop()释放
    2. 情况2: 非手动创建自动释放池

       - (void)viewDidLoad {
           [super viewDidLoad];
           [self test2];
           NSLog(@"%s",__func__);
       }
              
       -(void)viewWillAppear:(BOOL)animated{
           [super viewWillAppear:animated];
            NSLog(@"%s",__func__);
       }
       //在这打断点,发现Person已经执行dealloc
       -(void)viewDidAppear:(BOOL)animated{
           [super viewDidAppear:animated];
            NSLog(@"%s",__func__);
       }
       - (void)test2 {
           NSLog(@"111111");
           Person *person = [[[Person alloc] init] autorelease];
           NSLog(@"222222");
           NSLog(@"%s",__func__);
       }
              
       //打印
       111111
       222222
       -[ViewController test2]
       -[ViewController viewDidLoad]
       -[ViewController viewWillAppear:]
       -[Person dealloc]
       -[ViewController viewDidAppear:]
      
      1. 那么这种事什么时候释放的呢? 是test2的”}”结束时刻?
        1. 从上面打印我们可以看到,person对象是在viewWillAppear执行完毕后释放的
        2. 那不可能每一个对象都是在这时刻释放的,那具体是什么时候释放的呢?
        3. runloop监听释放
      2. 还是main函数内部的@autoreleasepool
        1. 这个很显然不是,因为有runloop,只要程序没有退出,就一直不会执行main函数@autoreleasepool的结尾”}”;
  3. 面试题
    1. autorelease 对象什么时候会被释放?
      1. 情况1: 手动创建自动释放池
        1. 会在autoreleasepool大括号即“}”处释放,即底层调用objc_autoreleasePoolPop()释放
      2. 非手动创建autoreleasepool
        1. 会在runloop的状态是即将休眠`kCFRunLoopBeforeWaiting释放
        2. 会在runloop即将退出时释放kCFRunLoopBeforeExit
Table of Contents