引言
OC里有一个Protocol叫做NSCopying,它声明了一个必须要实现的方法- (id)copyWithZone:(nullable NSZone *)zone;
。
假设有一个Person类,如果要实现这个协议的话,我们一般是这么写的。
@interface Person: NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
- (id)copyWithZone:(nullable NSZone *)zone {
Person *copy = [[Person allocWithZone:zone] init];
copy->_name = _name.copy;
return copy;
}
@end
在方法里我们创建了一个Person对象,被将当前的_name作为实例变量传给了这个新建的对象,最后将这个对象返回,最后这样就实现了copy。
Copy作为属性修饰词的那些坑
- 被修饰的属性必须要实现NSCopying,否则crash。
- Copy不一定是Copy
- Foundation的immutable类如NSArray,NSString在使用Copy时等同于retain或strong。
先看下面的代码
NSString *str1 = @"string";
NSString *str1Copy = str1.copy;
NSLog(@"str1:%p, str2:%p", str1, str1Copy);
// str1:0x100001058, str2:0x100001058
NSMutableString *mStr1 = [[NSMutableString alloc] initWithString:str1];
NSMutableString *mStr1Copy = mStr1.copy;
NSLog(@"\nmStr1 class = %@, address = %p\nmStr1Copy class = %@, address = %p", mStr1.class, mStr1, mStr1Copy.class, mStr1Copy);
// mStr1 class = __NSCFString, address = 0x100500230
// mStr1Copy class = NSTaggedPointerString, address = 0x676e6972747365
@try {
[mStr1Copy appendString:@"a"];
} @catch (NSException *exception) {
NSLog(@"%@", exception);
// -[NSTaggedPointerString appendString:]: unrecognized selector sent to instance 0x676e6972747365
}
NSArray *arr = @[@1, @2, @3];
NSArray *arrCopy = arr.copy;
NSLog(@"arr:%p, arrCopy:%p", arr, arrCopy);
// arr:0x100202dd0, arrCopy:0x100202dd0
NSMutableArray *mArr = [NSMutableArray arrayWithArray:arr];
NSMutableArray *mArrCopy = mArr.copy;
NSLog(@"\nmArr class = %@, address = %p\nmArrCopy class = %@, address = %p", mArr.class, mArr, mArrCopy.class, mArrCopy);
// mArr class = __NSArrayM, address = 0x100200510
// mArrCopy class = __NSArrayI, address = 0x100202270
Foundation的NSArray,NSString和它们对应的mutable在使用copy时是与我们期待的行为不同的。
- immutable对象因为是不可变的,我们需要copy一个对象是因为需要对这个copy进行一定的改变,例如我copy一个文件,然后修改这个文件,保持源文件不变。既然对象本身不可变的,即使你copy出来也是不能改的,那么为什么还需要在内存里再塞一个一模一样的不可变对象呢?
- mutable对象在实现时也不是我们期待的copy行为,虽然这次的确创建了一个全新的对象,与immutable不同。但是这个新的对象除了在内容上和原来的对象一样外,但是它们的class却是不同的。mutable对象在实现copy方法时实际上是创建了一个immutable对象并返回。
上面两点印证了,Copy不一定是Copy。值得注意的是,因为dynamic typing的关系,对象实际的类型是在运行时决定而不是编译时决定的。所以NSMutableArray *mArrCopy = mArr.copy;
是完全没有问题的,你甚至可以写任何NSObject的子类。但是到了运行时就会原形毕露,所以这里会报错[mStr1Copy appendString:@"a"];
在看看我们自己实现的NSCopying吧,这正是我们所期待的的copy,两个对象地址不同,内容相同,你可以随便修改一个而不影响另一个。
Person *person = Person.new;
person.name = @"Noah";
Person *person2 = person.copy;
NSLog(@"\nperson address = %p name = %@\nperson2 address = %p name = %@", person, person.name, person2, person2.name);
// person address = 0x100203c40 name = Noah
// person2 address = 0x100202820 name = Noah
NOTE:
因为NSCopying只是一个Protocol,所以没什么阻止你乱来,你可以乱七八糟的返回一些让其他人困惑的东西。所以在开发时,不要假定copy的行为!除了系统的那些是已知的外,第三方或其他人实现的记得要看源码。
上面说了那么多,现在终于入正题,作为修饰词的用法。
关于实例变量,setter和getter这里不多说,看这里http://www.jianshu.com/p/e442711a867a
假设有一个类有一个属性Person
@property (nonatomic, copy) Person *person;
当属性被copy修饰后,编译器生成的setter大概是这样的
- (void)setPerson:(Person *)person {
_person = person.copy;
}
用strong的话是这样的
- (void)setPerson:(Person *)person {
_person = person;
}
实际上作为修饰词,copy没有什么特别的。它只是在setter里加了个copy,只要属性实现了NSCopying。所以为什么在没有实现NSCopying的属性上使用时会崩溃了。
NOTE:
修饰immutable对象时必须要使用copy。这个面试经常问到,很多人也无法准确说出。
前面说过因为dynamic typing的关系,对象的类型是在运行时决定的。所以str在运行时会是NSMutableString类型。如果开发者本人并不知情并继续使用下去的话就会出现问题了。因为运行时的类型并不是期待的类型。
NSString *str = nil;
NSMutableString *mStr = [[NSMutableString alloc] initWithString:@"str2"];
str = mStr;
解决方法也很简单,对可变对象copy一下就好了。str = mStr.copy
关于mutableCopy
这个东西不能作为属性修饰词,但这里也有必要说说。
继承自NSObject的类都有copy和mutableCopy两个方法。当调用这两个方法后,系统会自动调用- (id)copyWithZone:(nullable NSZone *)zone
或- (id)mutableCopyWithZone:(nullable NSZone *)zone;
两个方法除了名字外都一样,所以看你怎么实现。
系统的immutable或mutable类都实现了NSCopying和NSMutableCopying,下面是一些特点。
- 对可变对象进行copy,返回的会是一个不可变对象,并不是一般意义上的copy行为,因为copy后的对象类型不同了。
- 对可变对象进行mutableCopy,返回的是另一个可变对象,这种才是copy行为,两个对象内容一样,类型一样,地址不同。
- 对不可变对象进行copy,系统不会再创建对象,而是直接返回源对象地址。
- 对不可变对象进行mutableCopy则返回一个可变对象,同样不是一般意义上的copy。
- 在不可变对象上实现一般意义上的copy的方式是
immutable.mutableCopy.copy
NOTE:
本文重点是,NSCopying是一个protocol,所以不要假定它实现了一个copy行为,系统实现的就不是我们期待的了。当然,在我们实现时,最好还是按一般的行为来实现,这样才不会让别人困惑。