iOS开发:__weak __strong解决Block嵌套

news2024/12/28 5:42:12

Block使用会存在循环引用的问题,多个Block嵌套使用的情况更复杂,还会出现对象nil的问题。

为什么会循环引用?

现在iOS开发都是在ARC引用计数管理模式下的,参考另一篇文章《Block底层原理》,我们知道Block访问外部变量对临时变量是值拷贝(深拷贝),对__block修饰的变量是指针拷贝(浅拷贝),这两种情况都不在这次的讨论范围内,因为变量的作用域有限,会在block执行完毕后释放掉;如果Block访问的是对象类型(比如:Dog类),Block访问外部变量对__strong修饰的对象(NSObject默认是__strong修饰),是执行的对象自身的copy函数(浅拷贝)。

  1. 问:Block访问实例对象会造成循环引用吗?
- (void)testBlock {
    void(^block1)(void) = ^{
        self.name = @"张三";
    };
    block1();
}

答:不会。通过打印self.name,证明name已经修改为@"张三",再次证明block捕获对象是浅拷贝,引用计数+1,当testBlock函数调用完毕,block1就被释放掉了,对self的引用计数-1,所以不会存在循环引用。

  1. 造成循环引用的情况
    循环引用其实也就是说,对象会被一直持有,对象无法释放,dealloc就不调用了,导致内存溢出,这才是我们担心的问题所在。造成这一问题所在的根源就是对象被持有没有释放,引用计数一直大于0。
    比如:A持有B,B也持有A,谁也无法释放内存;这个是最常见的。
- (void)testBlock {
    self.block1 = ^{
        self.name = @"张三";
    };
    block1();
}
//self持有了block1,block1又访问了self,

还有A持有B,B持有C,C持有D…D持有A,最后肯定形成了闭环

- (void)testBlock {
	
    self.block1 = ^{
       void(^block2)(void) = ^{
           self.name = @"张三";
       }; 
	   block2();
    };
    block1();
}
//self持有了block1,block1持有了block2访问了self,block2访问了self。

我们要做的就是把强制引用形成的链条打断。
请添加图片描述

__weak原理

解决循环引用,我们最多的就是用__weak来修饰变量,从而打破强引用的链条。那么__weak为什么可以做到呢?

Runtime维护了一个 weak_table_t hash(哈希) 表,用于存储指向某个对象的所有weak指针。Key是所指对象的地址,Valueweak指针的地址(这个地址的值是所指对象的指针地址)数组。

 __weak typeof(self) weakSelf = self;
  • 当我们初始化一个weak变量时,runtime会调用 NSObject.mm 中的objc_initWeak函数
id objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}
  • *location :__weak weakSelf 的指针地址
  • newObj :所引用的对象,即例子中的self
  • objc_initWeak函数会调用storeWeak函数,该函数主要是更新指针指向,创建对应的弱引用表
template <HaveOld haveOld, HaveNew haveNew,
          enum 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;

 retry:
    // 如果weak指针变量 之前弱引用过一个对象 讲这个对象对应的SideTable从SideTables中取出来,赋值给oldTable
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        // 如果weak指针变量 之前没有弱引用过一个obj,则oldTable = nil
        oldTable = nil;
    }
    
    //  如果weak指针变量要weak引用一个新的obj,则将该obj对应的SideTable取出,赋值给newTable
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        // 如果weak指针变量不需要引用一个新obj,则newTable = nil
        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()) //  如果cls还没有初始化,先初始化,再尝试设置weak
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(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; // 重新获取一遍newObj,这时的newObj应该已经初始化过了
        }
    }
    
    
    
    // 如果weak指针变量之前弱引用过别的对象oldObj,则调用weak_unregister_no_lock,在oldObj的weak_entry_t中移除该weak_ptr地址

    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }
    
    // 如果weak指针变量需要弱引用新的对象newObj
    if (haveNew) {
        
        // 1 调用weak_register_no_lock方法,weak_register_no_lock会将weak指针变量的地址 记录到newObj对应的weak_entry_t中
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
                                  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);

        // 2 更新newObj的isa的weakly_referenced bit标志位
        if (!newObj->isTaggedPointerOrNil()) {
            newObj->setWeaklyReferenced_nolock();
        }
        
        // 3 *location 赋值,也就是将weak指针变量直接指向了newObj   这里并没有将newObj的引用计数+1 , 所以weak引用不会让newObj引用计数+1
        *location = (id)newObj;  // 也就是例子中 将weakSelf 指向self
    }
    else {
        // No new value. The storage is not changed.
    }
    
    // 解锁,其他线程可以访问oldTable, newTable了
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

.
    // 返回newObj,此时的newObj与刚传入时相比,weakly-referenced bit位置1
    callSetWeaklyReferenced((id)newObj);

    return (id)newObj;
}
  • 当对象的引用计数为0时,底层会调用_objc_rootDealloc方法对对象进行释放
  • _objc_rootDealloc -> object_dispose -> objc_destructInstance -> clearDeallocating -> clearDeallocating_slow -> weak_clear_no_lock
void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;
    
    // 找到referent在weak_table中对应的weak_entry_t
     weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); 
        if (entry == nil) {
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }

    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    
    // 找出weak引用referent的weak指针地址数组以及数组长度
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i]; // 取出每个weak 指针变量的地址
        if (referrer) {
            if (*referrer == referent) { // 如果weak 指针变量确实weak引用了referent,则将weak指针变量设置为nil,这也就是为什么weak 指针会自动设置为nil的原因
                *referrer = nil;
            }
            else if (*referrer) { // 如果所存储的weak 指针变量没有weak 引用referent,这可能是由于runtime代码的逻辑错误引起的,报错
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    
    weak_entry_remove(weak_table, entry); // 由于referent要被释放了,因此referent的weak_entry_t也要移除出weak_table
}

强弱共舞(weak-strong-dance)

我们在Block里面嵌套一个延迟函数,通过输出打印,对象内存已经被置空。

- (void)checkBlock {
    Dog *dog = [Dog new];
    __weak Dog *dog_weak = dog;
    dog.eatBlock = ^(NSString * _Nonnull name) {
        dog_weak.name = name;
        dispatch_async(dispatch_get_main_queue(), ^{
            dog_weak.name = @"牛肉";
            NSLog(@"wuwuFQ:%@", dog_weak);//输出 wuwuFQ:(null)
        });
        
    };
    dog.eatBlock(@"鸡肉");
    NSLog(@"wuwuFQ:%@", dog.name);//输出 wuwuFQ:鸡肉
}

dispatch_async是主线程异步执行,可能会在checkBlock函数执行完后才执行,对象dog就会在之前被释放掉,导致弱引用dog_weak指针为nil
我们用__strong来解决这个问题:

- (void)checkBlock {
    Dog *dog = [Dog new];
    __weak Dog *dog_weak = dog;
    dog.eatBlock = ^(NSString * _Nonnull name) {
        dog_weak.name = name;
        __strong Dog *dog_strong = dog_weak;
        dispatch_async(dispatch_get_main_queue(), ^{
            dog_strong.name = @"牛肉";
            NSLog(@"wuwuFQ:%@", dog_strong);//输出 wuwuFQ:<Dog: 0x6000027ddce0>
        });
        
    };
    dog.eatBlock(@"鸡肉");
    NSLog(@"wuwuFQ:%@", dog.name);//输出 wuwuFQ:鸡肉
}

我们再来理解下这段代码,dispatch_async在这里是主线程异步执行任务,会添加到 main_queue() 的末端,我在调用checkBlock函数之后又尝试调用了其他同步函数,dispatch_async总是最后一个被调用,也就是说肯定在checkBlock函数之后了,也就导致了对象dog被释放掉。
我们用 __strong 强引用了dog_weak指针,可以让dispatch_async内部捕获到变量dog_strongdispatch_async执行完毕,变量dog_strong释放,也不会产生循环引用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/602880.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

文心一言 VS 讯飞星火 VS chatgpt (29)-- 算法导论5.2 1题

一、在 HIRE-ASSISTANT 中&#xff0c;假设应聘者以随机顺序出现&#xff0c;你正好雇用一次的概率是多少&#xff1f;正好雇用 n 次的概率是多少? 文心一言&#xff1a; 讯飞星火&#xff1a; 华为盘古&#xff1a; 在HIRE-ASSISTANT中&#xff0c;假设应聘者以随机顺序出现…

仙境传说RO:从零开始编译服务端开发环境搭建安装Pandas-rAthena

仙境传说RO&#xff1a;从零开始编译服务端开发环境搭建安装Pandas-rAthena 大家好&#xff0c;我是艾西今天跟大家分享下仙境传说RO 开发脚本环境搭建。 Pandas是RO的模拟器实际上是开源的一个叫做rAthenna的一个开源项目 准备工具&#xff1a; Git 2.25.0 Setup建议放入D盘…

【消息队列】| 队列的优势介绍及应用场景

目录 &#x1f981; 前言&#x1f981; 那么MQ的优势在哪里&#xff1f;&#x1f981; 应用场景&#x1f981; 最后 &#x1f981; 前言 消息队列&#xff1a;MQ全称Message Queue&#xff08;消息队列&#xff09;&#xff0c;是在消息的传输过程中保存消息的容器。多用于系统…

javaweb课程设计——商城项目

前言&#xff1a; &#x1f44f;作者简介&#xff1a;我是笑霸final&#xff0c;一名热爱技术的在校学生。 &#x1f4dd;个人主页&#xff1a;个人主页1 || 笑霸final的主页2 &#x1f4d5;系列专栏&#xff1a;项目专栏 &#x1f4e7;如果文章知识点有错误的地方&#xff0c;…

2.2 动态范围的常用计算方法

1. 动态范围的常用计算方法 动态范围(Dynamic Range)指的是输入数据中数值的范围&#xff0c;计算动态范围是为了确定量化时使用的比特位数(还是抽象&#x1f602;)。个人理解:考虑到输入数据可能存在数据分布不均&#xff0c;即有些数据偏离过大。而过大的偏离值&#xff0c;会…

Ansys Zemax | NSC 非序列矢高图用户分析

本文介绍如何使用 NSC 矢高图用户分析功能在非序列模式下测量和显示对象的矢高。了解此功能的基础知识&#xff0c;包括如何设置复杂 CAD 零件的文件以获取特定面的矢高值。&#xff08;联系我们获取文章附件&#xff09; 介绍 OptocStudio 的序列模式具有表面矢高分析功能&…

硬件系统工程师宝典(28)-----关于LDO,应该知道的事

各位同学大家好&#xff0c;欢迎继续做客电子工程学习圈&#xff0c;今天我们继续来讲这本书&#xff0c;硬件系统工程师宝典。上篇我们说到BJT配合MOSFET控制电源开关的四种电路以及MOSFET的均流电路。今天我们来讲讲LDO的应用分析。 LDO的结构 LDO&#xff08;Low Dropout R…

Linux内核源码的配置和编译

目录 配置交叉编译工具链 读README 配置内核源码支持当前的硬件平台 驱动配置 内核编译 编译&#xff1a; 问题&#xff1a; 解决问题的方法&#xff1a; 测试内核 配置交叉编译工具链 打开内核源码顶层目录的Makefile&#xff0c; hqUbuntu:~/fs6818_uboot/kernel-3.4.39$ vi …

IP地址、子网划分

目录 一、IP地址1.IP地址表示2.分类IP地址3.无分类编址 CIDR4.特殊IP地址 二、子网划分1.子网、子网掩码、子网划分VLSM2.网络地址、广播地址3.示例1&#xff1a;等分为两个子网3.1 划分前&#xff1a;3.2 划分后&#xff1a; 4.示例2&#xff1a;等分为四个子网3.1 划分前&…

五种经典IO模型详解

目录 同步和异步同步阻塞IO模型基本概念应用场景优缺点 同步非阻塞IO模型基本概念应用场景优缺点 IO多路复用模型信号驱动IO模型回顾复习1.信号2.产生信号的条件3.可重入函数4.为什么中断处理函数不能直接调用不可重入函数5.如何写出可重入的函数 基本概念应用场景优缺点 异步I…

【操作系统】 1、计算机系统概述

1.1 操作系统的基本概念 从操作系统的角度上来划分计算机体系结构&#xff1a; 这里注意一点&#xff1a; 编译器属于应用程序。 操作系统&#xff1a;是指控制和管理计算机系统的 硬件 和软件 资源&#xff0c;合理的组织、调度计算机的工作与资源分配&#xff0c;进而为用…

对象数组练习案例

定义一个长度为3的数组&#xff0c;数组存储1~3名学生对象作为初始数据&#xff0c;学生对象的学号&#xff0c;姓名各不相同。 * 学生的属性&#xff1a;学号、姓名、年龄 * 要求1&#xff1a;再次添加一个学生对象&#xff0c;并在添加的时候进行学号的唯一性判断 * 要求2&am…

Nautilus Chain:我们将支持EIP6969

在今年 5 月初&#xff0c;以太坊核心开发者、Slingshot 的 CTO zkCole 提出了一个通用的协议标准 EIP-6969 &#xff0c;其旨在实现合约保护收入&#xff08;在以太坊 L2 上引入 / 标准化 CSR &#xff09;&#xff0c;该提案可以看作是之前 EIP-1559的改进版&#xff0c;并在…

Record类浅喽一眼~

Record类的一点小概念嗷。 一. 基本使用 java19 的新特性: 我们先构造一个student的Record类. 默认构造几个属性. public record Student(Integer id,String name, String email,Integer age) {} 然后简单搞一点例子 public static void main(String[] args) { St…

2023年上半年系统分析师上午真题及答案解析

1.信息系统的构成包括( )。 A.计算机硬件、计算机软件、网络和通信设备、系统分析人员、系统设计人员、系统开发人员 B.计算机硬件、计算机软件、系统分析人员、系统设计人员、系统开发人员 C.计算机硬件、计算机软件、系统设计人员、系统开发人员、信息用户 D.计算机硬件…

【C++】类和对象——友元函数和友元类的概念、初始化列表、explicit关键字、static成员

文章目录 1.友元函数和友元类的概念1.1友元函数1.2友元类 2.构造函数知识补充2.1初始化列表2.2explicit关键字 3.static成员3.1static成员概念3.2static成员特性 1.友元函数和友元类的概念 在C中&#xff0c;友元函数和友元类是指允许非成员函数或非成员类访问某个类中的私有成…

LeetCode面向运气之Javascript—第13题-罗马数字转整数-99.21%

LeetCode第13题-罗马数字转整数 题目要求 给定一个罗马数字&#xff0c;将其转换成整数。 罗马数字 罗马数字包含以下七种字符: I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M 分别代表1&#xff0c;5&#xff0c;10&#xff0c;50&#xf…

一些常用的分布式组件实现技巧

广播 可用redis的pubsub机制来支持集群内的广播。 基于redis的分布式锁 加锁 使用setnx命令&#xff1a; SET lock_key random_value NX PX 5000 其中&#xff1a; random_value 是客户端生成的唯一的字符串&#xff0c;用于在删除时唯一标识client身份。 NX 代表只在键不…

【开发实用】还在用BeanUtils?不如用MapStruct

文章目录 1. 什么是MapStruct2. 为什么使用MapStruct3. 如何使用MapStruct 1. 什么是MapStruct MapStruct是一个Java注解处理器&#xff0c;它可以简化Java bean之间的转换。它使用基于生成器的方法创建类型安全的映射代码&#xff0c;这些代码在编译时生成&#xff0c;并且比…

2023夏季黑客松大赛,Moonbeam邀请你来BUIDL

由Parity和OneBlock联合举办的「2023 夏季波卡黑客松大赛」正在火热开启中。自报名开启之日&#xff0c;便获得了来自海内外对波卡生态的高度专注和对Web3开发的热情。 本次黑客松聚焦智能合约、开发工具、社交网络等大赛命题&#xff0c;邀请了行业领军人、技术大咖、投资人等…