美文网首页
内存管理-MRC与ARC

内存管理-MRC与ARC

作者: 依米米一 | 来源:发表于2021-05-27 13:30 被阅读0次

    引用计数

    在iOS中,使用引用计数来管理OC对象的内存
    1、一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
    2、调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
    3、引用计数存在优化过的isa指针中(19位存放引用计数,不够存储的时候has_sidetable_rc变为1,若不够存储就存到SideTable中的refcountMap散列表中
    4、当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它

    MRC Manual Reference Counting 手动引用计数(手动内存管理)

    ARC Automatic Reference Counting 自动引用计数(自动内存管理)

    一、MRC

    简单的说:谁retain谁release

    @interface ViewController ()
    //retain 引用计数+1
    @property (retain, nonatomic) NSMutableArray *data;
    @end
    
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
     //三种方式
    //1、最原始的写法  
    // self.data = [[NSMutableArray alloc] init];
    // [self.data release];
    
    //2、写法不需要release 内部已经autorelease、  
     //self.data = [NSMutableArray array];
    
     // 3、autorelease 不需要再去release   
     //self.data = [[[NSMutableArray alloc] init] autorelease];
    }
    
    - (void)dealloc {
        self.data = nil;
        [super dealloc];
    }
    

    二、ARC

    ARC 都帮我们做了什么?
    LLVM + Runtime互相协调, ARC 利用LLVM编译器自动帮我生成release和retain,autorelease相当于开启了ARC,弱引用就要用到Runtime,在程序运行过程中监控到对象销毁的时候,会把对象对应的弱引用都清空,不是编译器的功劳是runtime功劳

    自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage

    调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的

    看实例,简单的类创建和释放过程

    1、自动给释放池

       @autoreleasepool {
                MJPerson *person = [[[MJPerson alloc] init] autorelease];
        }
    

    2、编译c++代码如下

     {
        __AtAutoreleasePool __autoreleasepool;
        MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
     }
    

    3、c++代码简化后,申明了__AtAutoreleasePool局部变量,创建MJPerson类

     {
     __AtAutoreleasePool __autoreleasepool;//申明局部变量 里面是一个结构体
     MJPerson *person = [[[MJPerson alloc] init] autorelease];//创建person
     }
    

    4、__AtAutoreleasePool再往底层,里面实际是一个结构体,结构体里是一个构造函数,一个析构函数

     struct __AtAutoreleasePool {
        __AtAutoreleasePool() { // 构造函数,在创建结构体的时候调用
            atautoreleasepoolobj = objc_autoreleasePoolPush();
        }
     
        ~__AtAutoreleasePool() { // 析构函数,在结构体销毁的时候调用
            objc_autoreleasePoolPop(atautoreleasepoolobj);
        }
        void * atautoreleasepoolobj;
     };
    

    5、所以又把最初的自动释放池创建类简化为三行代码,主要是 构造函数:atautoreleasepoolobj = objc_autoreleasePoolPush(); 及析构函数:objc_autoreleasePoolPop(atautoreleasepoolobj);

    @autoreleasepool {
               MJPerson *person = [[[MJPerson alloc] init] autorelease];
        }
    

    简化后

     atautoreleasepoolobj = objc_autoreleasePoolPush();构造函数
     MJPerson *person = [[[MJPerson alloc] init] autorelease];
     objc_autoreleasePoolPop(atautoreleasepoolobj);//析构函数
    

    5.1 构造函数push atautoreleasepoolobj = objc_autoreleasePoolPush();

    c语言的objc_autoreleasePoolPush()会调用c++类的AutoreleasePoolPage::push()方法

     void *
     objc_autoreleasePoolPush(void)
     {
         return AutoreleasePoolPage::push();
     }
    

    push底层内部实现

      static inline void *push() 
        {
            id *dest;
            if (slowpath(DebugPoolAllocation)) {
                // Each autorelease pool starts on a new pool page.
                dest = autoreleaseNewPage(POOL_BOUNDARY);
            } else {
                dest = autoreleaseFast(POOL_BOUNDARY);
            }
            ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
            return dest;
        }
    

    5.2 析构函数pop objc_autoreleasePoolPop(atautoreleasepoolobj);

     void
     objc_autoreleasePoolPop(void *ctxt)
     {
         AutoreleasePoolPage::pop(ctxt);
     }
    

    pop内部实现

    template<bool allowDebug>
        static void
        popPage(void *token, AutoreleasePoolPage *page, id *stop)
        {
            if (allowDebug && PrintPoolHiwat) printHiwat();
    
            page->releaseUntil(stop);
    
            // memory: delete empty children
            if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
                // special case: delete everything during page-per-pool debugging
                AutoreleasePoolPage *parent = page->parent;
                page->kill();
                setHotPage(parent);
            } else if (allowDebug && DebugMissingPools  &&  page->empty()  &&  !page->parent) {
                // special case: delete everything for pop(top)
                // when debugging missing autorelease pools
                page->kill();
                setHotPage(nil);
            } else if (page->child) {
                // hysteresis: keep one empty child if page is more than half full
                if (page->lessThanHalfFull()) {
                    page->child->kill();
                }
                else if (page->child->child) {
                    page->child->child->kill();
                }
            }
        }
    

    5.3 调用c++中push中有autoreleaseNewPage, pop中AutoreleasePoolPage,所以都用到AutoreleasePoolPage,因此自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage

    6、__AtAutoreleasePool内部是两个结构体,objc_autoreleasePoolPush以下简称push, objc_autoreleasePoolPop 简称push

    这两个里又用到AutoreleasePoolPage数据结构,所以底层都是靠AutoreleasePoolPage来管理,调用autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的
    去NSObject中查看AutoreleasePoolPage类,objc4源码:NSObject.mm

    6.1 AutoreleasePoolPage类中的有用的成员变量

    成员变量

    6.2 AutoreleasePoolPage内部结构,0x1000-0x2000,共4096个字节

    6.2.1 、0x1000-0x1038段存成员变量

    由图可知0x1000转十进制为4096------0x1038转十进制4152,一共56个字节,用来存储成员变量

    6.2.2 、0x1038-0x2000存autorelease对象的地址4040个字节

    0x1038转十进制为4152------0x2000转十进制8192,一共4040个字节,用来存放autorelease对象的地址

    6.2.3 、 AutoreleasePoolPage对象通过双向链表链接

    所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起,用child指针指向下一个AutoreleasePoolPage,用parent指针指向上一个AutoreleasePoolPage
    若剩下的(0x1038到0x2000之间)4040个字节中不够存放autorelease对象,会创建下一个 AutoreleasePoolPage对象(一般一个对象8个字节)


    AutoreleasePoolPage内部结构
    AutoreleasePoolPage中,查看底层代码
    begin() 实现,返回的是这个指针的地址和大小
    id * begin() {
            return (id *) ((uint8_t *)this+sizeof(*this));
        }
    
    end() 实现,得到整个地址大小SIZE=4096个字节
     id * end() {
            return (id *) ((uint8_t *)this+SIZE);
        }
    

    注意:不是一个person对象对应创建一个AutoreleasePoolPage,也不是一个pool池创建一个AutoreleasePoolPage,是放不下的时候才会创建多个,如1000个person对象至少需要8000个字节,因为一个AutoreleasePoolPage中能存储autorelease对象一共是4040个,那么至少是需要两个AutoreleasePoolPage才够存放,存放方式

    6.3 AutoreleasePoolPage中进栈出栈过程(栈不是指的栈区,这部分存在数据块区)

    push过程

    6.3.1 一个AutoreleasePool

    一个对象调用objc_autoreleasePoolPush(push)方法会将一个POOL_BOUNDARY等于0入栈,表示边界,并且返回其存放的内存地址
    POOL_BOUNDARY放在autorelease的首位置 接下来的位置存放person1 person2 .。。。

     @autoreleasepool {
                // atautoreleasepoolobj = objc_autoreleasePoolPush();
            MJPerson *p1 = [[[MJPerson alloc] init] autorelease];
            MJPerson *p2 = [[[MJPerson alloc] init] autorelease];
                //        objc_autoreleasePoolPop(atautoreleasepoolobj);
            }
    

    查看源码
    objc_autoreleasePoolPush是将一开始的POOL_BOUNDARY压入page栈中,再把对象地址返回(可以自行查看源码),不够哟过时创建下一个page

     page->add(POOL_BOUNDARY);
    
    page中autorelease对象的存放

    6.3.1 多个AutoreleasePool嵌套

    @autoreleasepool { //  r1 = push()
            
            MJPerson *p1 = [[[MJPerson alloc] init] autorelease];
            MJPerson *p2 = [[[MJPerson alloc] init] autorelease];
            MJPerson *p21 = [[[MJPerson alloc] init] autorelease];
            MJPerson *p22 = [[[MJPerson alloc] init] autorelease];
            MJPerson *p23 = [[[MJPerson alloc] init] autorelease];
            @autoreleasepool { // r2 = push()
                for (int i = 0; i < 600; i++) {
                    MJPerson *p3 = [[[MJPerson alloc] init] autorelease];
                }
                @autoreleasepool { // r3 = push()
                    MJPerson *p4 = [[[MJPerson alloc] init] autorelease];
                  _objc_autoreleasePoolPrint();
                } // pop(r3)
            } // pop(r2)
        } // pop(r1)
    

    打印结果,可以看出换页了,有full hot标记

    objc[37067]: ##############
    objc[37067]: AUTORELEASE POOLS for thread 0x1000e7e00
    objc[37067]: 609 releases pending.
    bjc[37067]: [0x106009000]  ................  PAGE (full)  (cold)
    objc[37067]: [0x106009038]  ################  POOL 0x106009038
    objc[37067]: [0x106009040]       0x10052b610  MJPerson
    objc[37067]: [0x106009048]       0x10052ac30  MJPerson
    objc[37067]: [0x106009050]       0x100529dc0  MJPerson
    objc[37067]: [0x106009058]       0x100529830  MJPerson
    objc[37067]: [0x106009060]       0x100529350  MJPerson
    objc[37067]: [0x106009068]  ################  POOL 0x106009068
    objc[37067]: [0x106009070]       0x1005294b0  MJPerson
    objc[37067]: [0x106009078]       0x100524da0  MJPerson
    objc[37067]: [0x106009080]       0x100529040  MJPerson
    .
    .
    .省略
    objc[37067]: [0x106009ff8]       0x10052f380  MJPerson
    objc[37067]: [0x100810000]  ................  PAGE  (hot) 
    objc[37067]: [0x100810038]       0x10052f390  MJPerson
    objc[37067]: [0x100810040]       0x10052f3a0  MJPerson
    objc[37067]: [0x100810048]       0x10052f3b0  MJPerson
    .
    .
    objc[37067]: [0x100810360]       0x10052f9e0  MJPerson
    objc[37067]: [0x100810368]  ################  POOL 0x100810368
    objc[37067]: [0x100810370]       0x10052f9f0  MJPerson
    objc[37067]: ##############
    

    注:............... PAGE (full) (cold)表示已满,................ PAGE (hot) 表示当前活跃的page

    这里需要至少两个page,里面的存放方式是每一个autoreleasepool的push都会有一个POOL_BOUNDARY


    跨page存放

    pop 过程

    查看源码

    template<bool allowDebug>
        static void
        popPage(void *token, AutoreleasePoolPage *page, id *stop)
        {
            if (allowDebug && PrintPoolHiwat) printHiwat();
    
            page->releaseUntil(stop);
    
            // memory: delete empty children
            if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
                // special case: delete everything during page-per-pool debugging
                AutoreleasePoolPage *parent = page->parent;
                page->kill();
                setHotPage(parent);
            } else if (allowDebug && DebugMissingPools  &&  page->empty()  &&  !page->parent) {
                // special case: delete everything for pop(top)
                // when debugging missing autorelease pools
                page->kill();
                setHotPage(nil);
            } else if (page->child) {
                // hysteresis: keep one empty child if page is more than half full
                if (page->lessThanHalfFull()) {
                    page->child->kill();
                }
                else if (page->child->child) {
                    page->child->child->kill();
                }
            }
        }
    

    page->releaseUntil(stop);有一句这个代码,release直到stop,stop的标记就是之前的POOL_BOUNDARY位置

    总结

    整个ARC的过程实际就是进栈出栈过程,其中最底层由AutoreleasePoolPage来管理

    7、autorelease释放时机

    存在两种情况
    1、有 @autoreleasepool {}
    这种情况的调用时机就是上面的讲解过程,就是调用pop的时候就会释放,也就是大括号结束的地方
    2、没有autoreleasepool大括号, MJPerson *person = [[[MJPerson alloc] init] autorelease];这种情况

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        NSLog(@"1");
        MJPerson *person = [[MJPerson alloc] init];
        NSLog(@"3");
        NSLog(@"%s", __func__);
        
    }
    
    - (void)viewWillAppear:(BOOL)animated
    {
        [super viewWillAppear:animated];
        
        NSLog(@"%s", __func__);
    }
    
    - (void)viewDidAppear:(BOOL)animated
    {
        [super viewDidAppear:animated];
        
        NSLog(@"%s", __func__);
    }
    

    运行结果

    2021-05-27 13:21:22.093420+0800 Interview18-autorelease时机[37726:5017612] 1
    2021-05-27 13:21:22.093579+0800 Interview18-autorelease时机[37726:5017612] 3
    2021-05-27 13:21:22.093675+0800 Interview18-autorelease时机[37726:5017612] -[ViewController viewDidLoad]
    2021-05-27 13:21:22.093770+0800 Interview18-autorelease时机[37726:5017612] -[MJPerson dealloc]
    2021-05-27 13:21:22.102455+0800 Interview18-autorelease时机[37726:5017612] -[ViewController viewWillAppear:]
    2021-05-27 13:21:22.112922+0800 Interview18-autorelease时机[37726:5017612] -[ViewController viewDidAppear:]
    

    根据答应结果可能会回答释放时机是在viewDidLoad之后,viewWillAppear之前,这种回答是很片面的

    释放时机全面回答:

    与runloop有关。会在所处的loop休眠之前进行release
    iOS在主线程的Runloop中注册了2个Observer
    第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
    第2个Observer监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
    监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()

    问题: 方法里有局部对象, 出了方法后会立即释放吗
    1、如果局部对象 是在autorelease里
    在某次RunLoop循环中,RunLoop休眠之前调用了release
    2、 如果arc生成的调用release
    代码[p release] 会立即释放

    相关文章

      网友评论

          本文标题:内存管理-MRC与ARC

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