IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> [iOS开发]autoreleasePool -> 正文阅读

[移动开发][iOS开发]autoreleasePool

AutoreleasePoolPage的引入

在这里插入图片描述
什么都没写,仅仅一个hello,world和一个autoreleasePool

可以看到调用了PoolPush和PoolPop两个函数,对于看一下两个函数的实现

void *
objc_autoreleasePoolPush(void)
{
    // 调用了AutoreleasePoolPage中的push方法
    return AutoreleasePoolPage::push();
}

void
objc_autoreleasePoolPop(void *ctxt)
{
    // 调用了AutoreleasePoolPage中的pop方法
    AutoreleasePoolPage::pop(ctxt);
}

可以看到都调用了AutoreleasePoolPage函数
看一下AutoreleasePoolPage这个函数的实现

AutoreleasePoolPage的实现

AutoreleasePoolPage的大小是4096字节

//当只有一个池时,空的\池\占位符存储在TLS中
    
    //已推送,且从未包含任何对象。这样可以节省内存
    
    //当顶层(即libdispatch)推送和弹出池,但
    
    //永远不要使用它们。
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)
    // 哨兵对象
#   define POOL_BOUNDARY nil
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    // AutoreleasePoolPage的大小,通过宏定义,可以看到是4096字节
    static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MAX_SIZE;  // size and alignment, power of 2
    //4096
#endif
    static size_t const COUNT = SIZE / sizeof(id);

    magic_t const magic; // 16字节
    // 一个AutoreleasePoolPage中会存储多个对象
    // next指向的是下一个AutoreleasePoolPage中下一个为空的内存地址(新来的对象会存储到next处)
    id *next; // 8字节 通过begin()方法初始化:
    // 保存了当前页所在的线程(一个AutoreleasePoolPage属于一个线程,一个线程中可以有多个AutoreleasePoolPage)
    pthread_t const thread; // 8字节 当前pool所处的线程
    // AutoreleasePoolPage是以双向链表的形式连接
    // 前一个节点
    
    AutoreleasePoolPage * const parent; // 8字节
    // 后一个节点
    AutoreleasePoolPage *child; // 8字节
    uint32_t const depth; // 4字节 page的深度,首次为0,以后每次初始化一个page都加1。
    uint32_t hiwat; // 4字节 这个字段是high water的缩写,这个字段用来计算pool中最多存放的对象个数。在每次执行pop()的时候,会更新一下这个字段。
    // SIZE-sizeof(*this) bytes of contents follow
    //固定的内容占56字节,之外的可以放入page中的对象地址

在这里插入图片描述
一个AutoreleasePoolPage占4096个字节,扣除56个字节存储上面的信息外,其余的都用来存储加入到page中的对象地址

  • 因为存储了两个指针,parent和child,所以AutoreleasePoolPage的实现方式是双向链表
    请添加图片描述
  • 该结构体的第一个成员变量是magic,我们在isa中也学习过,isa中是分判对象是否未完成初始化,在这里也一样,用来检查这个节点是否已经被初始化了。
  • thread保存了当前页所在的线程
  • depth表示page的深度,首次为0,每个page的大小都是4096字节(16进制0x1000),每次初始化一个page,depth都加一

自动释放池中的栈

假如我们的一个AutoreleasePoolPage被初始化在内存的0x100816000~0x100817000中,它在内存中的结构如下
请添加图片描述
其中有 56 bit 用于存储 AutoreleasePoolPage 的成员变量,剩下的 0x100816038 ~ 0x100817000 都是用来存储加入到自动释放池中的对象。

next指向了下一个为空的内存地址。如果next指向的地址加入一个object,他就会移动到下一个为空的内存地址中
请添加图片描述
hiwat和depth并不影响自动释放池的实现,也不在关键方法的调用栈中。

POOL_SENTINEL(哨兵对象)

除了我们熟悉的上面的结构体成员变量,还有一个哨兵对象,哨兵对象到底是什么,为什么它在栈中?

POOL_SENTINEL 只是 nil 的别名

#define POOL_BOUNDARY nil
(我们现在的哨兵对象叫POOL_SENTINEL,为了方便还是延续上面的叫法)

在每个自动释放池初始化调用objc_autoreleasePoolPush的时候,都会把一个POOL_SENTINELpush到自动释放池的栈顶,并且返回这个哨兵对象

而当方法objc_autoreleasePoolPop调用时,就会想自动释放池中的对象发送release消息,直到第一个POOL_SENTINEL

请添加图片描述

说到这我们就要来看看objc_autoreleasePoolPush方法了

objc_autoreleasePoolPush方法

以及了解了哨兵对象,再回到最开始的起点objc_autoreleasePoolPush方法

void *
objc_autoreleasePoolPush(void) {
    // 调用了AutoreleasePoolPage中的push方法
    return AutoreleasePoolPage::push();
}

我们进入push看一下push的实现

static inline void *push() 
    {
        id *dest;
        // POOL_BOUNDARY就是nil
        // 首先将一个哨兵对象插入到栈顶
        if (DebugPoolAllocation) {
            // 区别调试模式
            // 调试模式下将新建一个链表节点,并将一个哨兵对象添加到链表栈中
            // Each autorelease pool starts on a new pool page.
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
            dest = autoreleaseFast(POOL_BOUNDARY);
            //POOL_BOUNDARY就是nil
        }
        assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }

一个简单的判断是否为调试模式,调试模式下新建一个链表节点,并将一个哨兵对象添加到链表栈中

正常情况下调用autoreleaseFast方法,这个方法的调用仅仅只有一个参数,哨兵对象
也就是说push调用的时候autoreleaseFast参数只有哨兵对象

那么我们“将对象加入自动释放池”这一步操作怎么进行的呢?
其实就不是push这个方法了

我们可以写一下将一个对象加入自动释放池的操作

int main(int argc, const char * argv[]) {
        id __strong obj0 = [[NSObject alloc] init];//生成对象A
        id __autoreleasing obj2 = obj0;
    return 0;
}

就是我们强行让对象入池
在这里插入图片描述
看一下此时的汇编
在这里插入图片描述
objc_retainAutorelease

中间寻找的过程就不展示了,最终就调用了下面的方法

static inline id autorelease(id obj)
    {
        printf("static inline id autorelease%p\n", obj);
        assert(obj);
        assert(!obj->isTaggedPointer());
        id *dest __unused = autoreleaseFast(obj);
        assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        return obj;
    }

这个方法会帮我们穿进去一个参数,是对象

看一下Fast这个方法

    static inline id *autoreleaseFast(id obj)
    {
        // hotPage就是当前正在使用的AutoreleasePoolPage
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {
            // 有hotPage且hotPage不满,将对象添加到hotPage中
            return page->add(obj);
        } else if (page) {
            // 有hotPage但是hotPage已满
            // 使用autoreleaseFullPage初始化一个新页,并将对象添加到新的AutoreleasePoolPage中
            return autoreleaseFullPage(obj, page);
        } else {
            // 无hotPage
            // 使用autoreleaseNoPage创建一个hotPage,并将对象添加到新创建的page中
            return autoreleaseNoPage(obj);
        }
    }

hotPage就是当前正在使用的AutoreleasePoolPage

  • 有hotPage并且当前page不满
    • 调用page->add(obj)方法将对象添加至AutoreleasePoolPage的栈中(这个obj就是哨兵)
  • 有hotPage并且当前的page已满
    • 调用autoreleaseFullPage初始化一个新的页
    • 调用page->add(obj)方法将对象添加至AutoreleasePoolPage的栈中
  • 无hotPage
    • 调用autoreleaseNoPage新建一个hotPage,并将对象添加到新创建的page中

看一下对应的几个方法

page->add 添加对象

去掉不需要我们理解的部分:

id *add(id obj) {
        id *ret = next;
        *next++ = obj;
        return ret;
    }

这其实就是一个压栈操作,将对象加入AutoreleasePoolPage,然后移动栈顶指针

autoreleaseFullPage当前hotPage已满

    static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {
        do {
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full())setHotPage(page);
        return page->add(obj);
    }

它会从传入的page开始遍历整个双向链表,直到

  1. 查找到一个未满的AutoreleasePoolPage
  2. 使用构造器传入Parent创建一个新的AutoreleasePoolPage

在查找到一个可以使用的AutoreleasePoolPage之后,会将该页面标记为hotPage,然后调用page->add方法添加对象

autoreleaseNoPage无hotPage

static id *autoreleaseNoPage(id obj) {
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    setHotPage(page);
    if (obj != POOL_SENTINEL) {
        page->add(POOL_SENTINEL);
    }
    return page->add(obj);
}

既然当前内存中不存在AutoreleasePoolPage,就要从头开始构建这个自动释放池的双线链表,即新的AutoreleasePoolPage是没有parent指针的

初始化之后,将当前也标记为hotPage,然后先向这个page中添加一个哨兵对象,来确保在pop调用的时候不会发生异常,最后,将obj添加到自动释放池中(就是加到这个page中)。

objc_autoreleasePoolPop方法

void
objc_autoreleasePoolPop(void *ctxt) {
    // 调用了AutoreleasePoolPage中的pop方法
    AutoreleasePoolPage::pop(ctxt);
}

看起来传入任何一个指针都是可以的,但是在整个工程并没有发现传入其他对象的例子。不过在这个方法中传入其他指针也是可行的,会将自动释放池释放到相应的位置

看一下pop的逻辑

static inline void pop(void *token) {
    AutoreleasePoolPage *page = pageForPointer(token);
    id *stop = (id *)token;

    page->releaseUntil(stop);

    if (page->child) {
        if (page->lessThanHalfFull()) {
            page->child->kill();
        } else if (page->child->child) {
            page->child->child->kill();
        }
    }
}

可以看到pop是一个静态方法,其总共做了三件事

  • 使用pageForPointer获取当前token所在的AutoreleasePoolPage
  • 调用releaseUntil方法释放栈中的对象、直到stop,stop就是传递的参数,一般为哨兵对象
  • 调用child的kill方法
    • 系统根据当前页的不同状态kill掉不同child的页面
      releaseUntil把page里的对象进行了释放,但是page本身也会占据很多空间,要通过kill()来处理
 如果当前page小于一半满,则把当前页的所有孩子都杀掉,否则,留下一个孩子,从孙子开始杀。正是因为这一步,在autoreleaseFullPage()方法中才会有这两步:[为啥???]
 if (page->lessThanHalfFull()) {
  	 page->child->kill();
 }
 else if (page->child->child) {
   	 page->child->child->kill();   
 }

kill掉child是为了节省内存;
但是当占用一半时,很快需要创建新的page,所以保留一个child,牺牲内存来提升性能。

token

自动释放池的实现:
官方注释

Autorelease pool implementation
A thread’s autorelease pool is a stack of pointers.
Each pointer is either an object to release, or POOL_BOUNDARY which is
an autorelease pool boundary.
A pool token is a pointer to the POOL_BOUNDARY for that pool. When
the pool is popped, every object hotter than the sentinel is released.
The stack is divided into a doubly-linked list of pages. Pages are added
and deleted as necessary.
Thread-local storage points to the hot page, where newly autoreleased
objects are stored.

  • 一个线程的自动释放池是一个指针堆栈
  • 每个指针要么指向被释放的对象,要么是作为自动释放池边界的POOL_BOUNDARY
  • token是指向该pool的POOL_BOUNDARY的指针。
  • 当池pop时,任何比哨兵更hotter的物体都会被释放?
  • pool被分成一个双向指针构成的pages,pages在必要的时候被添加和删除
  • 线程本地存储的指针指向hot page,其中存储新的自动释放对象

  • token的本质就是指向哨兵对象的指针,存储着每次push时插入的POOL_BOUNDARY的地址
  • 只有第一次push的时候会在page中插入一个POOL_BOUNDARY【或者page满了,或者没有hotPage需要使用新的page了】,并不是page的开头都一定是POOL_BOUNDARY

releaseUntil

void releaseUntil(id *stop) {
    while (this->next != stop) {
        AutoreleasePoolPage *page = hotPage();

        while (page->empty()) {
            page = page->parent;
            setHotPage(page);
        }

        page->unprotect();
        id obj = *--page->next;
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
        page->protect();

        if (obj != POOL_SENTINEL) {
            objc_release(obj);
        }
    }

    setHotPage(this);
}

用一个while循环持续释放,一直到next指向stop【token】

问题

  1. 保存autoreleasePoolPage的双向链表只有一个么?也就是所有线程的autoreleasePoolPage都保存在一个链表中,还是每个线程保存一个自己的链表?并且链表头也就是链表的入口位置是保存在哪里呢?谁来控制呢?

一个线程有自己单独autoreleasePool链表,也有可能没有链表。链表的hotPage存储在TLS中,因为链表是双向的,通过hotpage就可以找到表头和表尾,不需要再单独存储表头

  1. autoreleasePoolPage与RunLoop的关系

RunLoop和AutoReleasePool是通过线程的方式一一对应的
对于每一个RunLoop,系统会隐式的创建一个Autorelease pool ,这样所有的release pool会构成会构成一个类似栈式结构,在每一个RunLoop结束时,当前栈顶的Autorelease Pool会被销毁,pool里的每个对象会被release。

在这里插入图片描述

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-12 16:42:49  更:2021-08-12 16:43:40 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 3:30:07-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码