深拷贝和浅拷贝
深拷贝和浅拷贝主要是对类类型而言的,浅拷贝就是指针拷贝,深拷贝是对象拷贝。
property的strong和copy
在接触iOS程序时经常会看到程序某些类类型属性被strong修饰,某些被copy修饰,刚开始接触时有些疑惑,后来不知咋地形成了这样的观念,对于mutable类型,用copy修饰,对于immutable类型,用strong修饰,现在看来这个大大地错了。
创建属性是一件机械化的工作:对于一般的属性,你会将它们声明为 nonatomic。默认情况下,对象属性是strong的,标量属性是assign的。但是有一个例外,就是对于具有可变副本的属性,我们倾向于将其声明为 copy。比如说,name属性的类型是NSString,有可能有人创建了一个Person对象,并且给这个属性赋了一个NSMutableString的名字值。然后过了一会儿,这个可变字符串被变更了。如果我们的属性不是copy而是strong的话,随着可变字符串的改变,我们的Person对象也将发生改变,这不是我们希望发生的。对于类似数组或者字典这样的容器类来说,也是这样的情况。
简单来说,对于被strong修饰property,在对其赋值时,并不是真正拷贝,而只是将右值的retain count加1,将左值的指针改为右值(也是一个指针)所指向的地址,直观来看,即赋值后左值和右值的地址值是一样一样的,即浅拷贝;
但对于被copy所修饰的property且它遵循NSCopying协议,在为property赋值时,赋值语句不会保留新值(变量不会随可变字符串的改变而改变),直观来看,赋值后左值和右值的地址值可能不同,也即“赋值语句可能是深拷贝,也可能是浅拷贝”;
P.S:这里为什么说“可能”呢?下文会对这个问题进行阐述。
P.S:对于属性的赋值,单纯理解为浅拷贝或深拷贝操作是不对的,因为还会改变retain count的值(对于浅拷贝而言)。
举个栗子:
// 两个属性,前者用copy修饰,后者用strong修饰 @property (nonatomic, copy) NSString *firstName; @property (nonatomic, strong) NSString *lastName; NSMutableString *str1 = [NSMutableString stringWithString:@"不坏"]; self.firstName = str1; // 赋值 // 打印左值和右值的地址值 NSLog(@"str1 addr: 0x%lx", str1); NSLog(@"self.first addr: 0x%lx", self.firstName); NSLog(@""); NSMutableString *str2 = [NSMutableString stringWithString:@"张"]; self.lastName = str2; // 赋值 // 打印左值和右值的地址值 NSLog(@"str2 addr: 0x%lx", str2); NSLog(@"lastName addr: 0x%lx", self.lastName);
打印结果是:
str1 addr: 0x7967f580 self.first addr: 0x7967f1d0 str2 addr: 0x7967f460 lastName addr: 0x7967f460
显然,对于用copy修饰的firstName,其赋值所采用的方式是深拷贝;对于用strong修饰的lastName,赋值方式是浅拷贝。
回过头来解释上述的“可能”。
其实很简单,对于赋值语句self.firstName = str1;
(firstName被copy修饰),如果右值str1是mutable,则这条语句处理的方式是深拷贝。因为如果str1是mutable,并且执行浅拷贝的话,这就意味着如果str1改变了,则self.firstName也会跟着改变,这是我们不希望的,所以执行深拷贝时合理的;如果右值str1是immutable,则赋值语句所处理的方式依然是浅拷贝,因为反正str1不会再发生改变了,对self.firstName值不会有意想不到的影响,此时进行深拷贝有些浪费。
P.S:Objective-C的这种设计理念略显啰嗦,比较智能的处理方法是当str1改变时,彼时再进行深拷贝处理,很多现代语言都是这么弄的,譬如Python。
总结:property的关键字copy的本意是为了保护“immutable属性在被mutable右值对象赋值后不被右值之后可能发生的改变所影响”,而对mutable属性,则不存在这样的问题。