导语:copy从字面意思上来讲就是复制的意思,好比我们平时通过一个文件来复制生成另外一个文件一样,而两个文件互不干扰。其实在oc中,copy的意思也是复制一个对象
copy的特点
- 修改源对象的属性和行为,不会影响副本对象
- 修改副本对象的属性和行为,不会影响源对象
oc中NSString、NSArray、NSDictionary的copy和mutableCopy
- 通过copy创建不可变的副本(NSString,NSArray等)
- 通过mutableCopy创建可变的副本(NSMutableString,NSMutableArray等)
- 对象能使用copy的前提是遵循了NSCopying协议,并实现了copyWithZone:方法
NSString中的copy和mutableCopy
- 不可变的字符串调用copy方法
NSString *str = @"123";
NSString *newStr = [str copy];
NSLog(@"str = %p, newStr = %p", str, newStr);
**2021-11-10 19:14:45.469933+0800 copy的基本使用[15171:1881395] str = 0x100004018, newStr = 0x100004018**
- 不可变的字符串调用mutableCopy方法
NSString *str = @"123";
NSMutableString *newStr = [str mutableCopy];
NSLog(@"str = %p, newStr = %p", str, newStr);
**2021-11-10 19:18:28.447722+0800 copy的基本使用[15315:1883896] str = 0x100004018, newStr = 0x104804b30**
- 可变的字符串调用copy方法
NSMutableString *mutableStr = [[NSMutableString alloc]initWithString:@"123"];
NSString *newMutableStr = [mutableStr copy];
NSLog(@"mutableStr = %p, newMutableStr = %p", mutableStr, newMutableStr);
**2021-11-10 19:22:27.857990+0800 copy的基本使用[15480:1887745] mutableStr = 0x1005480a0, newMutableStr = 0xd509abef3c7c50c5**
- 可变的字符串调用mutableCopy方法
NSMutableString *mutableStr = [[NSMutableString alloc]initWithString:@"123"];
NSMutableString *newMutableStr = [mutableStr mutableCopy];
NSLog(@"mutableStr = %p, newMutableStr = %p", mutableStr, newMutableStr);
**2021-11-10 19:23:50.067708+0800 copy的基本使用[15534:1888793] mutableStr = 0x100598980, newMutableStr = 0x1005989d0**
可以看出只有不可变的字符串进行copy操作的时候才会不生成新的对象,而其他的三种情况都会生成新的对象。这也正好跟copy的特点相吻合,不可变的字符串进行copy操作的时候产品的副本也是不可变的,两者都不可变,所以不生成新的对象也不会造成源对象和副本对象互相影响,其他的三种情况都有可变的对象,所以必须生成一个新的对象,这样才能互不干扰
NSArray中的copy和mutableCopy
NSArray的copy其实跟NSString的copy操作是一样的,只有不可变数组进行copy操作不会生成新的对象,其他的三种情况都会生成新的操作
NSArray *arr = @[@1,@2,@3];
NSArray *newArr = [arr copy];
NSLog(@"arr = %p, newArr = %p", arr, newArr);
NSMutableArray *newMutableArr = [arr mutableCopy];
NSLog(@"arr = %p, newMutableArr = %p", arr, newMutableArr);
NSMutableArray *mutableArr = [NSMutableArray arrayWithObject:@1];
NSArray *newArr1 = [mutableArr copy];
NSLog(@"mutableArr = %p, newArr1 = %p", mutableArr, newArr1);
NSArray *newMutableArr1 = [mutableArr mutableCopy];
NSLog(@"mutableArr = %p, newMutableArr1 = %p", mutableArr, newMutableArr1);
**2021-11-10 19:34:06.700294+0800 copy的基本使用[15884:1893935] arr = 0x1000080a8, newArr = 0x1000080a8**
**2021-11-10 19:34:06.700918+0800 copy的基本使用[15884:1893935] arr = 0x1000080a8, newMutableArr = 0x100744d80**
**2021-11-10 19:34:06.701005+0800 copy的基本使用[15884:1893935] mutableArr = 0x100744c90, newArr1 = 0x100746390**
**2021-11-10 19:34:06.701051+0800 copy的基本使用[15884:1893935] mutableArr = 0x100744c90, newMutableArr1 = 0x100746d00**
NSDictionary中的copy和mutableCopy
NSDictionary中的copy操作也是跟NSString一样的,在这里就不过多举例了
通过上面的例子我们可以引出两个概念:
深拷贝和浅拷贝
深拷贝:拷贝对象本身,在内存中生存另外一个对象 浅拷贝:不拷贝对象本身,仅仅是拷贝对象的指针。在MRC中相单于将对象进行了一次retain操作
从这个图中可以看出只有[NSArray copy]、[NSDictionary copy]、[NSString copy]是浅拷贝,其他的操作都是深拷贝。
copy在property(属性中的使用)
在这,我们先思考一个问题,在property(属性)中NSString的用strong还是用copy好?
我们先看下面一个例子:
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@end
NSMutableString *str = [[NSMutableString alloc]initWithString:@"MK"];
Person *person = [[Person alloc]init];
person.name = str;
[str appendString:@" cool"];
NSLog(@"person.name = %@", person.name);
2021-11-10 19:54:11.636787+0800 copy的基本使用[16675:1905943] person.name = MK cool
在上面我们定义了一个Person类,并且里面有一个strong修饰的name属性。在main函数中我们用一个可变的字符串赋值给person实例里面的name变量,当可变的str字符串追加了字符串的时候,我们发现person中的name属性也发生了改变。因为strong修饰符相单于MRC里面的retain修饰符,在MRC只是把引用计数加1,而在strong里面其实就是多了一个指针指向了MK的字符串。如果把strong修饰符改成copy就不会出现这个问题,因为可变的字符串进行copy操作也会生成一个新的对象,相单于世深拷贝。
如下:
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
NSMutableString *str = [[NSMutableString alloc]initWithString:@"MK"];
Person *person = [[Person alloc]init];
person.name = str;
[str appendString:@" cool"];
NSLog(@"person.name = %@", person.name);
2021-11-10 19:54:11.636787+0800 copy的基本使用[16675:1905943] person.name = MK cool
在思考一个问题,可变的字符串(NSMutableString)用strong还是copy呢? 我们先用copy修饰
@interface Person : NSObject
@property (nonatomic, copy) NSMutableString *mutableStr;
@end
NSMutableString *str = [[NSMutableString alloc]initWithString:@"MK"];
Person *person = [[Person alloc]init];
person.mutableStr = str;
[person.mutableStr appendString:@" 123"];
**2021-11-10 21:28:01.543669+0800 copy的基本使用[17899:1930293] -[NSTaggedPointerString appendString:]: unrecognized selector sent to instance 0x21a639271ae3d24f**
当我们用实例中的可变的字符串追加字符的时候,程序会产生崩溃,这是因为,将可变的字符串进行copy的时候产生的是不可变的字符串,实际上person中的mutableStr是不可变的,追加字符的时候会产生崩溃,所以这里用strong比较好点,不会产生崩溃,这个时候用strong也会跟NSString有同样的问题,这里我们只能自己避免。
通过这种方式也可以,直接mutableCopy一个字符串出来赋值给person的mutableStr。
person.mutableStr = [str mutableCopy];
NSArray和NSDictonary同样也有这样的问题
总结:
- 不可变的NSString、NSArray、NSDictonary用copy好点
- 可变的NSMutableString、NSMutableArray、NSMutableDictonary用strong比较好点
copy在block中的使用
MRC中block属性修饰符
typedef void(^MyBlock)(void);
@interface Person : NSObject
@property (nonatomic, retain) MyBlock myBlock;
@end
Person *person = [[Person alloc]init];
Dog *dog = [[Dog alloc]init];
NSLog(@"retainCount = %ld",[dog retainCount]);
person.myBlock = ^{
NSLog(@"retainCount = %ld",[dog retainCount]);
NSLog(@"dog = %@",dog);
};
NSLog(@"retainCount = %ld",[dog retainCount]);
[dog release];
person.myBlock();
[person release];
**2021-11-11 08:57:57.991547+0800 copy的基本使用[26038:2068864] retainCount = 1**
**2021-11-11 08:57:57.992204+0800 copy的基本使用[26038:2068864] retainCount = 1**
**2021-11-11 08:58:37.926729+0800 copy的基本使用[26038:2068864] -[Dog dealloc]**
**2021-11-11 08:58:38.567806+0800 copy的基本使用[26038:2068864] *** -[Dog retainCount]: message sent to deallocated instance 0x100712db0**
当dog对象在使用block之前释放的时候,再次调用block的时候程序会发生崩溃,使用了已经释放的对象。
可以看出这时候的block存放在栈中。我们把修饰符改成copy
typedef void(^MyBlock)(void);
@interface Person : NSObject
@property (nonatomic, copy) MyBlock myBlock;
@end
这时候的block存放在堆中,再次调用block程序不会发生崩溃
**2021-11-11 09:08:38.115245+0800 copy的基本使用[26435:2076149] retainCount = 1**
**2021-11-11 09:08:38.116298+0800 copy的基本使用[26435:2076149] retainCount = 2**
**2021-11-11 09:09:19.682681+0800 copy的基本使用[26435:2076149] retainCount = 1**
**2021-11-11 09:09:22.305131+0800 copy的基本使用[26435:2076149] dog = <Dog: 0x10050f0a0>**
**2021-11-11 09:09:24.768995+0800 copy的基本使用[26435:2076149] -[Person dealloc]**
不过这时候dog对象好像没有释放,是因为block对其引用计数加了1,导致dog对象没有释放,我们只需要在person对象释放的时候,对block进行释放,block在释放的时候,会给被block引用的对象都发送一个release消息。
@implementation Person
- (void)dealloc {
NSLog(@"%s", __func__ );
Block_release(_myBlock);
[super dealloc];
}
@end
可以得出结论:block在栈中:不会对使用到的对象引用计数加1。而block在堆中会对使用到的对象的引用计算加1,这样防止在调用block之前对象被提前释放导致产生崩溃,所以在MRC中block最好用copy修饰,如果用retain修饰,编译器也会给出警告
ARC中block属性修饰符
那么在ARC中,我们的block用strong还是copy呢。
先用strong修饰:
typedef void(^MyBlock)(void);
@interface Person : NSObject
@property (nonatomic, strong) MyBlock myBlock;
@end
这时候的block存放在堆中,那么用copy修饰存放在哪里呢?
typedef void(^MyBlock)(void);
@interface Person : NSObject
@property (nonatomic, copy) MyBlock myBlock;
@end
用copy修饰的时候block也是存放在堆中。
之前我们需要手动的用copy属性来修饰block,让block从stack拷贝到heap,保证block在出了作用域之后也能够让block继续存在,并且以ARC的方式来决定什么时候释放在heap上的block。在2014年9月后的一次编译器优化之后,如果用strong修饰block,编译器会自动将blcok从stack拷贝到heap上。
所以只要你使用ARC,现在的Clang编译器,你可以像对待其他对象一样对待block。因为block是可变的。这意味着你不需要拷贝他们。
编译器优化了之后,不应该要求使用者将属性改为copy,使用者只要知道被strong过的block也会想普通对象一样被ARC管理就行了。
copy和block的循环引用问题
因为copy修饰的block会被拷贝到堆中,导致对象的引用计数加1,当block中又引用到本身的时候,这时候会造成循环引用的问题,这时候又该怎么解决呢
Person *person = [[Person alloc]init];
person.myBlock = ^{
NSLog(@"person = %@",person);
};
[person release];
**2021-11-11 09:32:10.484107+0800 copy的基本使用[27410:2097007] person = <Person: 0x100790ab0>**
**Program ended with exit code: 0**
这时候person对象并没有被释放,那么该如何解决呢?
这时候我们只需要在变量的前面加上__block来修饰变量,告诉编译器引用计数不要加1,只时候就可以释放
__block Person *person = [[Person alloc]init];
person.myBlock = ^{
NSLog(@"person = %@",person);
};
person.myBlock();
[person release];
**2021-11-11 09:34:03.820602+0800 copy的基本使用[27500:2099466] person = <Person: 0x100605460>**
**2021-11-11 09:34:03.825047+0800 copy的基本使用[27500:2099466] -[Person dealloc]**
在ARC中,我们通过__weak关键字修饰
Person *person = [[Person alloc]init];
__weak Person *weakPerson = person;
person.myBlock = ^{
NSLog(@"person = %@",weakPerson);
};
person.myBlock();
自定义类实现copy
自定义的类实现copy需要满足下面几个条件
- 遵循
NSCopying协议 - 实现NSCopying里面的
copyWithZone:方法
NS_ASSUME_NONNULL_BEGIN
typedef void(^MyBlock)(void);
@interface Person : NSObject <NSCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSArray *arr;
@property (nonatomic, strong) MyBlock myBlock;
@end
NS_ASSUME_NONNULL_END
- (id)copyWithZone:(NSZone *)zone {
//创建对象
id obj = [[[**self** class] allocWithZone:zone]init];
//对象赋值
[obj setName:_name];
[obj setArr:_arr];
[obj setMyBlock:_myBlock];
//返回对象
return obj;
}
子类进行自定义copy
当父类遵循了NSCopying协议,并且实现了copyWithZone:方法,子类同时也可以调用父类的copy方法,也可以重写方法,来初始化自己独有的属性
- (id)copyWithZone:(NSZone *)zone
{
//调用父类的copy方法,先初始化父类的属性
id obj = [**super** copyWithZone:zone];
//初始化自己独有的属性
[obj setAge:_age];
//返回对象
return obj;
}
总结:copy方式就是生成一个跟源对象一摸一样的新对象。而且两者不会互相影响