AutoReleasePool 底层原理

本文深入探讨了Objective-C中的AutoReleasePool概念,包括它在RunLoop中的作用,以及通过clang编译后的cpp实现和底层的内存管理原理。文章通过汇编分析展示了AutoReleasePool的push和pop操作,解释了如何存储和释放对象,同时揭示了其与内存页的关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概念

AutoreleasePool(自动释放池)OC中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。在正常情况下,创建的变量会在超出其作用域的时候release,但是如果将变量加入AutoreleasePool,那么release延迟执行。
2251862-00621f50ede334d6.png

  • App启动后,苹果在主线程 RunLoop里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler(),从程序启动到加载完成,主线程对应的runloop会处于休眠状态,等待用户交互唤醒runloop
    • 第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush()创建自动释放池。优先级最高,保证创建释放池发生在其他所有回调之前
    • 第二个 Observer监视了两个事件:BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush()释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop()来释放自动释放池。优先级最低,保证其释放池子发生在其他所有回调之后
  • 用户的每一次交互都会启动一次runloop,用于处理用户的所有点击、触摸事件
  • runloop在监听到交互事件后,就会创建自动释放池,并将所有延迟释放的对象添加到自动释放池中
  • 主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被RunLoop创建好的 AutoreleasePool环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool了。
  • 在一次完整的runloop结束之前,会向自动释放池中所有对象发送release消息,然后销毁自动释放池

clang 分析

int main(int argc, char * argv[]) {
    @autoreleasepool {
    }
}

通过clang编译成cpp文件插看实现:
xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc main.m

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ {
          __AtAutoreleasePool __autoreleasepool; 
    }
}

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

通过代码可以看出autoreleasepool在底层实际是调用__AtAutoreleasePool,而__AtAutoreleasePool本质上是一个结构体,其内部包含构造函数__AtAutoreleasePool()析构函数~__AtAutoreleasePool(),在{}作用域结束后会自动调用析构函数,以便及时创建销毁

###汇编分析

struct LGTest {
    LGTest(){
        printf("1123 - %s",__func__);
    }
    ~LGTest(){
        printf("5667 - %s",__func__);
    }
};

int main(int argc, char * argv[]) {
         LGTest LGTest; 
}

在main函数中添加断点查看汇编
2251862-6b5a98298a8d11c5.jpg

可以看出跟clang编译后一样都是经过objc_autoreleasePoolPushobjc_autoreleasePoolPop

底层原理

在objc源码中是这样注释的

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.
每个指针都是要释放的对象,或者是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.
池令牌是指向该池的POOL_BOUNDARY的指针。弹出池后,将释放比哨点更热的每个对象。

- 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. 
线程本地存储指向热页面,该页面存储新自动释放的对象。

查看源码

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

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

通过代码可以看出pushpop操作都是基于AutoreleasePoolPage,根据其定义看出自动释放池是页结构,每页的大小为4096字节

//************宏定义************
#define PAGE_MIN_SIZE           PAGE_SIZE
#define PAGE_SIZE               I386_PGBYTES
#define I386_PGBYTES            4096            /* bytes per 80386 page */

//************类定义************
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
    friend struct thread_
<think>嗯,用户想了解Runtime和Runloop的底层原理,这两个都是iOS开发中的核心概念。首先,我需要回忆一下自己之前学过的内容,确保理解正确。Runtime主要是Objective-C的动态运行时环境,而Runloop则与事件处理循环相关。 先从Runtime开始。记得Runtime库是用C和汇编写的,它处理对象的创建、消息转发、方法交换等等。可能需要解释一下消息发送机制,比如objc_msgSend的作用,还有方法解析和消息转发的流程。另外,类与对象的结构,如isa指针、方法列表、属性列表这些也是关键点。动态特性如方法交换(method swizzling)和关联对象(associated objects)也应该提到,这些都是Runtime的实际应用场景。 然后是Runloop。Runloop的基本概念是事件处理循环,保持程序持续运行并处理各种事件。需要区分主线程和子线程的Runloop,主线程的默认开启,子线程的需要手动启动。Runloop的模式,比如DefaultMode和TrackingMode,以及不同模式下如何处理事件。还要讲Runloop的结构,Sources、Timers、Observers这些组成部分,以及它们如何协同工作。最后,Runloop在应用中的实际作用,比如界面刷新、定时器、事件响应等,都是必须涵盖的点。 用户可能对底层原理感兴趣,但作为开发者,他们可能更关心这些机制在实际开发中的应用,比如性能优化、解决卡顿问题,或者如何利用Runtime进行动态方法添加。需要确保解释清晰,结构合理,避免过于晦涩。同时,注意使用中文,按照系统指令正确排版数学公式,不过这里可能用不上太多数学内容,但如果有涉及的地方要记得用$符号包裹。 可能需要注意用户的知识水平,如果用户是初学者,可能需要更简洁的解释,避免过多技术细节。但问题中提到了“底层原理”,所以需要深入一些。但也要保持条理,分点说明。同时,检查是否有遗漏的重要概念,比如自动释放池与Runloop的关系,或者Runloop如何与GCD交互。 最后,确保回答结构清晰,分Runtime和Runloop两部分,每部分有概述、核心机制、实际应用等小节。使用例子帮助理解,比如消息转发的步骤,或者Runloop处理事件的流程。避免错误,比如混淆Runloop和线程的关系,或者错误描述方法解析的顺序。需要确认信息的准确性,必要时参考官方文档或可靠的资料。</think>好的,我将以iOS/macOS开发为背景,分步解释Runtime和Runloop的底层原理: ### 一、Runtime(运行时系统) #### 1. 核心概念 - Objective-C的动态特性基础 - C语言和汇编实现的库(源码可见于[objc4](https://opensource.apple.com/source/objc4/)) - 核心职责:实现面向对象特性和消息传递机制 #### 2. 核心机制 $$Class结构体 = isa指针 + superclass指针 + method\_list\_t + cache\_t + ...$$ **消息传递流程**: 1. 快速查找:通过`objc_msgSend`在方法缓存`cache_t`中查找 2. 慢速查找:遍历类的方法列表`method_list_t` 3. 动态解析:未找到时触发`+resolveInstanceMethod:` 4. 消息转发:经过`forwardingTargetForSelector:`和`methodSignatureForSelector:`两阶段 **关键数据结构**: - `isa指针`:实现对象与类的关联(arm64架构包含non-pointer isa优化) - `method_t`:包含SEL(方法选择器)、IMP(函数指针)、TypeEncoding(类型编码) #### 3. 实际应用 - 方法交换(Method Swizzling) - 动态添加方法(`class_addMethod`) - 关联对象(Associated Objects) - KVO实现(通过派生子类重写setter方法) --- ### 二、Runloop(运行循环) #### 1. 核心概念 - 事件处理循环:`do-while`循环保持线程活跃 - 与线程一一对应(主线程Runloop自动启动,子线程需手动启动) - 核心作用:管理事件/消息、进行线程调度 #### 2. 核心结构 ```plaintext CFRunLoop { currentMode = "UIInitializationRunLoopMode", modes = [ CFRunLoopMode { name = "kCFRunLoopDefaultMode", sources0 = [触摸事件、performSelector:], sources1 = [基于端口的系统事件], observers = [CA动画回调、AutoreleasePool清理], timers = [NSTimer、performSelector:withObject:afterDelay:] }, ... ] } ``` #### 3. 运行流程 $$进入循环 \rightarrow 处理输入源 \rightarrow 进入休眠 \rightarrow 等待唤醒 \rightarrow 处理事件$$ **详细步骤**: 1. 通知Observer:即将进入Loop(`kCFRunLoopEntry`) 2. 通知Observer:即将处理Timer(`kCFRunLoopBeforeTimers`) 3. 通知Observer:即将处理Source(`kCFRunLoopBeforeSources`) 4. 处理Blocks 5. 处理Source0(非基于Port的事件) 6. 如果有Source1待处理,立即跳转处理 7. 通知Observer:即将休眠(`kCFRunLoopBeforeWaiting`) 8. 进入休眠(通过mach_msg系统调用) 9. 被以下事件唤醒: - Port-Based事件 - Timer到时间 - Runloop超时 - 被手动唤醒 10. 通知Observer:结束休眠(`kCFRunLoopAfterWaiting`) 11. 处理唤醒时收到的事件 12. 回到步骤2循环执行 #### 4. 关键应用 - 界面刷新:CADisplayLink与VSync信号同步 - 自动释放池:Runloop休眠前执行释放操作 - 事件响应:手势识别、屏幕触摸 - GCD调度:`dispatch_main_queue`通过Runloop执行 --- ### 三、两者关联 1. **消息处理**:Runloop驱动Runtime的消息分发 2. **对象生命周期**:Runloop控制AutoreleasePool的释放 3. **线程保活**:Runloop通过`CFRunLoopRun`维持线程存活,配合Runtime进行消息处理 ### 四、调试技巧 1. Runtime调试: ```bash # 打印类方法列表 (lldb) p [MyClass _methodDescription] ``` 2. Runloop观察: ```objective-c CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopAllActivities); ``` 理解这两个机制,可以帮助开发者: - 优化滚动流畅度(通过Runloop模式控制) - 诊断消息转发导致的崩溃 - 实现高性能的异步渲染 - 合理管理线程资源
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值