Objective-C的对象复制与拷贝选项
对象复制与拷贝
文章目录
- 对象复制与拷贝
- copy与mutablecopy
- copy与mutablecopy的简介
- 示例:
- 不可变对象的复制
- 可变对象的复制
- NSCopying和NSMutableCopying协议
- 深复刻和浅复刻
- 浅拷贝(Shallow Copy):
- 深拷贝(Deep Copy):
- setter方法的复制选项
copy与mutablecopy
copy与mutablecopy的简介
copy和mutablecopy都是NSObeject所提供用来复制对象的方法,但它们有着不同的行为,主要区别如下:
copy方法:
copy方法用于创建一个不可变的副本,无论原始对象是可变的还是不可变的。- 对于不可变对象,
copy方法仅仅是增加了引用计数,返回的仍然是原始对象本身。 - 对于可变对象,
copy方法会创建一个新的不可变对象,内容与原始对象相同。
mutableCopy 方法:
mutableCopy方法用于创建一个可变的副本,无论原始对象是可变的还是不可变的。- 对于不可变对象,
mutableCopy方法会创建一个新的可变对象,内容与原始对象相同。 - 对于可变对象,
mutableCopy方法会创建一个新的可变对象,内容与原始对象相同。
总结一下,主要区别在于:
copy返回的是不可变副本,而mutableCopy返回的是可变副本。copy对不可变对象和可变对象的行为不同,而mutableCopy则对所有对象的行为都一致。
示例:
不可变对象的复制
#import <Foundation/Foundation.h>int main(int argc, const char * argv[]) {@autoreleasepool {NSString* str1 = @"deer";NSString* str2 = str1;NSString* strcopy = [str1 copy];NSString* strmutablecopy = [str1 mutableCopy];NSLog(@"%p",str1);NSLog(@"%p",str2);NSLog(@"%p",strcopy);NSLog(@"%p",strmutablecopy);}return 0;
}

不难看出来,str1,str2, strcopy ,指向的都为同一内存区域,为浅拷贝,由于mutablecopy生成了新的NSMutableString所以系统为其重新开辟一块空间,进行了深拷贝。
可变对象的复制
#import <Foundation/Foundation.h>int main(int argc, const char * argv[]) {@autoreleasepool {NSMutableString *mstr = [NSMutableString stringWithString:@"hello"];NSString *mstrcopy1 = [mstr copy];NSMutableString *mstrcopy2 = [mstr copy];NSMutableString *mstrmutablecopy = [mstr mutableCopy];[mstr appendString:@",aa"];//[mstrcopy2 appendString:@",bb"];[mstrmutablecopy appendString:@",cc"];NSLog(@"%p",mstr);NSLog(@"%p",mstrcopy1);NSLog(@"%p",mstrcopy2);NSLog(@"%p",mstrmutablecopy);}return 0;
}
对于注释的那一句代码,由于mstrcopy是通过copy方法得来,类型为NSString是不可变的,所以后面无法进行追加。可以发现通过copy得到的字符串指向的是同一片地址,但是复制得到的地址相对于原来的mstr都是不同的,所以我们可以知道对于可变对象进行copy和mutablecopy都是进行深拷贝。
NSCopying和NSMutableCopying协议
虽然NSObject虽然都给出了copy和mutablecopy的方法,但是不一定在任何时候都能够使用,为了确保一个对象可以调用copy方法复制得到自身的不可变副本,我们需要做如下事情
- 让该类实现NSCopying协议
- 让该类实现copyWithZone方法
对于mutablecopy也是同理
- 实现NSMutableCopying协议
- 让该类实现mutableCopyWithZone方法
#import <Foundation/Foundation.h>@interface MyObject : NSObject <NSCopying>@property (nonatomic, strong) NSMutableString *name;
@property (nonatomic, assign) int age;@end@implementation MyObject- (id)copyWithZone:(NSZone *)zone {MyObject *copy = [[[self class] allocWithZone:zone] init];// 递归复制子对象copy.name = self.name;copy.age = self.age;return copy;
}-(NSString*)description {return [NSString stringWithFormat:@"name = %@, age = %d", self.name, self.age];
}
@endint main(int argc, const char * argv[]) {@autoreleasepool {// 创建原始对象MyObject *org = [MyObject new];org.name = [NSMutableString stringWithString:@"aa"];org.age = 10;NSLog(@"%@",org);MyObject *copy1 = [org copy];NSLog(@"%@",copy1);copy1.name = [NSMutableString stringWithString:@"bb"];copy1.age = 20;NSLog(@"%@",org);NSLog(@"%@",copy1);}return 0;
}
从以上程序我们看出来,我们对org进行一次副本复制,对得到的copy的成员变量进行赋值,对org不会产生影响。

#import <Foundation/Foundation.h>@interface MyObject : NSObject <NSCopying>@property (nonatomic, strong) NSMutableString *name;
@property (nonatomic, assign) int age;@end@implementation MyObject- (id)copyWithZone:(NSZone *)zone {MyObject *copy = [[[self class] allocWithZone:zone] init];// 递归复制子对象copy.name = self.name;copy.age = self.age;return copy;
}-(NSString*)description {return [NSString stringWithFormat:@"name = %@, age = %d", self.name, self.age];
}
@endint main(int argc, const char * argv[]) {@autoreleasepool {// 创建原始对象MyObject *org = [MyObject new];org.name = [NSMutableString stringWithString:@"aa"];org.age = 10;NSLog(@"%@",org);MyObject *copy1 = [org copy];NSLog(@"%@",copy1);}return 0;
}
深复刻和浅复刻
深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是在编程中经常遇到的两个概念,它们描述了在复制对象时复制的内容的不同程度。
浅拷贝(Shallow Copy):
浅拷贝是指将一个对象复制到一个新的对象中,即将对内存指针的复制,并增加其引用计数。如果对象包含其他对象的引用,浅拷贝将不会复制这些引用的对象,而是将其引用复制到新对象中。因此,新对象和原对象中的引用指向相同的对象。如果其中一个对象修改了共享对象,另一个对象也会受到影响。当使用A指针改变内容时,B指针指向的内容也会跟着改变

深拷贝(Deep Copy):
深拷贝是指将一个对象递归复制到一个新的对象中,开辟一片新的空间,并且复制所有引用的对象,直至没有任何共用的部分。这意味着,新对象中的每个对象都是原始对象的副本,而不是共享的。因此,如果其中一个对象修改了它指针所指向的对象,另一个对象不会受到影响。

对于上图来说,分配了新的内存,改变A指针指向的值,不会影响到指针B指向值的内容。
通常情况下,浅拷贝的效率比深拷贝高,因为它不需要递归地复制所有引用的对象。但是,在需要保持对象之间的独立性时,深拷贝是必需的。
我们再来探究一下我们刚刚看到的例子,
#import <Foundation/Foundation.h>@interface MyObject : NSObject <NSCopying>@property (nonatomic, strong) NSMutableString *name;
@property (nonatomic, assign) int age;@end@implementation MyObject- (id)copyWithZone:(NSZone *)zone {MyObject *copy = [[[self class] allocWithZone:zone] init];// 递归复制子对象copy.name = self.name;copy.age = self.age;return copy;
}-(NSString*)description {return [NSString stringWithFormat:@"name = %@, age = %d", self.name, self.age];
}
@endint main(int argc, const char * argv[]) {@autoreleasepool {// 创建原始对象MyObject *org = [MyObject new];org.name = [NSMutableString stringWithString:@"aa"];org.age = 10;NSLog(@"%@",org);MyObject *copy1 = [org copy];NSLog(@"%@",copy1);[copy1.name replaceCharactersInRange:NSMakeRange(0,2) withString:@"bb"];copy1.age = 20;NSLog(@"%@",copy1);NSLog(@"%@",org);}return 0;
}
我们的程序输出以下结果。

似乎很奇怪,我们通过repalce的函数去修改字符串,使得org和copy的字符串内容都发生了改变,想要解决这个问题我们可以画一个图来辅助我们进行理解。

对于即将被复制的org来说,变量name之中存储的是字符串的地址指针,而不是字符串本身,如果进行copy.name = self.name; copy.age = self.age;,那么只是将字符串的地址赋给copy之中的name属性,实际上两者指针都指向一个相同的NSMutableString类型字符串。

对于这种复制模式我们就称之为浅复制,在内存中复制一个对象,但是原来的和复制得到的内容指向一个相同的对象,也就是说两个对象都存在依然存在的部分
那如果要实现深复制呢?前面说过深复制不仅会对对象本身进行复制,而且还会"递归"复制每一个指针的属性,直到两者没有共同的部分。我们将copyWithZone的代码进行一些修改就能实现深复制的原理。
- (id)copyWithZone:(NSZone *)zone {MyObject *copy = [[[self class] allocWithZone:zone] init];// 递归复制子对象copy.name = [self.name mutableCopy];copy.age = self.age;return copy;
}
我们将原对象的属性值复制了一份可变的副本,再将新的可变副本的内容赋值给新对象的name属性,与原先的对象再无共用的部分,实现了深复制
一般来说,实现深复制的难度很大,对于包含大量指针属性的对象来说,更或者对象中的变量中又嵌套了一层指针的话,那会更加复杂
setter方法的复制选项
当属性声明为 copy 时,在 setter 方法中会执行深复制操作。这意味着在设置属性值时,会创建一个属性值的副本,而不是直接引用传入的对象。
考虑以下代码:
@property (nonatomic, copy) NSString *name;
对应的 setter 方法会类似于这样:
- (void)setName:(NSString *)name {if (_name != name) {_name = [name copy];}
}
在这个 setter 方法中,首先进行了对象的比较,确保新值和旧值不是同一个对象。然后,使用 copy 方法来创建新值的副本,并将副本赋值给属性 _name。这样做可以确保属性值的安全性,防止外部对象对属性值的意外修改。
copy 操作实际上是执行了浅复制,它只会复制对象的指针,而不会复制对象本身。这意味着它会创建一个新的指针,指向相同的内存地址,而不是创建一个完全独立的副本。
对于不可变对象,由于其内容是不可变的,因此可以安全地使用 copy 属性。对于可变对象而言,使用copy可能会导致意外的行为。因为修改一个对象会影响到所有指向该对象的指针。这就是为什么使用 copy 属性时需要小心,特别是。另外,如果属性值是可变对象,那么每次设置属性值时都会创建一个新的不可变副本,这样会导致性能开销增加。
需要注意的是,copy 属性通常适用于容器类对象和其他可复制的对象,但对于自定义类对象,需要确保其实现了 NSCopying 协议。否则,编译器会报错。
相关文章:
Objective-C的对象复制与拷贝选项
对象复制与拷贝 文章目录 对象复制与拷贝copy与mutablecopycopy与mutablecopy的简介示例:不可变对象的复制可变对象的复制 NSCopying和NSMutableCopying协议深复刻和浅复刻浅拷贝(Shallow Copy):深拷贝(Deep Copy&…...
HTML5 中的离线缓存机制,即应用缓存(Application Cache 或 AppCache)已被废弃并正在被逐步移除
HTML5 中的离线缓存机制,即应用缓存(Application Cache 或 AppCache)已被废弃并正在被逐步移除。这是因为应用缓存存在一些设计上的缺陷和限制,导致它在实际应用中经常出现问题。 取而代之的是一种更强大、更灵活的技术——Servi…...
vue3+ant design实现表格数据导出Excel
提示:实现表格数据导出Excel 文章目录 前言 一、安装ant design? 二、引用ant design 1.搭建框架 2.获取表格数据 三、封装导出表格的代码 四、导出 1.获取导出地址 2.在下载导出事件中添加导出代码 五、全部代码 前言 今天终于有时间来更新文章了,最近公司项目比较紧…...
VBA_NZ系列工具NZ06:VBA创建PDF文件说明
我的教程一共九套及VBA汉英手册一部,分为初级、中级、高级三大部分。是对VBA的系统讲解,从简单的入门,到数据库,到字典,到高级的网抓及类的应用。大家在学习的过程中可能会存在困惑,这么多知识点该如何组织…...
Git === Git概述 Git安装
第1章 Git概述 Git是一个免费的、开源的分布式版本控制系统,可以快速高效地处理从小型到大型的各种项目。 Git易于学习,占地面积小,性能极快。 它具有廉价的本地库,方便的暂存区域和多个工作流分支等特性。其性能优于Subversion…...
Linux diff命令(比较两个文件或目录的内容差异)
文章目录 Linux diff 命令详解教程基本用法比较文件输出解释 递归比较(-r)示例代码 控制输出格式统一格式(-u)上下文格式(-c) 高级选项忽略所有空白差异(-w)仅报告文件是否不同 Linu…...
从传统到现代:水表的远程抄表革命
1.引言:技术驱动的转型 在过去的几十年里,我们的生活方式被科技的快速发展深深影响,其中就包括了公用设施的管理方式。传统水表的远程抄表系统就是这样一个例子,它将老旧的手动抄表模式转变为高效、精确的自动化系统。 2.传统水…...
视频怎么打水印?6个软件教你快速进行视频水印制作
视频怎么打水印?6个软件教你快速进行视频水印制作 添加水印是保护视频版权、提升视频专业性的重要手段之一。以下是六款软件,它们能够帮助你快速进行视频水印制作,让你的视频更具个性和专业性: 1.迅捷视频剪辑软件:…...
面试 Java 基础八股文十问十答第二十八期
面试 Java 基础八股文十问十答第二十八期 作者:程序员小白条,个人博客 相信看了本文后,对你的面试是有一定帮助的!关注专栏后就能收到持续更新! ⭐点赞⭐收藏⭐不迷路!⭐ 1)动态代理是什么&am…...
Excel-VBA报错01-解决方法
【已删除的部件:部件/xl/vbaProject.bin。(Visual Basic for Applications(VBA))】 1.问题复现: Win10 ;64位 ;Office Excel 2016 打开带有宏的Excel文件,报错:【已删除的部件:部件/xl/vbaProject.bin。…...
php利用阿里云短信SDK实现短信发送功能
当使用PHP结合阿里云短信服务SDK来实现短信验证码登录时,你需要遵循以下步骤: 1. 注册阿里云账号并开通短信服务 首先,你需要有一个阿里云账号,并在阿里云控制台中开通短信服务(Dysmsapi)。 2. 获取Acce…...
承装(修、试)电力工程施工许可证四级资质可以承接多大的项目?
承装(修、试)电力工程施工许可证四级资质可以承接多大的项目? 承装(修、试)电力工程施工许可证四级资质可以承接的项目规模及范围是一个复杂且细致的问题,涉及电力工程施工的多个方面。根据四级资质的相关规…...
影像图层调整图像显示效果的色彩参数汇总
在Cesium的ImageryProvider中,以下图层对象支持调整图像显示效果的色彩参数: - ArcGisMapServerImageryProvider - BingMapsImageryProvider - GoogleEarthEnterpriseImageryProvider(如果服务支持) - TileMapServiceImager…...
EasyHPC - PyTorch入门教程【笔记】
内容来源:超算习堂 (easyhpc.net) 文章目录 01 Tensors环境要求1.1 Tensors1.1.1 直接创建tensor1.1.2 在现有tensor中创建tensor1.1.3 从NumPy中创建tensor 1.2 基本运算1.2.1 使用运算符1.2.2 调用方法 1.3 CUDA Tensors 02 Autograd2.1 Tensor2.2 Gradient 03 Ne…...
Node.js里面 Path 模块的介绍和使用
Node.js path 模块提供了一些用于处理文件路径的小工具,我们可以通过以下方式引入该模块: var path require("path") 方法描述 序号方法 & 描述1path.normalize(p) 规范化路径,注意.. 和 .。2path.join([path1][, path2][,…...
【Linux】Centos7配置JDK
1.启动虚拟机、Xshell、Xftp 2.在Xshell中新建一个会话,用于连接到虚拟机中 3.因为虚拟机里自带有JDK,所以需要先卸载自带的JDK 3.1.查询已安装的 jdk 列表 rpm -qa | grep jdk3.2.将查询到的全部删除 yum -y remove XXX(上面查询到的 j…...
pytorch中统计一个数在tensor中出现了几次
pytorch中统计一个数在tensor中出现了几次 在PyTorch中,可以使用torch.eq()函数配合torch.sum()来统计某个数值在Tensor中出现的次数。torch.eq()函数会返回一个新的Tensor,其中对于每个元素来说,如果和指定的数值相等,则该位置为…...
a-auto-complete 请求后端数据做模糊查询,解决下拉框选择选不上,不回显的问题
a-auto-complete 请求后端数据做模糊查询,解决下拉框选择选不上,不回显的问题 记录一个a-auto-complete卡bug卡了两天,找不到哪里的问题下拉框选择选不上,不回显,最后终于解决了。 我还对下拉框显示的内容做了小调整。…...
Leetcode—724. 寻找数组的中心下标【简单】
2024每日刷题(129) Leetcode—724. 寻找数组的中心下标 实现代码 class Solution { public:int pivotIndex(vector<int>& nums) {int sum accumulate(nums.begin(), nums.end(), 0);int prefix 0;for(int i 0; i < nums.size(); i) {i…...
C语言 | Leetcode C语言题解之第72题编辑距离
题目: 题解: static inline int Min(const int a, const int b, const int c) {int min (a < b) ? a : b;return (min < c) ? min : c; }int minDistance(char * word1, char * word2){int m strlen(word1), n strlen(word2);int dp[m 1][n…...
铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...
使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...
STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
