前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS内存管理(二)-深入解析自动释放池

iOS内存管理(二)-深入解析自动释放池

原创
作者头像
用户6658895
修改2023-07-20 18:25:44
9880
修改2023-07-20 18:25:44
举报

AutoreleasePool是什么

自动释放池是Objective-C/Swift中的一种内存自动回收机制,AutoreleasePool可以将其中的变量进行release的时机延迟。简单来说,就是当创建一个对象,在正常情况下,变量会在超出其作用域的时立即release。如果将对象加入到了自动释放池中,这个对象并不会立即释放,会等到runloop休眠/超出@autoreleasepool作用域{}之后才会被释放。

AutoreleasePool释放时机

  • 一个是在runloop中隐式创建的autoreleasepool,每个接受autorelease消息的对象,都会在runloop结束时释放。对于每一个Runloop, 系统会隐式创建一个Autorelease pool,这样所有的release pool会构成一个象CallStack一样的一个栈式结构,在每一个Runloop结束时,当前栈顶的Autorelease pool会被销毁,这样这个pool里的每个Object会被release。
  • @autoreleasepool {}这种方法可以用在MRC和ARC中,它比NSAutoreleasePool更高效。这种情况在大括号结束释放。
理解主线程上的自动释放过程
  • 程序启动到加载完成后,主线程对应的 RunLoop会停下来等待用户交互
  • 用户的每一次交互都会启动一次运行循环,来处理用户所有的点击事件、触摸事件。
  • RunLoop检测到事件后,就会创建自动释放池;
  • 所有的延迟释放对象都会被添加到这个池子中;
  • 在一次完整的运行循环结束之前,会向池中所有对象发送 release消息,然后自动释放池被销毁;
理解主线程上的自动释放过程

也就是说,每一个线程都会维护自己的 Autoreleasepool栈,所以子线程虽然默认没有开启 RunLoop,但是依然存在 AutoreleasePool,在子线程退出的时候会去释放 autorelease对象。

AutoreleasePool的底层原理

双向链表结构
双向链表结构
Autoreleasepool结构图
Autoreleasepool结构图

大致流程

  • 当进入@autoreleasepool作用域时,objc_autoreleasePoolPush 方法被调用, runtime 会向当前的 AutoreleasePoolPage 中添加一个 nil 对象作为哨兵对象,并返回该哨兵对象的地址;
  • 对象调用autorelease方法,会被加入到对应的的AutoreleasePoolPage中去,next指针类似一个游标,不断变化,记录位置。如果加入的对象超出一页的大小,便会自动加一个新页。
  • 当离开@autoreleasepool作用域时,objc_autoreleasePoolPop(哨兵对象地址)方法被调用,其会从当前 page 的 next 指标的上一个元素开始查找, 直到最近一个哨兵对象, 依次向这个范围中的对象发送release消息;

因为哨兵对象的存在,自动释放池的嵌套也是满足的,不管是嵌套还是被嵌套的自动释放池,找自己对应的哨兵对象就行了。

一些名词

hotPage

指當前使用的 AutoreleasePoolPage 節點

coldPage

指已經被裝滿的鏈表節點

POOL_BOUNDARY

哨兵对象,值为nil,表示当前的起点

next

指向AutoreleasePoolPage指向栈顶空位的指针,每次加入新的元素都会往上移动。

一些操作

push操作
image
image
  • 如果当前 Page 存在且未满,走page->add(obj)将 autorelease 对象入栈,即添加到当前 Page 中
  • 如果当前 Page 存在但已满,走autoreleaseFullPage,创建一个新的 Page,并将 autorelease 对象添加进去
  • 如果当前 Page 不存在,即还没创建过 Page,创建第一个 Page,并将 autorelease 对象添加进去
pop操作
image
image

首先pop的入参token即为POOL_BOUNDARY对应在Page中的地址。当销毁自动释放池时,会从从自动释放池的中的最后一个入栈的autorelease对象开始,依次给它们发送一条release消息,直到遇到这个POOL_BOUNDARY

page->full()
  • begin的地址为:Page自己的地址+Page对象的大小56个字节;
  • end的地址为:Page自己的地址+4096个字节;
  • empty:判断Page是否为空的条件是next地址是不是等于begin;
  • full:判断Page是否已满的条件是next地址是不是等于end(栈顶)。
page->add(obj)

当page没有存满时,会调用此方法,内部的原理非常简单,就是一个压栈的操作,并将next指针指向这个对象的下一个位置,然后将该对象的位置返回。

autoreleaseFullPage(obj, page)

如果当前 Page 存在但已满,会调用此方法。其内部实现的主要方法就是一个do..while循环,主要实现了一下的逻辑

  • 由于page是链表结构,所以通过循环查找page->child
  • 一级级判断是否page->full()
  • 如果到最后一个page都是满的,那么就新new一个AutoreleasePoolPage
  • 如果有不满的,或者新创建的,调用setHotPage(page)将当前页设置为活跃
  • 最后将对象通过page->add压栈
autoreleaseNoPage(obj)

当没有page时,会走到此方法,其主要逻辑如下:

  • 先会判断是否有空的自动释放池存在,如果没有会通过setEmptyPoolPlaceholder()生成一个占位符,表示一个空的自动释放池
  • 创建第一个Page,设置它为hotPage
  • 将一个POOL_BOUNDARY添加进Page中,并返回POOL_BOUNDARY的下一个位置。
  • 插入第一个对象

autorelease的嵌套

image
image
image
image

Autorelease与NSThread、NSRunLoop的关系

RunLoop和NSThread的关系
  • RunLoop与线程是一一对应关系,每个线程(包括主线程)都有一个对应的 RunLoop对象;其对应关系保存在一个全局的Dictionary里;
  • 主线程的 RunLoop默认由系统自动创建并启动;而其他线程在创建时并没有 RunLoop,若该线程一直不主动获取,就一直不会有 RunLoop;
  • 苹果不提供直接创建 RunLoop的方法;所谓其他线程 Runloop的创建其实是发生在第一次获取的时候,系统判断当前线程没有 RunLoop就会自动创建;
  • 当前线程结束时,其对应的 Runloop也被销毁;
RunLoop和AutoreleasePool的关系

如上所述,主线程的 NSRunLoop在监测到事件响应开启每一次 eventloop之前,会自动创建一个 autorelease pool,并且会在 eventloop结束的时候执行 drain操作,释放其中的对象。

Thread和AutoreleasePool的关系

如上所述, 包括主线程在内的所有线程都维护有它自己的自动释放池的堆栈结构。新的自动释放池被创建的时候,它们会被添加到栈的顶部,而当池子销毁的时候,会从栈移除。对于当前线程来说,Autoreleased对象会被放到栈顶的自动释放池中。当一个线程线程停止,它会自动释放掉与其关联的所有自动释放池。

使用局部自动释放池块来降低内存峰值

许多程序创建的临时对象是自动释放的 ( autoreleased )。这些对象在程序运行到自动释放池块的结尾之前都会占据着程序的内存。在当前事件循环结束之前允许临时对象一直累积,在多数情况下不会导致过度的内存开销;但有时,创建大量的临时对象会导致内存占用大幅度升高,这时可以自己创建一个自动释放池块来及时处理下。在块的末尾,这些临时对象会被释放掉,内存占用通常也会因此而降下来。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • AutoreleasePool是什么
  • AutoreleasePool释放时机
  • AutoreleasePool的底层原理
  • 大致流程
  • 一些名词
  • 一些操作
  • autorelease的嵌套
  • Autorelease与NSThread、NSRunLoop的关系
  • 使用局部自动释放池块来降低内存峰值
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档