__weak详解

2023-11-03

object默认的修饰符是__strong,然而在开发中我们也经常使用__weak,用它来解决循环引用的问题:两个对象相互引用无法释放,造成内存泄露。用__weak来破坏一个强引用,来达到正常释放的目的。这种情况常见于block中,但是有没有想过为什么block会强引用block中的对象呢?答案也很简单,我们手动或者编译器帮我们对block进行了copy的操作,所以block会对其块中的对象就行强引用
!这个是一个小知识点,有的面试中可能会问到。
上面的都不是本篇文章的重点,重点是用__weak修饰的变量有两个特点:1.__weak修饰的对象释放后会自动致为nil;2.__weak修饰的对象注册到autoreleasepool中。今天我们来重点分析下__weak的这两个特点。
通过苹果开源代码,找到了与__weak相关变量函数:

第一点:__weak修饰的对象释放后会自动致为ni
//创建
id objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

//销毁
void objc_destroyWeak(id *location)
{
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}

template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating crashIfDeallocating>
static id 
storeWeak(id *location, objc_object *newObj)
{
    assert(haveOld  ||  haveNew);
    if (!haveNew) assert(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry:
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));

            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread 
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and 
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // Clean up old value, if any.
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }

    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}

__weak修饰的变量由weak表统一管理,这个和引用计数表一样,都是作为散列表被实现的。
例如:

__weak id weakObj = obj; 
转换=>
objc_initWeak(&weakObj, obj);
objc_destroyWeak(&weakObj);

objc_initWeak函数把obj地址作为键值,把weakObj的地址注册到weak表中。当销毁的时候objc_destroyWeak调用storeWeak传入nil.对于一个键值可以注册多个变量的地址,以obj地址作为键值的weak变量可能是多个,就会以ojb为键值进行检索,并做了以下操作:
(1)从weak表中获取废弃对象的地址为键值的记录
(2)将记录中的__weak修饰符修饰的变量的地址赋值为nil
(3)从weak表中删除该记录
(4)从引用计数表中删除废弃对象的地址为键值的记录
所以从上面的过程来看,__weak修饰的对象废弃之后,就会至为nil。由此也能看出来,这些步骤的执行必定会消耗一定的CPU资源,最好的办法是在需要使用__weak的时候才去使用!!!

第二点:__weak修饰的对象注册到autoreleasepool中
__weak id weakObj = obj; 
NSLog(@"%@", weakObj);
转换=>
objc_initWeak(&weakObj, obj);
let tmp = objc_loadWeakRetained(&weakObj);
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destroyWeak(&weakObj);

当有__weak修饰符的情况下多了objc_loadWeakRetained函数和objc_autorelease函数的调用。
objc_loadWeakRetained:取出__weak变量并retain
objc_autorelease:将对象注册到autoreleasepool中,因为对象为弱引用在访问的过程中随时都有可能释放,如果那它加入到autoreleasepool中,那么在autoreleasepool未释放之前对象可以放心使用了。

另外建议在使用__weak的时候同时使用__strong来强引用weak指针。如下:

__weak typeof(self) weakSlef = self;
self.block = ^{
    __strong typeof(weakSlef) strongSelf = weakSelf;

};

这样做的目的是为了在block执行的过程中self被释放,导致一些异常问题甚至是crash。

奇巧淫技

另外介绍一种解决block循环引用的方法:

__weak typeof(self) weakSlef = self;
self.block = ^{

    self.block = nil; 
    或
    self = nil;

};

任何一个至为nil都可以解除循环引用,但是有个缺点:必须执行block才能解除循环引用,可在一些会造成循环引用的但又不能用__weak的时候使用。

参考:
《Objective-C高级编程》

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

__weak详解 的相关文章

随机推荐

  • python爬取多个网页内容——招聘网站

    python爬虫思路 此次爬虫获取leipin网站上的招聘信息 liepin ningde 首先右击网页 检查 获取网页代码 点击网络 刷新网页 得到网页URL以及请求方法 首先在python环境中安装各种库 import requests
  • python如何解决js逆向混淆?

    JavaScript混淆是一种保护网站安全的技术 混淆可将代码进行多种变形和加密 使得 JavaScript 代码变得难以阅读和理解 逆向混淆是混淆中的一种方式 通过逆向混淆 混淆的代码更难被攻击者分析和了解混淆的含义 Python 是一种
  • morph 原理实现

    PE教学 超超超初心者向PE入门Part1 基本原理 2013 08 16 17 31 18 标签 mmd 教学 杂谈 分类 教学 应群里要求自己也写了一点比较浅显的PE入门教学 因为本人研究的并不深入 只是根据自己自学时的经验总结的比较基
  • keil 如何打开eeprom_如何在Keil中使RAM在带电复位时保持数据?

    最近关注的人比较多 非常感谢大家的认可 如何写点对大家有点借鉴作用的文章 也是我一直思考的问题 如果大家有什么想法 请留言或私信 谢谢 在 被Keil坑了一天 实在太意外了 用Keil的来瞅瞅看 中 提到了在使用Keil设置栈空间时可能存在
  • equals方法该怎么重写呢

    关于equals方法的重写 在编写代码的时候 我们总是会需要重写equals代码 因为equals代码没有重写的时候比较的是两个对象的内存地址 而两个代码的内存地址不同 答案肯定返回false 这并不是我们希望看到的 因此要重写equals
  • Java回调函数详解

    什么是回调函数 CallBack 在编写程序时 有时候会调用许多API中实现实现的函数 但某些方法需要我们传入一个方法 以便在需要的时候调用我们传入进去的函数 这个被传入的函数称为回调函数 Callback function 打个比方 有一
  • Visual Studio Code 配置java开发环境

    最近在学习算法 有时需要在自己的机器上调试一下代码 有些算法题目的题解是用java编的 因为这类代码只是单个的java文件 所以不想动用MyEclipse那样的重型工具 正好机器上有一个轻量级的VS Code 我就试着在上面搭了一个java
  • windows上的linux子系统(WSL)

    目录 一 介绍 二 原理 2 1 wsl组件 2 2 wsl运行过程 2 3 文件系统 2 4 禁忌 三 使用 3 1 安装 3 2 用户账户和权限 3 3 wsl管理 3 3 互操作 参考 推荐阅读 一 介绍 Windows Subsys
  • 时间对象new Date 以及中国标准时间与时间戳,标准时间之间的转换

  • C语言程序设计大作业——员工管理系统(代码超详细内含实验报告)

    写在前面 欢迎来到 发奋的小张 的博客 我是小张 一名普通的在校大学生 在学习之余 用博客来记录我学习过程中的点点滴滴 也希望我的博客能够更给同样热爱学习热爱技术的你们带来收获 希望大家多多关照 我们一起成长一起进步 也希望大家多多支持我鸭
  • 基于BP神经网络的回归预测

    基本概念 误差反向传播神经网络简称为BP Back Propagation 网络 它是一种具有三层或三层以上的多层神经网络 每一层都由若干个神经元组成 如图所示为一个BP神经网络的结构图 它的左 右各层之间各个神经元实现全连接 即左层的每一
  • LINUX下搭建JAVA的开发环境

    1 Linux下JDK的安装 至于下载JDK的二进制可执行文件 这里就不讲了 大家直接到官方网去下载就可以了 1 我下载下来的JDK安装文件名称为 jdk 1 5 0 14 linux i586 rpm bin 我把它保存在 tools目录
  • el search 条件更新

    from elasticsearch import Elasticsearch es Elasticsearch 192 168 55 90 9200 updateBody query bool must wildcard fileName
  • esxi管理端口_VMWare Esxi 基于NAT的管理端口转发

    上一篇关于WMWare Esxi的文章介绍了在多台虚拟机之间用软路由共享一个IP的情况 VMWare Esxi 海蜘蛛配置NAT共享IP上网 其中用了两个IP 一个用于虚拟机上网 一个用于Management Network 但是在只有一个
  • R语言 数据导出和导入 csv tsv xls xlsx

    R语言 Excel 导出为Excel的xls xlsx 导出数据 write table data2 file train1 xls sep t row names TRUE col names TRUE quote TRUE write
  • Promise的理解和使用

    一 Promise 是什么 1 理解 Promise是一门新的技术 ES6规范 Promise是JS中进行异步编程的新解决方案 旧方案是使用回调函数 Promise是一个构造函数 Promise对象用来封装一个异步操作并可以获取其成功 失败
  • 使用 Docker 部署 Prometheus + Grafana 监控平台

    使用 Docker 部署 Prometheus Grafana 监控平台 默认docker 已经安装好 我使用的是CentOS7 注意一定要开启服务器对应的端口 1 安装Prometheus docker run itd name prom
  • Windows虚拟机激活

    打开cmd命令提示符 首先连接kms服务器 slmgr skms skms netnr eu org 接着输入对应系统版本的产品密钥 xxxxx xxxxx xxxxx xxxxx xxxxx slmgr ipk xxxxx xxxxx x
  • 【DEBUG】MoviePy couldn't find the codec associated with the filename.Provide the 'codec' parameter

    问题描述 ValueError MoviePy couldn t find the codec associated with the filename Provide the codec parameter in write videof
  • __weak详解

    object默认的修饰符是 strong 然而在开发中我们也经常使用 weak 用它来解决循环引用的问题 两个对象相互引用无法释放 造成内存泄露 用 weak来破坏一个强引用 来达到正常释放的目的 这种情况常见于block中 但是有没有想过