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)
否则,会出现:
-
数据共享引发逻辑错误,多次释放导致崩溃,内存泄漏
意义 | 说明 |
---|---|
✅ 数据隔离 | 避免不同对象“共用一份内存”,引发修改互相影响 |
✅ 性能优化 | 只有必要时才创建副本,节省资源 |
✅ 安全性 | 避免数据被篡改、提前释放 |
✅ 明确设计意图 | 设计类时更清楚属性的生命周期和使用方式 |