对象复制与拷贝
文章目录
- 对象复制与拷贝
- 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
协议。否则,编译器会报错。