Logo Image
Published on

ios内存原理(2)

Authors
  • Profile Image
    Name
    jamesitachi
Table of Contents

经过前面文档我们大致了解了arc/mrc机制,是为了更好的去做iOS的内存管理

前言

经过前面文档我们大致了解了arc/mrc机制,是为了更好的去做iOS的内存管理.在面对一个对象的时候,从初始化到delloc销毁的过程中进行合适的插入 retainrelease 做到内存的回收释放♻️,随着iOS的迭代发展,我们开始逐渐采用一种解放程序员的新型管理模式 ARC …

什么是ARC/MRC

Manual Reference Counting 是根据对象的引用计数器 reference count 来判断在何时将一个对象所占用的内存回收 Automatic Reference Counting 自动引用计数 是苹果子WWDC2011年引入的,其工作原理下面详细理解,根本上是通过强指针来判断

AutoreleasePool

从main函数开始了解:

int main(int argc, const char * argv[]) {
   @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

在这个autoreleasepool的block里面 所有的事件,消息,全部转交给了UIApplication来处理,从此可以看出一个iOS应用是完全包裹在自动释放池里面的

通过clang 编译器将main函数转换成了 main.cpp

   $ clang -rewrite-objc main.m

我们将关注点放在__AtAutoreleasePool这个结构体上面

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

不难看出我们main函数的工作原理大致上是在初始化时候调用了

void * atautoreleasepoolobj = objc_autoreleasePoolPush();

然后在析构时候调用了

objc_autoreleasePoolPop(atautoreleasepoolobj);```

所以@autoreleasepool会被转换成上面两行代码

> objc_autoreleasePoolPush

从[源码](https://opensource.apple.com/source/objc4/objc4-532/runtime/NSObject.mm.auto.html "标题")中我们看到

```objc
void *_objc_autoreleasePoolPush(void) { return objc_autoreleasePoolPush(); }
void _objc_autoreleasePoolPop(void *ctxt) { objc_autoreleasePoolPop(ctxt); }

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

void
objc_autoreleasePoolPop(void *ctxt)
{
    if (UseGC) return;

    // fixme rdar://9167170
    if (!ctxt) return;

    AutoreleasePoolPage::pop(ctxt);
}

push和pop是操作这一个 AutoreleasePoolPage 对象

AutoreleasePoolPage

class AutoreleasePoolPage 
{

#define POOL_SENTINEL 0
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    static size_t const SIZE = 
#if PROTECT_AUTORELEASEPOOL
        4096;  // must be multiple of vm page size
#else
        4096;  // size and alignment, power of 2
#endif
    static size_t const COUNT = SIZE / sizeof(id);

    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;

    // SIZE-sizeof(*this) bytes of contents follow

    static void * operator new(size_t size) {
        return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
    }
    static void operator delete(void * p) {
        return free(p);
    }

    inline void protect() {
#if PROTECT_AUTORELEASEPOOL
        mprotect(this, SIZE, PROT_READ);
        check();
#endif
    }

    inline void unprotect() {
#if PROTECT_AUTORELEASEPOOL
        check();
        mprotect(this, SIZE, PROT_READ | PROT_WRITE);
#endif
    }
   

从上面的源码简略分析一下 AutoreleasePoolPage 类 包含了

  • magic 用于校验AutoreleasePoolPage的完整性
  • thread 保存了page所在的线程
  • size_t 常量4096字节大小 0X1000
  • parent 父指针
  • child 子指针 通过父子指针来构造出一条双向链表

AutoreleasePoolPage 类是有56bit用来存储成员变量 剩余的都是用来存储加入自动释放池的对象 通过begin() end() 来帮我们快速获取到内存地址的边界 next()指向了下一个page的一片区域

POOL_SENTINEL(哨兵对象)

#define POOL_SENTINEL nil 哨兵对象只是nil的一个别称,当每一次push()压栈的时候 都会把一个nil对象放在栈顶 并且会返回该哨兵对象

当调用pop()时候就会向释放池里面发送release消息 直到出现了第一个 POOL_SENTINEL

//1.先调用push
void *objc_autoreleasePoolPush(void) {
    return AutoreleasePoolPage::push();
}
//2.push定义如下
static inline void *push() {
   return autoreleaseFast(POOL_SENTINEL);
}
//3.在这里会进入一个比较关键的方法 autoreleaseFast,并传入哨兵对象 POOL_SENTINEL
static inline id *autoreleaseFast(id obj)
{
   AutoreleasePoolPage *page = hotPage();
   if (page && !page->full()) {
       return page->add(obj);
   } else if (page) {
       return autoreleaseFullPage(obj, page);
   } else {
       return autoreleaseNoPage(obj);
   }
}
//4 将对象添加到自动释放池页中
id *add(id obj) {
    id *ret = next;
    *next = obj;
    next++;
    return ret;
}

上述的代码逻辑很简单就是 1.pool 调用poolpage的push 2.page 调用push()收到一个返回参数 POOL_SENTINEL 告诉我们该压入对象的栈顶内存地址
3.fast 会根据pool_sentinel 的地址来计算

  • 有hotPage 并且当前 page 不满调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
  • 有 hotPage 并且当前 page 已满调用 autoreleaseFullPage 初始化一个新的页调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
  • 无 hotPage 调用 autoreleaseNoPage 创建一个 hotPage调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中 4.最终的执行代码都是page去调用了一个 add()方法

最终我们流程跑完了总结一个 方法的调用栈

[NSObject autorelease]
└── id objc_object::rootAutorelease()
    └── id objc_object::rootAutorelease2()
        └── static id AutoreleasePoolPage::autorelease(id obj)
            └── static id AutoreleasePoolPage::autoreleaseFast(id obj)
                ├── id *add(id obj)
                ├── static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
                │   ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
                │   └── id *add(id obj)
                └── static id *autoreleaseNoPage(id obj)
                    ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
                    └── id *add(id obj)