OC Foundation框架 对象复制
Posted Billy Miracle
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OC Foundation框架 对象复制相关的知识,希望对你有一定的参考价值。
NSObject类提供了copy和mutableCopy方法,通过这两个方法即可复制已有对象的副本。
copy与mutableCopy方法
copy方法用于复制对象的副本。通常来说,copy方法总是返回对象的不可修改的副本,即使该对象本身是可修改的。
mutableCopy方法用于复制对象的可变副本。通常来说,mutableCopy方法总是返回对象的可修改的副本,即使该对象本身是不可修改的。
无论如何,它们返回的总是原对象的副本,当程序对复制的副本进行修改时,原对象通常不受影响。
示例程序:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableString* book = [NSMutableString
stringWithString:@"Billy"];
//复制book字符串的可变副本
NSMutableString* bookCopy = [book mutableCopy];
//修改副本,对原字符串没有任何影响
[bookCopy replaceCharactersInRange:
NSMakeRange(2, 3)
withString:@"llboard"];
//此处看到原字符串的值并没有改变
NSLog(@"book的值为:%@", book);
//字符串副本发生了改变
NSLog(@"bookCopy的值为:%@", bookCopy);
NSString* str = @"Amy";
//复制不可变字符串str的可变副本
NSMutableString* strCopy = [str mutableCopy];
//向可变字符串后面追加字符串
[strCopy appendString:@".hhh"];
NSLog(@"%@", strCopy);
//调用可变字符串book的copy方法,程序返回一个不可修改的副本
NSMutableString* bookCopy2 = [book copy];
//由于bookCopy2是不可修改的,因此下面的代码将会出现错误
//[bookCopy2 appendString:@"aa"];
}
return 0;
}
可以看到输出结果为:
book的值为:Billy
bookCopy的值为:Billboard
Amy.hhh
NSCopying与NSMutableCopying协议
通过上面的程序我们发现使用copy和nutableCopy方法复制对象的副本使用起来确实很方便,那么自定义类是否可以调用copy与mutableCopy方法来复制副本呢?
示例:
// Car.h
#import <Foundation/Foundation.h>
@interface Car : NSObject
@property (nonatomic, strong) NSMutableString* brand;
@property (nonatomic, strong) NSMutableString* type;
@property (nonatomic, assign) int price;
@end
// main.m
#import "Car.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Car* car1 = [[Car alloc] init];
car1.brand = [NSMutableString stringWithString:@"BMW"];
car1.type = [NSMutableString stringWithString:@"M3"];
car1.price = 90;
Car* car2 = [car1 copy];
}
return 0;
}
程序编译不会有问题,但运行会出错:
-[Car copyWithZone:]: unrecognized selector sent to instance 0x1006440c0
提示Car类找不到copyWithZone:方法。
再试试:
Car* car3 = [car1 mutableCopy];
也会出错:
[Car mutableCopyWithZone:]: unrecognized selector sent to instance 0x1050c20e0
提示Car类找不到mutableCopyWithZone:方法。
通过上面的尝试可以看出,虽然NSObject提供了copy和mutableCopy方法,但是我们自定义的类并不能够直接调用这两个方法来复制自身。为了保证一个对象可以调用copy方法来复制自身的不可变副本,通常需要做如下事情:
- 让该类实现NSCopying协议。
- 让该类实现copyWithZone: 方法。
与此同时,为了保证一个对象可以调用mutableCopy方法来复制自身的可变副本,通常需要做如下事情:
- 让该类实现NSMutableCopying协议。
- 让该类实现mutableCopyWithZone: 方法。
下面在Car类的接口部分声明实现NSCopying协议,然后在Car类的实现部分增加如下copyWithZone: 方法。
// Car.m
#import "Car.h"
@implementation Car
-(id)copyWithZone: (NSZone*) zone {
NSLog(@"执行copyWithZone:");
Car* car = [[[self class] allocWithZone: zone] init];
car.brand = self.brand;
car.type = self.type;
car.price = self.price;
return car;
}
@end
上面代码让Car实现了NSCopying协议,并实现了copyWithZone: 方法,在该方法中重新创建了一个Car对象,并让该对象的所有属性值与被复制对象的属性值相等,最后返回这个新创建的方法,也就是返回该对象的副本。copyWithZone: (NSZone*) zone方法中的zone参数与不同的储存区有关,通常无须过多地关心该参数,只要将zone参数传给copyWithZone:方法即可。
主程序:
#import "Car.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Car* car1 = [[Car alloc] init];
car1.brand = [NSMutableString stringWithString:@"BMW"];
car1.type = [NSMutableString stringWithString:@"M3"];
car1.price = 90;
Car* car2 = [car1 copy];
car2.brand = [NSMutableString stringWithString:@"Alfa"];
car2.type = [NSMutableString stringWithString:@"Giulia"];
car2.price = 100;
NSLog(@"car1\\'s brand: %@",car1.brand);
NSLog(@"car1\\'s type: %@",car1.type);
NSLog(@"car1\\'s price: %d",car1.price);
NSLog(@"car2\\'s brand: %@",car2.brand);
NSLog(@"car2\\'s type: %@",car2.type);
NSLog(@"car2\\'s price: %d",car2.price);
}
return 0;
}
输出结果:
执行copyWithZone:
car1's brand: BMW
car1's type: M3
car1's price: 90
car2's brand: Alfa
car2's type: Giulia
car2's price: 100
为什么此处调用copy时返回的依然是一个可变的Car对象呢?因为此处的Car没有提供对应的不可变类,自然无法复制不可变的Car对象。如果Car提供了不可变类,还是应当让Car的copyWithZone: 返回不可变的Car对象。
如果重写copyWithZone: 方法时,其父类已经实现了NSCopying协议,并且重写过copyWithZone: 方法,那么子类重写copyWithZone: 方法应该先调用父类的copy方法复制从父类继承到的成员变量,然后对子类中定义的成员变量进行赋值。
格式如下:
-(id)copyWithZone:(NSZone*)zone {
id obj = [super copy];
//对子类定义的成员变量赋值
...
return obj;
}
浅复制与深复制
先看一段示例程序:
#import "Car.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Car* car1 = [[Car alloc] init];
car1.brand = [NSMutableString stringWithString:@"BMW"];
car1.type = [NSMutableString stringWithString:@"M3"];
car1.price = 90;
Car* car2 = [car1 copy];
[car2.brand replaceCharactersInRange:NSMakeRange(0, 3) withString:@"Mazda"];
NSLog(@"car1\\'s brand: %@",car1.brand);
NSLog(@"car2\\'s brand: %@",car2.brand);
}
return 0;
}
上面程序调用了car1的copy方法复制了一个副本,并将该副本赋给car2变量,接下来修改car2对象的属性值。
输出:
执行copyWithZone:
car1's brand: Mazda
car2's brand: Mazda
可以看到,虽然只是修改了car2的brand属性值,但是为什么car1的brand也发生改变了呢?
创建了一个Car对象并使用car1指针指向了堆内存中的一片区域。接下来要复制一个Car对象,查看copyWithZone:代码,是如下三行:
car.brand = self.brand;
car.type = self.type;
car.price = self.price;
注意,name与type都只是指针变量,该变量中存放的其实是字符串的地址,而非存储字符串本身。这种赋值效果让两个对象的brand和type分别指向两个相同的字符串,效果如下:
从图中可以看出,此时car1与car2两个指针指向两个不同的Car对象,但brand和type属性都指针,可以看到,两个brand指向了同一个NSMutableString对象,type也是如此。这样一来,程序修改任何一个brand或type属性时,另一个对象的属性也会发生改变。
对于这种复制方式:当对象的属性是指针变量时,如果程序仅仅是复制该指针的地址,而不是复制指针所指的对象,这种方式被称为“浅复制”。也就是两个对象存在共用的部分。
深复制会采取不同的方式,深复制不仅会复制对象本身,而且会“递归”复制每个指针类型的属性,直到两个对象没有任何共用的部分。如果将上面的copyWithZone: 改为如下形式,即可实现深复制。
-(id)copyWithZone: (NSZone*) zone {
NSLog(@"执行copyWithZone:");
Car* car = [[[self class] allocWithZone: zone] init];
car.brand = [self.brand mutableCopy];
car.type = [self.type mutableCopy];
car.price = self.price;
return car;
}
上面程序没有直接将对象赋值,而是先将原对象的name属性值复制了一份可变副本,再将该副本的值赋给新对象的name属性,这样就实现了深复制。
一般来说,深复制的实现难度大很多,尤其是当该对象包含大量的指针类型属性时,如果某些属性所引用的对象再次包含指针类型的属性,那么实现深复制会更加复杂。
一般来说,Foundation框架中的类大部分都只实现了浅复制。
setter方法的复制选项
前面介绍合成setter方法和gatter方法时提到可以使用copy指示符,copy的作用是当程序调用setter方法复制时,将传入参数的副本赋给程序的实例变量。
// Item.h
#import <Foundation/Foundation.h>
@interface Item : NSObject
@property (nonatomic , copy) NSMutableString* name;
@end
// main.m
#import "Item.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Item* item = [Item new];
item.name = [NSMutableString stringWithString: @"Billy"];
[item.name appendString:@"hhh"];
}
return 0;
}
编译程序不会出现问题,但是运行会出错:
[NSTaggedPointerString appendString:]: unrecognized selector sent to instance 0x966d797a018becfe
提示不允许修改name属性值,这是因为程序定义name属性时使用的是copy指示符。也就是说,setName: 方法的代码如下:
-(void) setName: (NSMutableString*) aname {
name = [aname copy];
}
定义合成getter、setter方法时没有提供mutableCopy指示符,因此,即使定义实例变量时使用了可变类型,但只要使用了copy指示符,实例变量实际得到的值总是不可变对象。
以上是关于OC Foundation框架 对象复制的主要内容,如果未能解决你的问题,请参考以下文章