从copy和mutableCopy谈起

Copy和mutableCopy

NSObject类有两个跟拷贝相关的方法——copy和mutableCopy。这两个方法都是返回一个id类型的对象,那么这两者之间有什么区别呢?根据官方文档解释,copy方法,返回copyWithZone方法返回的对象(Returns the object returned by copyWithZone:)。而mutableCopy方法,返回mutableCopyWithZone方法返回的对象(Returns the object returned by mutableCopyWithZone:)。读起来有点绕,一言以蔽之,调用copy就是调用copyWithZone,调用mutableCopy就是调用mutableCopyWithZone。还是不够清楚!!!接下来我们以NSString为例子,来说明copy和mutableCopy的区别。

NSString对象调用copy和mutableCopy

NSString *string = @"a";
NSString *stringCopy = [string copy];
NSMutableString *stringMCopy = [string mutableCopy];
NSLog(@"string:%@ -- %p",[string class],string);
NSLog(@"stringCopy:%@ -- %p",[stringCopy class],stringCopy);
NSLog(@"stringMCopy:%@ -- %p",[stringMCopy class],stringMCopy);

打印结果如下:

2017-05-22 12:24:23.175 Copy&MutableCopy[24729:5637322] string:__NSCFConstantString -- 0x10d24d068
2017-05-22 12:24:23.176 Copy&MutableCopy[24729:5637322] stringCopy:__NSCFConstantString -- 0x10d24d068
2017-05-22 12:24:23.176 Copy&MutableCopy[24729:5637322] stringMCopy:__NSCFString -- 0x608000078800

由以上输出可知,对一个NSString对象调用copy返回的还是该对象本身,因为str的地址和cpStr的地址是同一个。而调用mutableCopy,返回的是一个NSMutableString对象(注:__NSCFConstantString是常量串即NSString,而__NSCFString是可变串即NSMutableString)。

NSMutableString对象调用copy和mutableCopy

NSMutableString *mString = [NSMutableString stringWithString:@"b"];
NSString *mstringCopy = [mString copy];
NSMutableString *mstringMCopy = [mString mutableCopy];
NSLog(@"mString:%@ -- %p",[mString class],mString);
NSLog(@"mstringCopy:%@ -- %p",[mstringCopy class],mstringCopy);
NSLog(@"mstringMCopy:%@ -- %p",[mstringMCopy class],mstringMCopy);

打印结果如下:

2017-05-22 12:24:23.176 Copy&MutableCopy[24729:5637322] mString:__NSCFString -- 0x608000078b00
2017-05-22 12:24:23.176 Copy&MutableCopy[24729:5637322] mstringCopy:NSTaggedPointerString -- 0xa000000000000621
2017-05-22 12:24:23.176 Copy&MutableCopy[24729:5637322] mstringMCopy:__NSCFString -- 0x608000078ac0

由以上输出可知,对一个NSMutableString对象调用copy返回的是一个NSTaggedPointerString对象,该对象可认为是一个常量串。而调用mutableCopy返回的是另外一个可变对象__NSCFString,即NSMutableString(原NSMutableString对象的地址是0x7fabba9012c0,新NSMutableString对象地址是0x7fabb840a860)。

针对NSArray、NSDictionary、NSSet等具有Mutable版本的类进行试验出现跟NSString类似的现象,不一一列举,有兴趣可以自己去试验。

copy和mutableCopy调用小结

class copy mutablecopy
不可变(如,NSString) 返回本身(相当于retain一次) 创建新的可变对象(如,创建一个NSMutableString对象,地址跟原对象不同)
可变(如,NSMutableString) 创建新的不可变对象(如,创建一个NSTaggedPointerString对象,地址跟原对象不同 ) 创建新的可变对象(如,创建一个NSMutableString对象,地址跟原对象不同 )
  • 针对不可变对象调用copy返回该对象本身,调用mutableCopy返回一个可变对象(新的);
  • 针对可变对象调用copy返回一个不可变对象(新的),调用mutableCopy返回另外一个可变对象(新的)。

再进一步从是否新建返回对象,返回对象是否可变两个角度总结如下:

  • 只有不可变的copy是retain一次,其他都是创建一个新对象;
  • copy返回的是不可变对象,mutableCopy返回的是可变对象。

属性copy、strong

先来看下测试代码

@property (nonatomic, strong) NSString *strongStr;
@property (nonatomic, copy) NSString *copStr;
@property (nonatomic, strong) NSMutableString *mStrongStr;
@property (nonatomic, copy) NSMutableString *mCopStr;
NSString *str = @"test";
NSLog(@"str:   %@       -- %p",str,str);
self.strongStr = str;
NSLog(@"strongStr:   %@       -- %p",_strongStr,_strongStr);
self.copStr= str;
NSLog(@"copStr:   %@       -- %p",_copStr,_copStr);
//    self.mStrongStr = str;  
//    NSLog(@"mStrongStr:%@ -- %p",_mStrongStr,_mStrongStr);
//    self.mCopStr= str;
//    NSLog(@"mCopStr:%@ -- %p",_mCopStr,_mCopStr);
str = @"aaa";
NSLog(@"str:   %@       -- %p",str,str);
NSLog(@"strongStr:   %@       -- %p",_strongStr,_strongStr);
NSLog(@"copStr:   %@       -- %p",_copStr,_copStr);

NSMutableString *str2 = [NSMutableString stringWithString:@"test2"];
NSLog(@"str2:   %@       -- %p",str2,str2);
self.strongStr = str2;
NSLog(@"strongStr:   %@       -- %p",_strongStr,_strongStr);
self.copStr= str2;
NSLog(@"copStr:   %@       -- %p",_copStr,_copStr);
self.mStrongStr = str2;
NSLog(@"mStrongStr:   %@       -- %p",_mStrongStr,_mStrongStr);
self.mCopStr= str2;
NSLog(@"mCopStr:   %@        - %p",_mCopStr,_mCopStr);

[str2 appendString:@"bbb"];
NSLog(@"str2:   %@       -- %p",str2,str2);
NSLog(@"strongStr:   %@       -- %p",_strongStr,_strongStr);
NSLog(@"copStr:   %@       -- %p",_copStr,_copStr);
NSLog(@"mStrongStr:   %@       -- %p",_mStrongStr,_mStrongStr);
NSLog(@"mCopStr:   %@        -- %p",_mCopStr,_mCopStr);

2017-05-22 12:57:35.662 Copy&MutableCopy[25257:5764440] str:   test       -- 0x10bf20078
2017-05-22 12:57:35.663 Copy&MutableCopy[25257:5764440] strongStr:   test       -- 0x10bf20078
2017-05-22 12:57:35.663 Copy&MutableCopy[25257:5764440] copStr:   test       -- 0x10bf20078
2017-05-22 12:57:35.663 Copy&MutableCopy[25257:5764440] str修改后:   aaa       -- 0x10bf200f8
2017-05-22 12:57:35.663 Copy&MutableCopy[25257:5764440] strongStr修改后:   test       -- 0x10bf20078
2017-05-22 12:57:35.663 Copy&MutableCopy[25257:5764440] copStr修改后:   test       -- 0x10bf20078
//由以上输出可知,假设两个NSString属性实际上指向的都是一个NSString对象,那么在原NSString对象修改后,strong版本的NSString和copy版本属性都保持原状。

2017-05-22 12:57:35.663 Copy&MutableCopy[25257:5764440] str2:   test2       -- 0x608000262400
2017-05-22 12:57:35.663 Copy&MutableCopy[25257:5764440] strongStr:   test2       -- 0x608000262400
2017-05-22 12:57:35.663 Copy&MutableCopy[25257:5764440] copStr:   test2       -- 0xa000032747365745
2017-05-22 12:57:35.664 Copy&MutableCopy[25257:5764440] mStrongStr:   test2       -- 0x608000262400
2017-05-22 12:57:35.664 Copy&MutableCopy[25257:5764440] mCopStr:   test2        - 0xa000032747365745
2017-05-22 12:57:35.664 Copy&MutableCopy[25257:5764440] str2修改后:   test2bbb       -- 0x608000262400
2017-05-22 12:57:35.664 Copy&MutableCopy[25257:5764440] strongStr修改后:   test2bbb       -- 0x608000262400
2017-05-22 12:57:35.664 Copy&MutableCopy[25257:5764440] copStr修改后:   test2       -- 0xa000032747365745
2017-05-22 12:57:35.664 Copy&MutableCopy[25257:5764440] mStrongStr修改后:   test2bbb       -- 0x608000262400
2017-05-22 12:57:35.664 Copy&MutableCopy[25257:5764440] mCopStr修改后:   test2        -- 0xa000032747365745
//由以上输出可知,假设两个NSString属性实际上指向的都是一个NSMutableString对象,那么在原NSMutableString对象修改后,strong版本的NSString属性跟着修改,而copy版本属性保持原状。self.cpStr实际上是一个NSTaggedPointerString对象,该对象正是NSMutableString对象执行copy的返回值

如果

 [self.mCopStr appendString:@"bbb"];

报错:

[NSTaggedPointerString appendString:]: unrecognized selector sent to instance 0xa000032747365745

原因很明显,是朝NSTaggedPointerString对象发了一个它不能识别的selector。原因是copy版本的NSMutableString属性本质上不是一个NSMutableString对象,而是一个NSTaggedPointerString对象,它是一个不可变对象。该对象是NSMutableString对象执行copy得来的,还记得我们上一节的结论吗?对一个对象执行copy得到的用于是一个不可变的对象。

针对NSArray、NSDictionary、NSSet等具有Mutable版本的类进行试验出现跟NSString类似的现象。

结论

  • 不可变类型属性,推荐使用copy,因为假设该对象实际上指向的是一个mutable的对象,mutable对象的改变不会导致该对象的改变;假设指向的不是mutable的对象,那么copy和strong是等价,都是执行一次retain。
  • 可变类型属性,不能使用copy,因为copy产生的对象是一个不可变对象,跟属性描述是冲突的。

原文地址:http://www.cocoachina.com/ios/20151202/14520.html