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…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
