Runtime源码 autoreleasepool

news/2024/7/7 14:09:10

前言

在iOS开发中,由于ARC的普遍使用,内存管理的问题好像不那么常见了,但了解Objective-C的内存管理机制依然是非常必要的,今天我们来看看autoreleasepool的一些细节,在ARC时代几乎很少看到autoreleasepool的身影了,唯一常见的应该就是在main函数中了:

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

这里可以看到整个 iOS 的应用都是包含在一个自动释放池 block 中的。那么这个autoreleasepool到底是什么呢?接下来我们来一窥究竟。

结构

使用clang -rewrite-objc main.m将main函数所在的文件,转化为C++代码:

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}
复制代码

可以看到autoreleasepool其实是类型为__AtAutoreleasePool的结构体,那么它又是什么呢?我们来看看他的定义:

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
}
复制代码

由此,我们可以知道:当使用autoreleasepool时,实际上是:

/* @autoreleasepool */ {
    void *atautoreleasepoolobj = objc_autoreleasePoolPush();
    ...// 自己的代码,接收到 autorelease 消息的对象会被添加到这个 autoreleasepool中
    objc_autoreleasePoolPop(atautoreleasepoolobj);
}
复制代码

它其实是调用了objc_autoreleasePoolPush,这又是什么?怎么越看越多?别急,马上到了,继续吧,打开runtime源码工程,在NSObject.mm文件中我们看到,其实他是:

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}
复制代码

最后,再来看看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 组成的,并且每一个 AutoreleasePoolPage 的大小都是 4096 字节(16 进制 0x1000)根据注释The stack is divided into a doubly-linked list of pages. Pages are added可以知道,他是以双链表的形式存储的,也对应了其结构中的parent和child,接下里我们看看他其他字段:

  • magic
    magic是magic_t类型的结构体,它用来校验 AutoreleasePoolPage结构的完整性
  • *next
    指向最新添加的 autoreleased 对象的下一个位置,初始化时指向 begin()
  • thread
    当前线程
  • parent
    指向父结点,第一个结点的 parent 值为 nil ;
  • child
    指向子结点,最后一个结点的 child 值为 nil ;
  • depth
    代表深度,从 0 开始,往后递增 1;
  • hiwat
    代表 high water mark 。

objc_autoreleasePoolPush()

上面我们看到了objc_autoreleasePoolPush()接下来,看看push的具体操作:

static inline void *push() 
    {
        id *dest;
        if (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;
    }
复制代码

这里有一个debug环境的判断,我们忽略,直接看到autoreleaseFast():

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);
        }
    }
复制代码
  • 当前 page 存在且没有满时:
    直接将对象添加到当前 page 中,即 next 指向的位置;
  • 当前 page 存在且已满时:
    创建一个新的 page ,并将对象添加到新创建的 page 中;
  • 当前 page 不存在时即还没有 page 时:
    创建第一个 page ,并将对象添加到新创建的 page 中。

每调用一次 push 操作就会创建一个新的 autoreleasepool ,即往 AutoreleasePoolPage 中插入一个POOL_BOUNDARY,并且返回插入的 POOL_BOUNDARY的内存地址。

从定义# define POOL_BOUNDARY nil可知:这里的POOL_BOUNDARY其实就是nil的别名

autorelease

接下来我们看看autorelease的实现,追溯源码发现它的函数调用栈很深,依次为:

  1. -(id) autorelease
  2. _objc_rootAutorelease(id obj)
  3. objc_object::rootAutorelease()
  4. objc_object::rootAutorelease2()
  5. static inline id autorelease(id obj)
  6. static inline id *autoreleaseFast(id obj)

它以obj为参数,通过层层调用,最终还是调用到了autoreleaseFast方法,
也就是说:它和 push 操作的实现基本一致,只是不过 push 操作插入的是一个 POOL_BOUNDARY ,而 autorelease 操作插入的是一个具体的 autoreleased 对象。

objc_autoreleasePoolPop()

他其实是调用了:

objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}
复制代码

pop调用到了releaseUntil里面有这样的操作:

while (this->next != stop) {
            // Restart from hotPage() every time, in case -release 
            // autoreleased more objects
            AutoreleasePoolPage *page = hotPage();

            // fixme I think this `while` can be `if`, but I can't prove it
            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_BOUNDARY) {
                objc_release(obj);
            }
        }
复制代码

pop可以说是:以上面说到的POOL_BOUNDARY的内存地址为入参,根据传入的POOL_BOUNDARY,找到这个push所在的位置,对之前入栈的每个antorealse对象都发送一次- release消息,并移动next指针,当指针的地址移动到POOL_BOUNDARY的位置时,这个自动释放池内的对象释放完毕。

应用

  1. 当使用循环时,使用@autoreleasePool能降低内存峰值(详见《Effective Objective-C 2.0》第34条)
  2. 遍历时尽量使用系统的enumerateObjectsUsingBlock,因为其内部有自动释放池,而for或者for-in没有

总结

  • 自动释放池是由 AutoreleasePoolPage 以双向链表的方式实现的。
  • 调用AutoreleasePoolPage::push向栈插入一个nil作为标记,并返回这个地址。
  • 当对象调用 autorelease 方法时,会将对象加入 AutoreleasePoolPage 的栈中(由于后加,所以他们一定在nil所在位置之上)。
  • 调用 AutoreleasePoolPage::pop 方法,传递push函数得到的地址,依次向栈中的对象发送 release 消息,并下移指针,直到push的nil所在位置,即完成了自动释放。

参考资料:
NSAutoreleasePool
黑幕背后的Autorelease
Objective-C Autorelease Pool 的实现原理


http://www.niftyadmin.cn/n/4073957.html

相关文章

ping主机的ip地址,判断以及自动安装sysstat软件包 等脚本

ping主机的ip地址。返回结果vi pinghost.sh#!/bin/bashping -c 3 -i 0.2 -W 3 $1 &> /dev/nullif [ $? -eq 0 ]thenecho "Host $1 is up."elseecho "Host $1 is down."fish pinghost.sh ip地址判断分数所在的区间,给出不同的…

GraphQL 进阶: Apollo Client 之 GraphQL Subscription 和 graphql容器

概述 下面是一个视频和一个GIF动画, 感受一下基于Websocket, 通过GraphQL实现的即时聊天应用是个什么鬼. 视频连接: https://v.qq.com/x/page/x0508... GIF动画 graphql() 函数是一个给组件增加数据逻辑(查询, 修改, 删除)的一个高阶函数, 存在于 react-apollo 模块中, 如果要使…

Ktor 1.0发布:JetBrains推出的Kotlin Web框架

以下由Kotlin中文站翻译自JetBrains官方博客,本文由Kotlin中文站授权转载: 在我们开展Kotlin工作时,我们实现了很多特性,以便用于构建符合使用习惯且高效的API。我们始终期待着能有一个将这些特性发挥得淋漓尽致的Web框架。今天&a…

常用Linq操作

常用&#xff1a;https://zhuanlan.zhihu.com/p/141469940 IEnumerable<int> s new List<int>() { 1,2,3,4,5,6,7,9};IEnumerable<int> res s.Where(t > t > 5);int count s.Count(t > t > 5);int sum s.Sum();IEnumerable<int> res1…

数据库中间件 MyCAT 源码分析:【单库单表】插入

2019独角兽企业重金招聘Python工程师标准>>> ???关注**微信公众号&#xff1a;【芋艿的后端小屋】**有福利&#xff1a; RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址您对于源码的疑问每条…

特性类基础

特性类 using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEngine;[Demo(10,Name "小韩")] public class NewBehaviourScript : MonoBehaviour {// Start is called before the …

微博能否在中国流行?

上周四&#xff0c;流行歌王迈克尔杰克逊(Michale Jackson)在洛杉矶的家中突发心脏病&#xff0c;当他被救护车抬出家门时&#xff0c;有位路人用手机向Twitter发布一条消息&#xff1a;天哪&#xff0c;我在杰克逊家门口&#xff0c;有人被抬出去了&#xff0c;肯定是发生什么…

Swift开发教程--怎样自己定义TabBarItem的图片显示

在做项目的时候&#xff0c;假设使用系统的UITabBarController的时候&#xff0c;底部的tab自己定义图片显示是蓝色和灰色的。这不是我们所想要的效果。 假设想显示自己定义的按下和弹起的图片效果。这个时候就须要对TabBarItem进行改动。 下面就是详细的演示样例&#xff1a; …