当前位置: 首页 > news >正文

OC语言学习——对象复制

一、copy与mutableCopy方法

        

copy方法用于复制对象的副本,复制下来的该副本是不可修改的,哪怕是调用NSMutableString的copy方法也不可修改。

        而mutableCopy方法复制下来的副本是可修改的,即使被复制的对象原本是不可修改的。例如调用mutableCopy方法复制NSString的,返回的是一个NSMutableString对象。

        因为以上方法返回的是原对象的副本,所以对复制的副本进行修改时,原对象通常不受影响。

 以下用代码演示copy和mutableCopy方法的功能:

#import <Foundation/Foundation.h>int main(int argc, const char * argv[]) {@autoreleasepool {//copy与mutableCopyNSMutableString *book = [NSMutableString stringWithString: @"疯狂iOS讲义"];//定义一个book字符串NSMutableString *bookCopy = [book mutableCopy];//用mutableCopy给book复制一个副本[bookCopy replaceCharactersInRange: NSMakeRange(2, 3) withString: @"Android"];//复制后的bookCopy副本是可以修改的,这里做个修改,对原字符串的值也没有影响NSLog(@"book的值为:%@",book);//原值NSLog(@"bookCopy的值为:%@",bookCopy);//副本修改后的值,没有问题NSString *str = @"fkit";//定义一个str字符串NSMutableString *strCopy = [str mutableCopy];//用mutableCopy给str复制一个副本[strCopy appendString:@".org"];//向可变字符串后面追加字符串NSLog(@"%@",strCopy);NSMutableString *bookCopy2 = [book copy];//用copy方法复制一个book的副本(这个副本不可变)[bookCopy2 appendString:@"aa"];//这里会报错,因为copy创建的副本不可变,修改了就崩了}return 0;
}

二、NSCopying和NSmutableCopying协议

当我们想将自定义类用上一节的两个方法复制副本时,我们可能会直接创建完对象后用”类名* 对象2 = [对象1 copy];“这样的格式来复制副本,但实际上直接这样复制是不对的,会报错说找不到copyWithZone:方法,mutableCopy也是一样。因此我们可以看出,自定义类是不能直接调用这两个方法来复制自身的。

        这是为什么呢?是因为当程序调用copy/mutableCopy方法复制时,程序底层需要调用copyWithZone:/mutableCopyWithZone:方法来完成复制的工作,并返回这两个方法的值。因此为了保证可以复制,需要在自定义类的接口部分声明NSCopying/NSMutableCopying协议,然后再类的实现部分增加copyWithZone:/mutableCopyWithZone:方法,因此,对自定义对象的复制应该如下所示:

接下来,我们以一个自定义的Person类为例,支持copy和mutablecopy:

#import <Foundation/Foundation.h>@interface Person : NSObject <NSCopying, NSMutableCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end@implementation Person// 实现 copy(返回不可变副本)
- (id)copyWithZone:(NSZone *)zone {Person *copy = [[[self class] allocWithZone:zone] init];copy.name = self.name;  // NSString 默认 copycopy.age = self.age;return copy;
}// 实现 mutableCopy(返回可变副本)
- (id)mutableCopyWithZone:(NSZone *)zone {Person *copy = [[[self class] allocWithZone:zone] init];copy.name = [self.name mutableCopy];  // 注意生成可变副本copy.age = self.age;return copy;
}@end
int main(int argc, const char * argv[]) {@autoreleasepool {Person *p1 = [[Person alloc] init];p1.name = @"Tom";p1.age = 18;Person *p2 = [p1 copy];         // 调用 copyWithZonePerson *p3 = [p1 mutableCopy];  // 调用 mutableCopyWithZoneNSLog(@"原始:%@ %ld", p1.name, p1.age);NSLog(@"copy:%@ %ld", p2.name, p2.age);NSLog(@"mutableCopy:%@ %ld", p3.name, p3.age);}return 0;
}

三、深复制与浅复制

深复制和浅复制是面向对象编程中非常重要的概念。

浅复制:仅复制对象的指针地址,多个变量共享同一个对象。

深复制: 不仅复制指针,还会复制整个对象内容,使得原对象和副本完全独立。

举个例子:

NSMutableString *str1 = [NSMutableString stringWithString:@"Hello"];
NSMutableString *str2 = str1;            // 浅复制(赋值)
// 修改 str1,str2 也变了
[str1 appendString:@" World"];

浅复制与深复制的区别:

浅复制:

#import "FKDog.h"@implementation FKDog@synthesize name;
@synthesize age;
- (id) copyWithZone:(NSZone *)zone {FKDog *dog = [[[self class] allocWithZone:zone] init];dog.name = self.name;dog.age = self.age;return dog;
}@end

深复制:

#import "FKDog.h"@implementation FKDog@synthesize name;
@synthesize age;- (id) copyWithZone:(NSZone *)zone {NSLog(@"--执行copyWithZone--");FKDog *dog = [[[self class] allocWithZone:zone] init];dog.name = [self.name mutableCopy];//在这个地方与浅复制不同dog.age = self.age;return dog;
}@end

因此总而言之,浅拷贝就是创建一个副本,对内存地址的复制。深拷贝就是创建一个副本,对内容完全复制。原始对象与副本对象内存地址不同。

按照类型说明:

非容器类对象的深拷贝与浅拷贝

不可变字符串
int main(int argc, const char * argv[]) {@autoreleasepool {NSString* str1 = @"dddddd";NSString* str2 = [str1 copy];NSString* str3 = [str1 mutableCopy];NSMutableString* str4 = [str1 copy];NSMutableString* str5 = [str1 mutableCopy];NSLog(@"str1:%p", str1);NSLog(@"str2:%p", str2);NSLog(@"str3:%p", str3);NSLog(@"str4:%p", str4);NSLog(@"str5:%p", str5);}return 0;
}

得出结论: 字符串常量,只要是copy就是浅拷贝,mutableCopy是深拷贝。tips:我们用NSString stringWithstring 方式创建的是一个常量区字符串。

可变类型字符串 
int main(int argc, const char * argv[]) {@autoreleasepool {NSMutableString* str1 = [NSMutableString stringWithString:@"helloworld"];NSMutableString* str2 = [str1 copy];NSMutableString* str3 = [str1 mutableCopy];NSString* str4 = [str1 copy];NSString* str5 = [str1 mutableCopy];NSLog(@"str1:%p", str1);NSLog(@"str2:%p", str2);NSLog(@"str3:%p", str3);NSLog(@"str4:%p", str4);NSLog(@"str5:%p", str5);}return 0;
}

 打印结果:

 这里我们发现这里的两种拷贝都是深拷贝,它都重新创建了一块新的区域储存这一部分内容

因此:

可变对象copy后的对象是不可变的,mutableCopy后的对象是可变的

对于可变对象的复制都是深拷贝

容器类对象的深浅拷贝

NSArray:NSArray 是 Objective-C 中的 不可变数组类,它在创建之后 元素数量和顺序都不能更改

NSArray *array01 = [NSArray arrayWithObjects:@"a",@"b",@"c", nil];NSArray *copyArray01 = [array01 copy];NSMutableArray *mutableCopyArray01 = [array01 mutableCopy];NSLog(@"array01 = %p,copyArray01 = %p",array01,copyArray01);NSLog(@"array01 = %p,mutableCopyArray01 = %p",array01,mutableCopyArray01);
//-----------------------------------------------------NSLog(@"array01[0] = %p,array01[1] = %p,array01[2] = %p",array01[0],array01[1],array01[2]);NSLog(@"copyArray01[0] = %p,copyArray01[1] = %p,copyArray01[2] = %p",copyArray01[0],copyArray01[1],copyArray01[2]);NSLog(@"mutableCopyArray01[0] = %p,mutableCopyArray01[1] = %p,mutableCopyArray01[2] = %p",mutableCopyArray01[0],mutableCopyArray01[1],mutableCopyArray01[2]);

可以得出下列结论:

copyArray01和array01指向的是同一个对象,包括里面的元素也是指向相同的地址。
mutableCopyArray01和array01指向的是不同的对象,但是里面的元素指向相同的对象,mutableCopyArray01可以修改自己的对象。
copyArray01是对array01的指针复制(浅复制),而mutableCopyArray01是内容复制。
所以这就是不可变的容器对象 copy是浅拷贝 mutablecopy是深拷贝

NSMutableArray:NSMutableArray 是 Objective-C 中的可变数组类,可以动态添加、删除、替换数组中的元素。

NSMutableArray *array01 = [NSMutableArray arrayWithObjects:@"a",@"b",@"c", nil];NSArray *copyArray01 = [array01 copy];NSMutableArray *mutableCopyArray01 = [array01 mutableCopy];NSLog(@"array01 = %p,copyArray01 = %p",array01,copyArray01);NSLog(@"array01 = %p,mutableCopyArray01 = %p",array01,mutableCopyArray01);NSLog(@"array01[0] = %p,array01[1] = %p,array01[2] = %p",array01[0],array01[1],array01[2]);NSLog(@"copyArray01[0] = %p,copyArray01[1] = %p,copyArray01[2] = %p",copyArray01[0],copyArray01[1],copyArray01[2]);NSLog(@"mutableCopyArray01[0] = %p,mutableCopyArray01[1] = %p,mutableCopyArray01[2] = %p",mutableCopyArray01[0],mutableCopyArray01[1],mutableCopyArray01[2]);

 我们不难发现,对于可变对象的拷贝都是一个新对象,深拷贝。

 但是,集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制

用更为严谨的表达来说:

集合的 copy/mutableCopy 是容器级别的拷贝,里面的元素对象不会被复制,而是指针指向原对象。

 自定义类型的拷贝

#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
@endNS_ASSUME_NONNULL_END
#import "Person.h"
@interface Person()<NSCopying,NSMutableCopying>@end@implementation Person- (id)copyWithZone:(NSZone *)zone {Person *person = [[Person allocWithZone:zone]init];person.name = self.name;person.age = self.age;return person;
}- (id)mutableCopyWithZone:(NSZone *)zone {Person *person = [[Person allocWithZone:zone] init];person.name = [self.name mutableCopy]; person.age = self.age;return person;
}@end
#import <Foundation/Foundation.h>
#import "Person.h"int main(int argc, const char * argv[]) {@autoreleasepool {Person *person = [[Person alloc] init];person.name = @"Jack";person.age = 23;Person *copyPerson = [person copy]; // 深拷贝Person *mutableCopyPerson = [person mutableCopy]; // 深拷贝NSLog(@"person = %p;copyPerson = %p",person,copyPerson);NSLog(@"person = %p;mutableCopyPerson = %p",person,mutableCopyPerson);}return 0;
}

 我们可以看到无论是copy还是mutableCopy,都是深拷贝,这是因为我们本来就是使用copyWithZone和mutableCopyWithZone进行拷贝的。

容器类对象的深拷贝

1. copyItems 方法是NSArray和NSMutableArray的一个方法,用于创建一个新的数组,并对数组中的元素进行拷贝。该方法遍历原数组的每个元素,并对每个元素执行copy操作,然后将拷贝后的元素添加到新数组中。

int main(int argc, const char * argv[]) {@autoreleasepool {NSMutableString* str1;FKUser* p1 = [[FKUser alloc] initWithName:@"krystal" pass:@"12"];FKUser* p2 = [p1 copy];NSArray* ary = [NSArray arrayWithObjects:p1, p2, nil];NSArray* ary1 = [[NSArray alloc] initWithArray:ary copyItems:YES];NSLog(@"%p, %p", ary, ary1);NSLog(@"%p, %p", ary1[0], ary[0]);FKUser* p3 = ary1[0];FKUser* p4 = ary[0];NSLog(@"%p, %p", p3.name, p4.name);}return 0;
}

 我们对于数组内部的元素也进行一了一次深拷贝。这个方法实现了我们的对于容器内部的一个深拷贝。但是,要注意一个点,就是我们容器类对象中的元素是非容器类才可以实现一个复制,但是注意这里我们没有对对象进行更深一层的拷贝

如果想实现一个完全意义上的深拷贝,我们可以通过解档与归档,我也不懂。。。

为什么要按照类型说明深拷贝和浅拷贝

 一、对象的“值语义”与“引用语义”

在 Objective-C 和 Java 中,大多数对象是 引用类型,意味着变量存的是地址。

如果不区分是浅拷贝还是深拷贝,就会遇到下面的问题:

示例:浅拷贝带来的“数据共享”问题

Person *p1 = [[Person alloc] init];
p1.name = [NSMutableString stringWithString:@"Jack"];Person *p2 = p1; // 只是引用复制
[p2.name appendString:@"son"];
NSLog(@"p1.name = %@", p1.name); // ❌ 输出:Jackson,被污染了!

如果是深拷贝就不会:

Person *p2 = [p1 copy]; // p2 有自己的 name 对象

二、不同类型容器(如数组、字典)处理方式不同

NSArray 的 copy 和 mutableCopy:

  • 容器对象是新建的(浅层拷贝)

  • 但里面的元素是指针复制(浅拷贝)

这就导致:

NSMutableString *str = [NSMutableString stringWithString:@"abc"];
NSArray *arr1 = @[str];
NSArray *arr2 = [arr1 copy];[str appendString:@"def"];
NSLog(@"%@", arr2[0]); // ❌ abcdef,浅拷贝没有保护内容

如果你期望元素不被影响,就要手动执行“深拷贝”:

NSMutableArray *arrDeepCopy = [[NSMutableArray alloc] initWithArray:arr1 copyItems:YES];

 三、性能与内存控制的考虑

  • 浅拷贝速度快,节省内存(适合临时引用、只读场景)

  • 深拷贝安全性高,但代价是开销大(创建副本、递归 copy)

所以,只有明确区分类型的拷贝方式,才能:

  • 在 需要保护数据 时使用深拷贝

  • 在 优化性能 时使用浅拷贝

四、合理设计类的拷贝行为(实现 NSCopying 协议)

当你写一个类(如 Person)时,你要决定:

  • 哪些属性需要深拷贝(如 NSString, NSArray)

  • 哪些属性只需指针引用(如代理对象 delegate)

否则,会出现:

  • 数据共享引发逻辑错误,多次释放导致崩溃,内存泄漏

意义

说明

✅ 数据隔离

避免不同对象“共用一份内存”,引发修改互相影响

✅ 性能优化

只有必要时才创建副本,节省资源

✅ 安全性

避免数据被篡改、提前释放

✅ 明确设计意图

设计类时更清楚属性的生命周期和使用方式

相关文章:

  • 实战项目2(03)
  • Python基础总结(十)之函数
  • 2025-05-11 Unity 网络基础11——UnityWebRequest 使用
  • 【基于 LangChain 的异步天气查询5】多轮对话天气智能助手
  • MYSQL语句,索引,视图,存储过程,触发器(一)
  • 07.three官方示例+编辑器+AI快速学习webgl_buffergeometry_attributes_integer
  • 【Flask开发踩坑实录】pip 安装报错:“No matching distribution found” 的根本原因及解决方案!
  • Qt 中 QWidget涉及的常用核心属性介绍
  • Qt模块化架构设计教程 -- 轻松上手插件开发
  • 英伟达微调qwen2.5-32B模型,开源推理模型:OpenCodeReasoning-Nemotron-32B
  • DINOv2
  • Nipype使用:从安装配置到sMRI处理
  • C++:流插入、流提取操作符
  • Wordpress头像无法加载太慢问题解决方式
  • Quartus与Modelsim-Altera使用手册
  • 数值运算的误差估计
  • 深入理解深度循环神经网络(Deep RNN)
  • 4.4 os模块
  • 3. 仓颉 CEF 库封装
  • FME处理未知或动态结构教程
  • 日月谭天丨这轮中美关税会谈让台湾社会看清了什么?
  • 外国游客“在华扫货”热:“带空箱子到中国!”
  • 第四届长三角国际应急博览会开幕,超3000件前沿装备技术亮相
  • 退休10年后,70岁成都高新区管委会原巡视员王晋成被查
  • 普京提议重启俄乌直接谈判后,特朗普表态了
  • 陈宝良 高寿仙 彭勇︱明清社会的皇权、商帮与市井百态