iOS——strong和copy的底层实现

news2024/9/20 5:30:07

copy和strong的区别

有如下代码:

#import "Person.h"

@interface Person ()

@property (nonatomic, strong) NSString *strStrong;
@property (nonatomic, copy) NSString *strCopy;

@end

@implementation Person

- (void) go {
    NSMutableString *newStr = [NSMutableString stringWithString:@"newString"];
    self.strStrong = newStr;
    self.strCopy = newStr;
    [newStr setString:@"changString"];
    
    NSLog(@"newStr:%p %@", newStr, newStr);
    NSLog(@"strStrong:%p %@", self.strStrong, self.strStrong);
    NSLog(@"strCopy:%p %@", self.strCopy, self.strCopy);

}

@end

打印出的结果是:
请添加图片描述

可以看出来使用copy修饰的strCopy的值没有改变。
根据前面的学习我们知道:copy修饰的变量,对象地址不一致了,指针指向了一个新的内存区域(相当于深拷贝),导致新值(newString)修改时不会影响。
那么copy和strong这种区别的实现究竟是在哪里,下面我们一步一步解析:

属性使用点语法和_属性名的区别的原理

我们根据之前学的知识可知:一个属性使用self.的赋值是调用它的setter方法,而使用_属性名是直接赋值。下面我们使用clang分析两个语法的cpp:

//self.strStrong = newStr;
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)self, sel_registerName("setStrStrong:"), (NSString *)newStr);
//_strStrong = newStr;
(*(NSString **)((char *)self + OBJC_IVAR_$_Person$_strStrong)) = newStr;

第一段代码是使用这个函数指针向对象 self 发送消息 setStrStrong:,并传递 newStr 作为参数。
而第二段是self + OBJC_IVAR_...(属性偏移值) = strongStr的内存地址,然后在内存中进行替换。

属性的setter方法的底层

实际上strong和copy的区别在于它们setter方法的底层逻辑不同,我们先来看strStrong的setter方法:

static void _I_Person_setStrStrong_(Person * self, SEL _cmd, NSString *strStrong) { (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_strStrong)) = strStrong; }

这里是通过指针偏移后,将变量指针指向新的地址。

而strCopy的setter方法:

static void _I_Person_setStrCopy_(Person * self, SEL _cmd, NSString *strCopy) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _strCopy), (id)strCopy, 0, 1); }

这里的(id)strCopy就是我们要给strCopy赋的新值newStr。因为_I_Person_setStrCopy_(Person * self, SEL _cmd, NSString *strCopy)函数传入的参数中的NSString *strCopy就是newStr。

与strStrong不同的是,strCopy的setter方法中多了一个objc_setProperty,它们出现这样的区别的代码就在这里。

objc_setProperty

objc_setProperty 函数是在 Objective-C 中用于实现属性设置操作的一个函数。它负责处理属性的内存管理策略,如 copy、retain(对应于 strong)、nonatomic、atomic 等。

/*self: 调用该方法的对象。
_cmd: 方法的选择子(selector),即当前方法的名字。
offset: 属性在对象中的偏移量。
newValue: 要设置的新值。
atomic: 是否为原子操作。
shouldCopy: 是否需要复制新值。*/
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{
    //#define MUTABLE_COPY 2
    bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
    bool mutableCopy = (shouldCopy == MUTABLE_COPY);
    reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}

为什么copy修饰的变量set方法是调用objc_setProperty函数,而strong修饰却没有呢?因为:

void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, true, false, false);
}

void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, false, false, false);
}

strong 修饰符要求在设置属性时,对传入的对象增加一个引用计数,以确保对象在属性持有期间不会被释放。这个操作比较简单,可以直接通过 objc_storeStrong 函数来实现,因此不需要调用 objc_setProperty。编译器生成的 set 方法会直接使用 objc_storeStrong 来处理 strong 修饰的属性。

objc_setProperty_nonatomic_copy

接下来在objc4中搜索objc_setProperty_nonatomic_copy可以看到它的源码:

void objc_setProperty_atomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, true, true, false);
}

void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, false, true, false);
}

可以看到实际上它里面调用了一个reallySetProperty方法:

reallySetProperty

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    //如果offset为0,说明这是在设置对象的类(如isa指针),直接调用object_setClass来设置类,并返回。
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    //slot指向属性在对象中的存储位置。
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
      //检查 newValue 是否与当前值相同,如果相同则返回;如果不同,调用 objc_retain 来保留新值。
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks.get()[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}

根据这段代码可知,使用copy时,底层会调用copyWithZone;而使用mutableCopy时,底层会调用mutableCopyWithZone;两个都不是时,会增加引用计数,确保对象被正确持有。
这里根据我们上面的例子可知,我们的newStr是NSMutableString类型的。而且根据上面的各个方法的结果可知,copy为1,mutableCopy为0。因此会进入newValue = [newValue copyWithZone:nil]; 这一行,在这一行中,调用了newValue(NSMutableString)的copyWithZone,但是在NSMutableString中并没有找到copyWithZone的方法,向上找到了父类中的copyWithZone方法。
我们通过GUNstep找到copyWithZone和mutableCopyWithZone的具体实现:

- (id) copyWithZone: (NSZone*)zone
{
  /*
 * 默认实现不应简单地保留(retain)...字符串可能已经在初始化时设置了 freeWhenDone==NO 并且不拥有其字符数据... 
 * 因此创建它的代码在处理完原始字符串后可能会销毁该内存... 
 * 这样会导致副本指向无效的数据指针。 所以我们总是完全复制。
 */
  return [[NSStringClass allocWithZone: zone] initWithString: self];
}

- (id) mutableCopyWithZone: (NSZone*)zone
{
  return [[GSMutableStringClass allocWithZone: zone] initWithString: self];
}

我们再查看allocWithZone 的内部:
请添加图片描述

NSAllocateObject方法

我们发现在allocWithZone中调用了NSAllocateObject方法

inline id
NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone)
{
  id	new;

#ifdef OBJC_CAP_ARC
  if ((new = class_createInstance(aClass, extraBytes)) != nil)
    {
      AADD(aClass, new);
    }
#else
  int	size;

  NSCAssert((!class_isMetaClass(aClass)), @"Bad class for new object");
  size = class_getInstanceSize(aClass) + extraBytes + sizeof(struct obj_layout);
  //如果 zone 为 0,则使用默认的内存分配区域。
  if (zone == 0)
    {
      zone = NSDefaultMallocZone();
    }
  //计算分配对象所需的大小。
  new = NSZoneMalloc(zone, size);
  //分配内存。
  if (new != nil)
    {
      memset (new, 0, size);
      new = (id)&((obj)new)[1];
      // 将新的内存空间设置为aClass的类型
      object_setClass(new, aClass);
      AADD(aClass, new);
    }

  /* Don't bother doing this in a thread-safe way, because the cost of locking
   * will be a lot more than the cost of doing the same call in two threads.
   * The returned selector will persist and the runtime will ensure that both
   * calls return the same selector, so we don't need to bother doing it
   * ourselves.
   */
   //初始化内存,设置类,并进行附加操作。
  if (0 == cxx_construct)
    {
      cxx_construct = sel_registerName(".cxx_construct");
      cxx_destruct = sel_registerName(".cxx_destruct");
    }
  callCXXConstructors(aClass, new);
#endif

  return new;
}

到这就可以得出结论了,NSMutablString的Copy协议是创建了新的内存空间,进行了内容拷贝,通俗可以理解为进行了深拷贝。

依次再使用别的拷贝模式的深浅拷贝关系:

请添加图片描述

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

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

相关文章

【前端】 flex布局详解

Flex布局开启,在编写之前,我们要先搞清楚一个问题,就是你要让谁开启flex布局?我们要开启flex布局的最终目的一定是为了让某几个元素进行规范化布局,那如果你单独写在某个元素身上,那它的兄弟元素也不知道自…

【FPGA XDMA AXI Bridge 模式】PCIe:BARs 和 AXI:BARs 含义解析

XDMA IP核两种模式 Xilinx的 DMA/Bridge Subsystem for PCI Express IP核中,支持普通的XDMA模式,但是这种模式只允许主机端发起PCIe 读写请求,FPGA内部无法主动发起读写请求,也即FPGA无法主动读写HOST的内存。 而该IP核的另一种…

超分论文ESPCN解读

论文地址:Real-Time Single Image and Video Super-Resolution Using an Efficient Sub-Pixel Convolutional Neural Network 相关知识点总结: 许多SR技术的一个关键假设是,大部分高频数据是冗余的,因此可以从低频分量中准确重建…

IIC时序(通俗易懂版,嘎嘎简单)

介绍 简述:IIC总线就是一个两根线的规则(半双工),规定通信双方如何传送数据,至于传送数据,无非就是主机给从机发送数据,或者从机给主机发送数据,其中加了一点发过去的数据有没有回应…

佰朔资本:大宗交易是什么?出现大宗交易意味着什么?

大宗生意,又叫作大宗生意,是指抵达规则的最低限额的证券单笔生意申报,生意两边经过协议达到共同并经生意所招认成交的证券生意。 出现大宗生意,说明该股票的整体体现弱于商场均匀体现,且主力正在减仓或许出货。大宗生…

charles使用ssl证书抓包https请求失败解决方案

前提 手机必须有root权限,并且是使用Magisk(面具)进行root; ssl证书安装 安卓7.0以下的手机,ssl证书是直接安装到了‘系统证书’里,可以直接抓取https请求,但是目前的手机大部分都是7.0以上的&#xff1…

第17章.RCC-STM32时钟配置

目录 0. 《STM32单片机自学教程》专栏 17.1 STM32时钟树 17.1.1 时钟源 17.1.2 锁相环PLL 17.1.3 系统时钟 17.1.3.1 系统时钟SYSCLK 17.1.3.2 AHB/APB总线时钟 17.1.3.3 其他时钟 17.1.3.4 MCO 时钟输出 17.2 系统时钟库函数 17.3 系统时钟配置练习 …

Python基础语法(1)上

常量和表达式 我们可以把 Python 当成一个计算器,来进行一些算术运算。 print(1 2 - 3) print(1 2 * 3) print(1 2 / 3) 这里我们可能会有疑问,为什么不是1.6666666666666667呢? 其实在编程中,一般没有“四舍五入”这样的规则…

C++入 门——“多态”

一、多态 多态是面向对象的一个重要特性,它允许程序在运行时通过传入不同对象而呈现出不同的运行结果,比如同样的采访,询问老师的年龄和学生的年龄最后得到的结果是不一样的,这就呈现出一种多态。 多态分为两种:静态多…

鸿蒙OpenHarmony【轻量系统芯片移植】内核移植

移植芯片架构 芯片架构的移植是内核移植的基础,在OpenHarmony中芯片架构移植是可选过程,如果当前OpenHarmony已经支持对应芯片架构则不需要移植操作,在“liteos_m/arch”目录下可看到当前已经支持的架构,如表1: 表1 …

2024年全国大学生软件测试大赛赛项安排(一)

✨博客主页: https://blog.csdn.net/m0_63815035?typeblog 💗《博客内容》:.NET、Java.测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识 📢博客专栏: https://blog.csdn.net/m0_63815035/cat…

怎么选择靠谱AI论文生成工具?看完我的试用都会明白!

2024年上半年开始AI论文写作工具开始火了,层出不穷!作为一个经常需要写论文的懒人,我非常好奇这些AI工具的实际效果到底怎么样?为了测试不同工具的实力,我对他们都进行了试用,发现了一些意想不到的结果....…

【楚怡杯】职业院校技能大赛 “云计算应用” 赛项样题一

某企业根据自身业务需求,实施数字化转型,规划和建设数字化平台,平台聚焦“DevOps开发运维一体化”和“数据驱动产品开发”,拟采用开源OpenStack搭建企业内部私有云平台,开源Kubernetes搭建云原生服务平台,选…

【F172】基于Springboot+vue实现的智能菜谱系统

作者主页:Java码库 主营内容:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 项目描述 近些年,随着中国经济发展,人民的…

消防装备仓库管理系统|实现消防装备全流程跟踪

智慧消防装备仓库管理系统(智物资DW-S302)是一套成熟系统,依托互3D技术、云计算、大数据、RFID技术、数据库技术、AI、视频分析技术对RFID智能仓库进行统一管理、分析的信息化、智能化、规范化的系统。 1、支持实时物资仓库货位二维和三维孪生…

第七篇——数学应用:华罗庚化繁为简的神来之笔

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么? 四、总结五、升华 一、背景介绍 数学的意义,以及它对于生活的指导边界,是那么的重…

Oracle CloudWorld 2024:多云时代的AI与数据库融合革命

在今年的 Oracle CloudWorld 大会上,甲骨文董事会主席兼首席技术官Larry Ellison 深入探讨了AI、云计算与数据库融合的未来。 在这一技术盛宴中,Ellison的演讲不仅展现了云计算的新趋势,还为企业数字化转型提供了前瞻性的技术路线。 我们来回…

kettle 数据库迁移 使用分页原理实现 数据库mysql

使用 kettle 9.0 先修改配置文件: C:\Users\xx\.kettle 新增如下配置,解决mysql 空字符串 自动转 null bug KETTLE_EMPTY_STRING_DIFFERS_FROM_NULLY git地址: GitHub - 2292011451/kettle_tool 第一步: 先把要迁移的表进行读取,循环查询每个表的最大数量以及页数,追加到…

(一)模式识别——基于SVM的道路分割实验(附资源)

写在前面:本报告所有代码公开在附带资源中,无法下载代码资源的伙伴私信留下邮箱,小编24小时内回复 一、实验目的 1、实验目标 学习掌握SVM(Support Vector Machine)算法思想,利用MATLAB的特定工具箱和库函…

MFC工控项目实例之十四模拟量信号名称从文件读写

承接专栏《MFC工控项目实例之十三从文件读写板卡信号名称》 在BoardTest.cpp文件中添加代码 int m_CountGetCurSel_AD[16];//索引号 UINT m_CountComboID_AD[16]//控件ID号{IDC_COMBO33,IDC_COMBO34,IDC_COMBO35,IDC_COMBO36,IDC_COMBO37,IDC_COMBO38,IDC_COMBO39,IDC_COMBO40…