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…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...

python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...

mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...