iOS内存管理之autorelease

2023-10-27

当你需要延迟调用release方法的时候会使用autorelease。如:

- (NSString *)fullName {
    NSString *string = [[[NSString alloc] initWithFormat:@"%@ %@",
                                          self.firstName, self.lastName] autorelease];
    return string;
}

在上面代码中,通过alloc生成并持有对象,根据iOS内存管理规则,在失去对象的引用之前,我们必须要放弃该对象的所有权。如果使用release,那么对象在返回之前就会失去所有权,导致返回一个无用的对象。使用autorelease,会保证调用放在使用该返回值之前,该对象不会释放,也就是说,autorelease可以保证对象在跨越“方法调用边界”后存活。

这时你可能会有一个疑问,使用autorelease的对象到底在什么时候释放呢?接下来,我们将会围绕这个问题逐步进行研究。

AutoreleasePool是如何工作的?

Coco框架会帮我们维护一个autorelease pool用来将对象临时保存在内存中,以便稍后释放。

iOS应用程序是在event loop(事件循环)中运行的。当应用程序加载时,程序就会进入事件循环并等待用户的交互事件。当一个点击事件发生时,Cocoa框架会检测到该事件,然后创建事件对象,并生成一个autorelease pool对象。整个流程如下表所示:
在这里插入图片描述
当事件处理方法完成执行时,控制权会返回给Cocoa框架。Cocoa框架会销毁autorelease Pool,它会向池子中的每一个对象都发送一条release消息。

autorelease实现

通过查看Objc源码,来确认autorlease的实现:

- (id)autorelease {
    return ((id)self)->rootAutorelease();
}

inline id objc_object::rootAutorelease()
{
    assert(!UseGC);

    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}

id objc_object::rootAutorelease2()
{
    assert(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}

通过源码可以发现,当调用对象的autorelease方法时,最终调用的是AutoreleasePoolPage的autorelease方法。

接下来我们看一下AutoreleasePoolPage的实现,

AutoreleasePoolPage是使用C++实现的一个类,截取源码中的主要部分,其实现如下:

class AutoreleasePoolPage 
{
#define POOL_SENTINEL nil
    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
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MAX_SIZE;  // 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;
    
    public:
    static inline id autorelease(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {
            return autoreleaseFullPage(obj, page);
        } else {
            return autoreleaseNoPage(obj);
        }
    }


    static inline void *push() 
    {
        id *dest;
        if (DebugPoolAllocation) {
            // Each autorelease pool starts on a new pool page.
            dest = autoreleaseNewPage(POOL_SENTINEL);
        } else {
            dest = autoreleaseFast(POOL_SENTINEL);
        }
        assert(*dest == POOL_SENTINEL);
        return dest;
    }

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

        page = pageForPointer(token);
        stop = (id *)token;
        if (DebugPoolAllocation  &&  *stop != POOL_SENTINEL) {
            // This check is not valid with DebugPoolAllocation off
            // after an autorelease with a pool page but no pool in place.
            _objc_fatal("invalid or prematurely-freed autorelease pool %p; ", 
                        token);
        }

        if (PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);

        // memory: delete empty children
        if (DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top) 
            // when debugging missing autorelease pools
            page->kill();
            setHotPage(nil);
        } 
        else if (page->child) {
            // hysteresis: keep one empty child if page is more than half full
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }
    
    id *add(id obj)
    {
        assert(!full());
        unprotect();
        id *ret = next;
        *next++ = obj;
        protect();
        return ret;
    }

    void releaseAll() 
    {
        releaseUntil(begin());
    }
}

通过分析上面的源码,我们可以了解到对象调用autorelease方法的过程如下:

  • 获取正在使用的AutoreleasePoolPage对象。
  • 获取当前AutoreleasePoolPage对象的next指针,将autorelease对象地址复制给next指针,next指针向上移动一个位置。
  • 若一个AutoreleasePoolPage空间被占满时,会新建一个AutoreleasePoolPage对象,链接链表,后来的autorelease对象会加入到新的page中。

AutoreleasePool的生命周期

在ARC下,我们使用@autoreleasepool{}来使用AutoreleasePool:

@autoreleasepool {
}

使用clang(clang -rewrite-objc)命令将其转化成C++代码,关键代码如下:

extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

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

因此我们可以得出结论:Autorelease Pool的生命周期是由 void * objc_autoreleasePoolPush(void)和void objc_autoreleasePoolPop(void *)这两个方法实现的。

通过查看Objc源码,确定这两个方法的实现:

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

void objc_autoreleasePoolPop(void *ctxt)
{
    if (UseGC) return;
    AutoreleasePoolPage::pop(ctxt);
}

通过源码,我们可以确定,void * objc_autoreleasePoolPush(void)和 void objc_autoreleasePoolPop(void *)这两个方法是对AutoreleasePoolPage::push()和AutoreleasePoolPage::pop(ctxt)方法的封装。

通过分析AutoreleasePoolPage源码中push()和pop()方法的实现,我们可以知道AutoreleasePool对象的生命周期

push()过程:

  • 获取当前正在使用的AutoreleasePoolPage对象。若当前对象为空,则生成一个新的page对象。若当前page存在且已满,则创建一个新的page,通过链表的形式将两个page链接起来。
  • 向当前page对象中next位置追加一个哨兵对象(POOL_SENTINEL)并返回该地址,这个地址作为pop操作的入参。

注意:每调用一次push()操作都会创建一个新的autoreleasepool对象,即往AutoreleasePoolPage中插入一个POOL_SENTINEL,并且返回插入的 POOL_SENTINEL 的内存地址。

pop()过程:

  • 根据传入的哨兵对象地址找到其所在的page。
  • 在当前page中,将晚于哨兵对象插入的所有autorelease对象都发送一次release消息,并将next指针移回正确位置。
  • 从最新加入的对象一直向前清理,可以跨越若干个page,直到哨兵对象所在的位置。
    在这里插入图片描述

autorelease对象在什么时候释放呢?

通过前面的分析,我们可以知道,autorelease对象是在AutoReleasePool的生命周期结束时释放的。在没有显示生成AutoReleasePool对象的情况下,autorelease对象在当前runloop迭代结束时释放的,Cocoa框架会为每个runloop添加AutoreleasePool的push和pop操作。

ARC下autorelease优化策略

先看一下,ARC下,调用NSMutableArray类的array方法时,编译器为我们做了哪些操作

+ (id)array {
    id obj = objc_msgSend(NSMutableArray, @selector(alloc));
    objc_msgSend(obj, @selector(init));
    return objc_autoreleaseReturnValue(obj);
}

id obj = objc_retainAutoreleasedReturnValue([NSMutableArray array]);

objc_autoreleaseReturnValue函数会检查使用该函数的方法或函数调用方的执行命令列表,如果方法或函数的调用方在调用了方法或函数后紧接着调用objc_retainAutoreleasedReturnValue()函数,那么就不将返回的对象注册到autoreleasepool中,而是直接传递到方法或函数的调用方。
在这里插入图片描述

拾遗

使用容器的block版本的枚举器时,内部会自动添加一个AutoreleasePool

[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    // 这里被一个局部@autoreleasepool包围着
}];

在普通for循环和for in循环中没有。for循环和for in循环中遍历产生大量autorelease变量时,就需要手加局部 AutoreleasePool啦。

扫一扫关注iOS学习社区公号,了解更多iOS知识以及提升工作技能
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

iOS内存管理之autorelease 的相关文章

  • 关于在 Objective-C 中迭代 2 个数组的简单问题

    我正在 Objective c 中迭代 NSArray for id object in array1 我现在有另一个 array2 我需要使用当前 array1 的相同索引进行访问 我应该使用另一个 for 语句吗 thanks 您有多种
  • 将 NSString 分离成 N​​SArray,但允许用引号对单词进行分组

    我有一个搜索字符串 人们可以使用引号将短语组合在一起 并将其与单个关键字混合 例如 像这样的字符串 Something amazing rooster 我想把它分成一个 NSArray 这样它就有Something amazing 不带引号
  • 将小箭头添加到 iPhone TableView 单元格中单元格的右侧

    这应该很简单 我有一个带有 TableView 的 iPhone 应用程序 如何将经典的小箭头添加到每个单元格的右侧 只需设置相应的附件类型的财产UITableViewCell cell accessoryType UITableViewC
  • 如何去除 UIImageView 遮罩后的透明区域?

    在我的一个 iOS 应用程序中 我尝试使用以下命令剪切图像的一部分CGImageMask 我已成功使用以下代码屏蔽图像 UIImage maskImage UIImage referenceImage withMask UIImage ma
  • iOS UIView子类,将透明文本绘制到背景

    我想将文本绘制到 UIView 上的子类上 以便文本从形状中切出 并且视图后面的背景显示出来 就像在 OSX Mavericks 徽标中找到的那样here http www n3rdabl3 co uk wp content uploads
  • UITableView 最后一个单元格被 UITabBarController 隐藏

    我面临一个奇怪的问题 我有一个UITableView在最初的UIViewController of the UITabBarController 起初一切都很好 但是当我更改选项卡并返回到第一个选项卡时 最后一个单元格UITableView
  • 错误域=NSURLErrorDomain代码=-1017“该操作无法

    我刚刚开始 ios 开发 我正在尝试与我的 api 交换数据 当我执行 POST 请求时 一切正常 但当我尝试执行 GET 请求时 出现以下错误 错误域 NSURLErrorDomain代码 1017 该操作无法 完全的 NSURLErro
  • 在横向模式下向 UIScrollView 添加子视图

    我有一个以横向模式启动的视图控制器 其中有一个 UIScrollView 我尝试创建子视图并将它们添加到 UIScrollView 但视图的框架大小都是纵向大小 这是我的代码 void viewDidLoad super viewDidLo
  • 如果您使用 CocoaPods,您的 .gitignore 中会包含什么内容?

    我从事 iOS 开发已经几个月了 刚刚了解到有前途的可可豆荚 http cocoapods org 用于依赖管理的库 我在个人项目上尝试过 添加了依赖项Kiwi https github com allending Kiwi到我的 Podf
  • 如何将图像放入此 UIPickerView 中?

    我不知道如何创建一个在文本一侧带有图像的自定义 UIPickerView 我一直在寻找一种方法 我刚刚发现了这个 UIView pickerView UIPickerView pickerView viewForRow NSInteger
  • 使用 NSString 进行 UTF8 解码

    我是 Objective C 新手 尝试使用以下示例将格式错误的 UTF8 编码 NSString 转换为格式良好的字符串苹果文档 http developer apple com library mac documentation Coc
  • locationOfTouch 和 numberOfTouches

    你好 我有这个识别器 设置为 2 次触摸 但它只返回一个 而不是两个 CGPoint void gestureLoad UIGestureRecognizer recognizer recognizer UITapGestureRecogn
  • 每 24 小时触发一次方法

    我正在尝试每天在给定时间触发一个方法 我尝试了一些方法 但我无法真正使其发挥作用 任何意见 将不胜感激 此外 如果无论应用程序是否打开它都会触发 那就更理想了 这可能吗 UI本地通知 http developer apple com lib
  • iOS:生成pdf时绘制文本时如何设置字体?

    我在ios应用程序中使用drawpdf函数生成pdf 同时调用nsobject类中的drawtext函数 它根据我指定的框架和字符串清楚地绘制文本 我的代码是 void drawText NSString textToDraw inFram
  • Cocoa 应用程序菜单栏不可点击

    我正在我的可可应用程序中构建一个菜单栏 其中包含以下代码 implementation我的自定义应用程序CustomApplication void setUpMenuBar CustomApplication sharedApplicat
  • UIView animateWithDuration:delay: 工作很奇怪

    我在使用 iPhone 动画块时遇到了一个奇怪的问题 这段代码 UIView animateWithDuration 2 delay 0 options 0 animations void controller setBackgroundC
  • iPhone:UIApplication WillResignActiveNotification 从未被调用

    我正在视图控制器中播放视频 当用户按下硬件主页按钮并且当前正在播放视频时 应用程序崩溃并显示EXC BAD ACCESS在模拟器中 我读到我应该使用applicationWillResignActive停止视频播放的消息应该可以解决崩溃问题
  • 如何在 UICollectionView 中将行居中?

    我有一个UICollectionView与随机细胞 有什么方法可以让我将行居中吗 默认情况下它是这样的 x x x x x x x x x x x x x x 这是所需的布局 x x x x x x x x x x x x 我必须做这样的事
  • 处理 UICollectionView 中的点击手势

    由于我无法使用任何框架来创建相册 因此我尝试使用 Collection View 创建自己的相册 但我一开始就陷入困境 我的目标是将网络服务中的所有图像显示到我的集合视图中 因为所有图像都已显示 下一步是当有人点击任何单元格时 我可以在新视
  • 为什么 Objective-C 允许在方法定义末尾使用分号? [复制]

    这个问题在这里已经有答案了 可能的重复 Objective C 实现文件中方法名后面的分号 https stackoverflow com questions 5678360 semicolon after the method name

随机推荐

  • C语言求s=a+aa+aaa+aaaa+....

    问题描述 求s a aa aaa aaaa 其中a是一个数字 n表示a的位数 a和n由键盘输入 代码描述 方法1 include
  • 网络安全(黑客)自学路线

    谈起黑客 可能各位都会想到 盗号 其实不尽然 黑客是一群喜爱研究技术的群体 在黑客圈中 一般分为三大圈 娱乐圈 技术圈 职业圈 娱乐圈 主要是初中生和高中生较多 玩网恋 人气 空间 建站收徒玩赚钱 技术高的也是有的 只是很少见 技术圈 这个
  • Python第九、十课

    枭 Python第九 十课 今天讲解了Python的 函数参数类型 变量作用域 函数参数类型 默认值参数 规则 在定义带有默认值参数的函数时 默认值参数必须全部出现在位置参数右侧 且任何一个默认参数右边都不能出现位置参数 默认值参数只在第一
  • 【云原生之kubernetes实战】kubernetes集群的HPA弹性伸缩

    云原生之kubernetes实战 kubernetes集群的HPA弹性伸缩 一 HAP介绍 1 HPA简介 2 HPA的实现原理 3 HPA自动伸缩示意图 4 HPA中影响 Pod 数量的因素 5 HPA改善服务的方式 二 检查本地k8s环
  • python学习主要应该学些什么

    近几年 python 正在成为最受欢迎的编程语言之一 无论是软件开发还是机器学习 python 都能够处理得游刃有余 人们喜欢使用 python 语言是因为它非常容易 相比于 c 语言和 java 等语言 它开发效率更高 它有着丰富的第三方
  • centos7安装或卸载RabbitMQ 3.8.4+Erlang 23.0详细步骤

    参考博客 Linux CentOS 7 下RabbitMQ的安装与配置 风萧萧1999的博客 CSDN博客 安装前准备 1 检查RabbitMQ Erlang版本 https www rabbitmq com which erlang ht
  • 【解决】使用mybatis解决sql报错: Parameter index out of range (1 > number of parameters, which is 0)

    当我们运用ssm框架做项目 并且要通过mybatis来做模糊查询时 sql语句中where条件语句 like后面 要用 like 条件名 而不是平常做where 时的like 条件名
  • InceptionNext实战:使用InceptionNext实现图像分类任务(一)

    文章目录 摘要 安装包 安装timm 安装 grad cam 数据增强Cutout和Mixup EMA 项目结构 计算mean和std 生成数据集 摘要 论文翻译 https blog csdn net m0 47867638 articl
  • wavefronts

    https www g truc net post 0597 html https michaldrobot com 2014 04 01 gcn execution patterns in full screen passes https
  • 力扣刷题记录(三)

    1084 销售分析III Create table If Not Exists Product product id int product name varchar 10 unit price int Create table If No
  • 机器学习——numpy逻辑回归(手写数字识别)

    二分类 识别1 7 import numpy as np import struct import matplotlib pyplot as plt import os from PIL import Image from sklearn
  • timm库安装

    按理来说 conda config append channels conda forge conda install timm 更新timm版本 pip install timm 0 5 4 python3 8 pytorch 1 11
  • HTTP与TCP的区别和联系

    https blog csdn net u013485792 article details 52100533 相信不少初学手机联网开发的朋友都想知道Http与Socket连接究竟有什么区别 希望通过自己的浅显理解能对初学者有所帮助 一 基
  • Kaggle入门——Titanic+随机森林(调参)+逻辑回归

    本博客记录一下自己的Kaggle入门题目 Titanic 只弄了一天 特征工程做得比较草率 结果只有0 76 不过主要是为了体验一下Kaggle竞赛的流程 以及熟悉一下Kaggle的使用 目录 1 题目相关 2 特征工程 3 随机森林 调参
  • 2021-09-08 PuTTY & Xftp-5使用密钥连接服务器

    PuTTY Xftp 5使用密钥连接服务器 所需材料 Xftp 5操作 PuTTY操作 所需材料 Xftp 5 PuTTY 密钥 服务器IP 用户名和密码 Xftp 5操作 新建会话 输入会话名称 主机IP 选择SFTP协议 方法选择Pub
  • 用python输出0到100所有能被3整除的数字_python: 输出 1~100 之间不能被 7 整除的数,每行输出 10 个数字,要求应用字符串格式化方法美化输出格式。...

    输出 1 100 之间不能被 7 整除的数 j 0 for i in range 1 101 遍历1 100取值 定义为变量 i if i 7 0 找出不能被 7 整除的数 print 3d format i end Format格式化输出
  • 伏秒积和安秒积

    伏秒平衡原则 伏秒平衡原则 在稳态工作的开关电源中电感两端的正伏秒值等于负伏秒值 安秒平衡原则 在稳态工作的开关电源中电容两端的正安秒值等于负安秒值 电容两端的电压不能突变 当电容足够大时 可认为其电压不变 电感中的电流不能突变 当电感足够
  • ./configure –prefix 命令用法

    在Linux上编译安装软件时 经常遇到 configure prefix usr这个命令 configure prefix 是什么意思呢 下面简单介绍一下 configure prefix 的用法 源码的安装一般由有这三个步骤 配置 con
  • 使用valgrind检查内存问题并且输出报告

    valgrind内存泄漏分析 是在linux中检查内存泄漏的工具 当程序编写完之后我一般都会使用它来检查一次内存问题 基本上能杜绝服务器的内存泄漏问题 当然是面对C C 这样的语言的 使用方式就是将程序编译好 然后通过valgrind来启动
  • iOS内存管理之autorelease

    当你需要延迟调用release方法的时候会使用autorelease 如 NSString fullName NSString string NSString alloc initWithFormat self firstName self