copy 与 mutableCopy 方法
copy 与 mutableCopy 方法总是返回原对象的副本,当程序对复制出来的副本 进行修改时,原来的 对象不受影响。
copy 方法
copy 方法用于复制对象的副本——通常来说,copy 方法总是返回对象的不可修改的副本。即使该对象本身是可以修改的。例如:程序调用NSMutableString的copy方法, 将会返回不可修改的字符串对象。
mutableCopy 方法
mutableCopy 方法用于复制对象的可变副本,——通常 来说,该方法总是返回该对象可修改的副本,即使被复制的对象本身是不可修改的。例如:程序调用 NSString 的 mutableCopy 方法,将会返回一个 NSMutableString 对象。
示例代码:
copyTest.m
#import <Foundation/Foundation.h>
int main(int argc , char * argv[])
{
@autoreleasepool{
NSMutableString* book = [NSMutableString
stringWithString:@"疯狂iOS讲义"];
// 复制book字符串的可变副本
NSMutableString* bookCopy = [book mutableCopy];
// 对副本修改,对原字符串没有任何影响
[bookCopy replaceCharactersInRange:
NSMakeRange(2, 3)
withString:@"Android"];
// 此处看到原字符串的值并没有改变
NSLog(@"book的值为:%@" , book);
// 字符串副本发生了改变。
NSLog(@"bookCopy的值为:%@" , bookCopy);
NSString* str = @"fkit";
// 复制str(不可变字符串)的可变副本
NSMutableString* strCopy = [str mutableCopy]; //①
// 向可变字符串后面追加字符串
[strCopy appendString:@".org"];
NSLog(@"%@" , strCopy);
// 调用book(可变字符串)的copy方法,程序返回一个不可修改的副本
NSMutableString* bookCopy2 = [book copy]; //②
// 由于bookCopy2是不可修改的,因此下面代码将会出现错误
[bookCopy2 appendString:@"aa"];
}
}
运行结果:
2015-10-01 11:34:36.841 923[2572:115135] book的值为:疯狂iOS讲义
2015-10-01 11:34:36.845 923[2572:115135] bookCopy的值为:疯狂Android讲义
2015-10-01 11:34:36.846 923[2572:115135] fkit.org
2015-10-01 11:34:36.874 923[2572:115135] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to mutate immutable object with appendString:'
NSCopying 与 NSMutableCopy
虽然NSObject 提供了 copy 和 mutableCopy 方法,但自定义类并不能直接调用这两个方法来复制自身。为什么呢?
为了保证一个 对象可以调用 copy 方法来复制自身的不可变副本,需要做如下事情:
1.让该类实现NSCopying协议。
2.让该类实现copyWithZone:
方法。
为了保证一个 对象可以调用 mutableCopy 方法来复制自身的可变副本,需要做如下事情:
1.让该类实现
NSMutableCopying
协议。
2.让该类实现mutableCopyWithZone:
方法。
当程序调用对象大的 copy 方法来复制自身时,程序底层需要调用copyWithZone:
方法来完成实际的复制工作,copy 实际上就是copyWithZone:
方法的返回值。
当程序调用对象大的 mutableCopy 方法来复制自身时,与copy方法类似。
copyWithZone:(NSZone*)zone
方法中的 zone 参数与不同德尔存储区有关,通常无需过多地关心该程序,只要将 zone 参数传给copyWithZone:(NSZone*)zone
方法,即可创建该对象的副本。
示例代码:
FKDog.h
#import <Foundation/Foundation.h>
@interface FKDog : NSObject <NSCopying>
@property (nonatomic , strong) NSMutableString* name;
@property (nonatomic , assign) int age;
@end
FKDog.m
#import "FKDog.h"
@implementation FKDog
@synthesize name;
@synthesize age;
//- (id)copyWithZone:(NSZone*)zone
//{
// NSLog(@"--执行copyWithZone--");
// // 使用zone参数创建FKDog对象
// FKDog* dog = [[[self class] allocWithZone:zone] init];
// dog.name = self.name;
// dog.age = self.age;
// return dog;
//}
// 为深复制实现的copyWithZone:方法
- (id)copyWithZone:(NSZone*)zone
{
NSLog(@"--执行copyWithZone--");
// 使用zone参数创建FKDog对象
FKDog* dog = [[[self class] allocWithZone:zone] init];
// 将原对象的name实例变量复制一份副本后赋值给新对象的name实例变量
dog.name = [self.name mutableCopy];
dog.age = self.age;
return dog;
}
@end
FKDogTest.m
#import <Foundation/Foundation.h>
#import "FKDog.h"
int main(int argc , char * argv[])
{
@autoreleasepool{
// 创建一个FKDog对象
FKDog* dog1 = [FKDog new];
dog1.name = [NSMutableString stringWithString:@"旺财"];
dog1.age = 20;
// 复制副本
FKDog* dog2 = [dog1 copy];
// 复制对象的可变副本
// FKDog* dog2 = [dog1 mutableCopy];
dog2.name = [NSMutableString stringWithString:@"snoopy"];
dog2.age = 12;
NSLog(@"dog1的名字为:%@" , dog1.name);
NSLog(@"dog1的年龄为:%d" , dog1.age);
NSLog(@"dog2的名字为:%@" , dog2.name);
NSLog(@"dog2的年龄为:%d" , dog2.age);
}
}
运行 结果:
2015-10-01 12:12:30.270 923[2689:128044] --执行copyWithZone--
2015-10-01 12:12:30.287 923[2689:128044] dog1的名字为:旺财
2015-10-01 12:12:30.288 923[2689:128044] dog1的年龄为:20
2015-10-01 12:12:30.288 923[2689:128044] dog2的名字为:snoopy
2015-10-01 12:12:30.289 923[2689:128044] dog2的年龄为:12
深复制(deep copy)与浅复制(shallow copy)
我们用以下程序来理解浅复制,深复制。
FKDog.h
#import <Foundation/Foundation.h>
@interface FKDog : NSObject <NSCopying>
@property (nonatomic , strong) NSMutableString* name;
@property (nonatomic , assign) int age;
@end
FKDog.m
#import "FKDog.h"
@implementation FKDog
@synthesize name;
@synthesize age;
//浅复制
- (id)copyWithZone:(NSZone*)zone
{
NSLog(@"--执行copyWithZone--");
// 使用zone参数创建FKDog对象
FKDog* dog = [[[self class] allocWithZone:zone] init];
dog.name = self.name;
dog.age = self.age;
return dog;
}
// 为深复制实现的copyWithZone:方法
//- (id)copyWithZone:(NSZone*)zone
//{
// NSLog(@"--执行copyWithZone--");
// // 使用zone参数创建FKDog对象
// FKDog* dog = [[[self class] allocWithZone:zone] init];
// // 将原对象的name实例变量复制一份副本后赋值给新对象的name实例变量
// dog.name = [self.name mutableCopy];
// dog.age = self.age;
// return dog;
//}
@end
运行结果:
2015-10-01 21:08:10.589 923[3306:181752] --执行copyWithZone--
2015-10-01 21:08:10.599 923[3306:181752] dog2的name为:snoopy
2015-10-01 21:08:10.600 923[3306:181752] dog1的name为:snoopy
说明:前面的代码中,我们只改了dog2 的 name 属性的值,为何 dog1 的 name 也发生改变了呢?
示意图:
dog1,dog2, 两个指针分别指向两个不同的 FKDog 对象,但这两个 FKDog 对象 的 name 实例变量都是指针,且它们都 指向同一个 NSMutabbleString 对象。所以会出现前面的情况。
这里就有了一个浅复制的概念:
浅复制浅复制——当对象的实例变量是指针变量时,如果哦程序只是复制该指针的地址,而不是真正复制指针所指向的对象。这种方式就被称为浅复制。
深复制:不仅会复制对象本身,还会”递归”复制每个指针类型的实例变量,直到两个对象没有任何共用的部分。上面将实现深复制的代码,注释解开以后即可以实现深复制——对象与复制对象彼此没有关联,相互不影响。
一般来说,foundation 框架中的类大部分都只实现浅复制。深复制的实现难度较大,尤其是当该对象包含大量的制造者类型的实例变量时——若某些实例变量里再次包含指针类型的实例变量,那么实现深复制会更加复杂。
setter 方法的复制选项
可以使用 copy 指示符合成 setter,getter 方法。 copy 指示符——指定当程序调用 setter 方法复制时,实际上是将传入参数的副本赋给程序的实例变量。
实例程序:
FKItem.h
#import <Foundation/Foundation.h>
@interface FKItem : NSObject
@property (nonatomic , copy) NSMutableString* name;
@end
FKItem.m
#import "FKItem.h"
@implementation FKItem
@synthesize name;
@end
FKItemTest.m
#import <Foundation/Foundation.h>
#import "FKItem.h"
int main(int argc , char * argv[])
{
@autoreleasepool{
// 创建一个FKItem对象
FKItem* item = [FKItem new];
// 对item的name属性赋值
item.name = [NSMutableString stringWithString:
@"疯狂iOS讲义"];
// 为item的name属性后追加一个字符串
[item.name appendString:@"fkit"];
}
}
运行结果:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to mutate immutable object with appendString:'
运行出现了错误,无法进行添加字符操作,这是为啥呢?
是因为定义name 属性的时候,使用了 copy指示符,该指示符调用setName:
方法(使用的点语法)时,程序实际上会使用参数的副本对 name 实例变量复制。
setName:`方法的代码如下:
-(void) setName:(NSMutableString *) aname
{
name=[aname copy];
}
重点在这里:copy 方法默认是复制该对象的不可变副本,虽然程序传入的NSMutableString,但程序调用该参数的 copy 方法得到的是不可变副本。——因此,name 的值依然是不可变字符串。