1、什么是深拷贝什么是浅拷贝?浅拷贝和深拷贝的区别
* 浅拷贝(shallow copy):指针拷贝,对于被拷贝对象的每一层都是指针拷贝,没有开启新的内存地址,拷贝前后的指针指向同一块内存地址。浅拷贝会影响内存地址引用计数。
* 深拷贝(one-level-deep copy):内存块拷贝,拷贝后的指针指向拷贝后的内存块。但是这里深拷贝只是深拷贝对象自身这一层,是单层深拷贝,对于容器类对象,容器内各层元素对象依然是浅拷贝。
2、copy和strong的区别
这里分两种场景,首先对于不可变对象,
@property(nonatomic, strong)NSString *strongString;
@property(nonatomic, copy)NSString *cpString;
------------------------------------------------------------------------------------------------------------
NSString *originString = [[NSString alloc] initWithFormat:@"Hello World!"];
self.strongString= originString;
self.cpString= originString;
NSLog(@" 对象地址 对象指针地址 引用计数");
NSLog(@"originString: %p , %p , %ld", originString, &originString,CFGetRetainCount((__bridge CFTypeRef)(originString)));
NSLog(@"strongString : %p , %p , %ld", _strongString, &_strongString, CFGetRetainCount((__bridge CFTypeRef)(_strongString)));
NSLog(@"cpString : %p , %p , %ld", _cpString, &_cpString, CFGetRetainCount((__bridge CFTypeRef)(_cpString)));
------打印log-------------------------------------------------------------------------------------------
对象地址 对象指针地址 引用计数
originString : 0x600001bcb1a0 , 0x7ffee876c188 , 3
strongString : 0x600001bcb1a0 , 0x7ff155208ab0 , 3
cpString : 0x600001bcb1a0 , 0x7ff155208ab8 , 3
对于不可变对象,copy对象和strong对象的内存地址都没有变化,只是生成新指针指向源对象地址,引用计数加 1 , 都是浅拷贝。
当源对象是可变对象时,使用点语法赋值:
@property(nonatomic, strong)NSArray *strongArray;
@property(nonatomic, copy)NSArray *cpArray;
--------------------------------------------------——————————————————
NSLog(@" 对象地址 对象指针地址 引用计数 index1元素地址");
NSMutableArray *originArray = [[NSMutableArray alloc] initWithArray:@[@"北京市", @[@"昌平区", @[@"天通苑"]]]];
self.strongArray= originArray;
self.cpArray= originArray;
NSLog(@"originArray : %p , %p , %ld, %p", originArray, &originArray,CFGetRetainCount((__bridge CFTypeRef)(originArray)), originArray[1]);
NSLog(@"strongArray : %p , %p , %ld, %p", _strongArray, &_strongArray, CFGetRetainCount((__bridge CFTypeRef)(_strongArray)), _strongArray[1]);
NSLog(@"cpArray : %p , %p , %ld, %p", _cpArray, &_cpArray, CFGetRetainCount((__bridge CFTypeRef)(_cpArray)), _cpArray[1]);
————————————————————————————————————
iOS_copy 对象地址 对象指针地址 引用计数 index1元素地址
originArray : 0x600002414e10 , 0x7ffee12ea180 , 2, 0x600002a28b80
strongArray : 0x600002414e10 , 0x7fa512307dd0 , 2, 0x600002a28b80
cpArray : 0x600002a28ae0 , 0x7fa512307dd8 , 1, 0x600002a28b80
对可变对象copy,新指针指向新拷贝开辟的内存块,是深拷贝,但是内部元素是浅拷贝。strong是新指针指向源内存块,引用计数加 1,是浅拷贝。
当源对象是可变对象时,直接赋值:
NSLog(@" 对象地址 对象指针地址 引用计数 index1元素地址");
NSMutableArray *originArray = [[NSMutableArray alloc] initWithArray:@[@"北京市", @[@"昌平区", @[@"天通苑"]]]];
_strongArray= originArray;
_cpArray= originArray;
NSLog(@"originArray : %p , %p , %ld, %p", originArray, &originArray,CFGetRetainCount((__bridge CFTypeRef)(originArray)), originArray[1]);
NSLog(@"strongArray : %p , %p , %ld, %p", _strongArray, &_strongArray, CFGetRetainCount((__bridge CFTypeRef)(_strongArray)), _strongArray[1]);
NSLog(@"cpArray : %p , %p , %ld, %p", _cpArray, &_cpArray, CFGetRetainCount((__bridge CFTypeRef)(_cpArray)), _cpArray[1]);
——————————————————————————————
对象地址 对象指针地址 引用计数 index1元素地址
originArray : 0x6000030b91d0 , 0x7ffeed7eb180 , 3, 0x600003e82780
strongArray : 0x6000030b91d0 , 0x7facdf707360 , 3, 0x600003e82780
cpArray : 0x6000030b91d0 , 0x7facdf707368 , 3, 0x600003e82780
看输出结果,直接赋值后对象地址相同,和上面点语法赋值出现了不同!!这是为什么呢?
用@property来声明属性变量时,编译器会自动为我们生成一个以下划线加属性名命名的实例变量(@synthesize cpArray = _cpArray),并且生成其对应的getter、setter方法。
当我们用self.cpArray = cpArray即使用点语法赋值时,才会调用cpArray的setter方法,而_cpArray = originArray 赋值时给_cpArray实例变量直接赋值,并不会调用cpArray的setter方法,而在setter方法中有一个非常关键的语句: _cpArray = [cpArray copy];
用点语法self.cpArray = originArray 赋值时,调用cpArray的setter方法,setter方法对传入的cpArray做了次深拷贝生成了一个新的对象赋值给_cpArray,所以_cpArray指向的地址和对象值都不再和originArray相同。
3、为什么不可变对象要用copy,改为strong可以吗?
不可变对象使用copy修饰可以让该对象不受传入对象的影响。外界传入的是一个不可变对象时和strong修饰差别不大;当传入的是一个可变对象时会进行深拷贝,属性指针指向新的地址,保证该对象持有的是一个不可变副本。
使用strong修饰时,如果这个属性指向一个可变对象,修改可变对象时,这个属性值也会被修改。因为在赋值时,直接将属性的指针指向了可变对象地址,内存地址共用一个。当然我们可以对一个strong对象使用 strongObj = [originObj copy] 来实现copy修饰作用,但是这样并不保险,还是直接用copy修饰不可变对象为好。
4、数组copy后里面的元素会复制一份新的吗
数组copy后里面的元素不会复制一份新的。无论是copy还是mutableCopy,数组内的元素都不会不会复制一份新的,都是浅拷贝。
使用- (instancetype)initWithArray:(NSArray *)array copyItems:(BOOL)flag函数,flag为YES时可以对数组元素进行深拷贝,复制一份新的。
使用归档解档的方法(NSArray *deepCpArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:originArray]];)实现了对数组的完全深拷贝,数组的每层元素都会复制一份新的。需要注意的是,归档解档数组内的元素需要实现NSCoding协议。
5、[object copy]是浅拷贝还是深拷贝?为什么是浅拷贝?copy是实现了哪个协议?
[object copy],object是不可变对象,是指针拷贝,新的指针指向源对象内存,是浅拷贝;object是可变对象,对源对象内存拷贝,新指针指向新地址,实现的深拷贝,但是这个深拷贝是单层深拷贝,因为object是容器类对象时,容器内元素还是浅拷贝。
copy方法实际上是调用NSCopying协议。如果没有实现copyWithZone方法会抛出异常。NSObject本身并没有支持NSCopying协议,子类必须支持此协议并且实现copyWithZone方法。子类在实现copyWithZone方法时应该先调用欺负类的copyWithZone方法以让内容复制完全,除非此类直接继承自NSObject
示例,Animal类,Dog类继承自Animal。首先需要遵守NSCopying协议,下面是copyWithZone实现。
@implementation Animal
- (id)copyWithZone:(NSZone*)zone
{
/*若该类直接继承自NSObject,则在copyWithZone:中使用allocWithZone:初始化*/
/*使用 [self class] 而非Animal是因为此方法可能由子类super调用过来的*/
Animal*animal = [[[selfclass]allocWithZone:zone]init];
animal.weight=self.weight;
returnanimal;
}
@implementation Dog
- (id)copyWithZone:(NSZone*)zone
{
/*一个子类调用copy,实际上是调用了NSCopying协议中的copyWithZone:方法
调用super的copyWithZone:确保父类的内容被拷贝*/
Dog*dog = [supercopyWithZone:zone];
dog.name=self.name;
returndog;
}
我们demo调用copy,输出对象地址和引用计数
Dog*dog = [[Dogalloc]init];
dog.name=@"Dahuang";
dog.weight=@"11K";
Dog*dog2 = [dogcopy];
NSLog(@"%p,,,,,%p", dog, dog2);
NSLog(@"%ld, %ld", CFGetRetainCount((__bridge CFTypeRef)(dog)), CFGetRetainCount((__bridge CFTypeRef)(dog2)));
-----------------------输出-------log-------------------------------------
iOS_copy[50970:4411255] 0x600000a80f00,,,,,0x600000a80ec0
2020-07-09 11K2020-07-09 15:55:52.340368+0800 iOS_copy[50970:4411255] 1, 1
我们看到copy实现的是深拷贝,当我们将Dog类中的copyWithZone中直接return self:
@implementation Dog
- (id)copyWithZone:(NSZone*)zone
{
//直接 return self; 实现的是浅拷贝
return self;
/*一个子类调用copy,实际上是调用了NSCopying协议中的copyWithZone:方法
调用super的copyWithZone:确保父类的内容被拷贝*/
/*Dog*dog = [supercopyWithZone:zone];
dog.name=self.name;
returndog;*/
}
再次运行调用demo,输出
2020-07-09 16:02:37.533212+0800 iOS_copy[51188:4415464] 0x6000026f1120,,,,,0x6000026f1120
2020-07-09 16:02:37.533417+0800 iOS_copy[51188:4415464] 2, 2
此时地址copy实现的是浅拷贝
6、对可变对象进行copy是深拷贝还是浅拷贝?
对系统可变对象进行copy是单层深拷贝,返回一个不可变对象。当这个对象是非容器类对象时copy也可以叫做完全深拷贝。当这个对象是容器类对象时copy是单层深拷贝,这里单层是指对对象本身是深拷贝(内存块拷贝),对象内的元素是浅拷贝(指针拷贝)。
7、@property (copy) NSMutableArray *array; 这种写法有什么问题?
1、虽然用了NSMutableArray声明,但是copy修饰返回的是不可变数组,在对array的元素进行增,删,改时,程序会因为找不到对应的方法而崩溃。
2、关于ARC下,不显式指定属性关键字时,默认关键字入下:
1).基本数据类型:atomic readwrite assign
2).普通OC对象: atomic readwrite strong
所以这里默认使用了 atomic 属性会严重影响性能。
8、什么是单层拷贝,怎么实现多层拷贝?
这里可以先看一下前面一篇文章iOS--拷贝 - 简书。
这里需要提到容器类对象,系统的容器类对象如NSArray,NSDictionary等。
在对不可变NSArray做copy操作后,是拷贝一个新的指针指向NSArray的地址,单纯的指针拷贝,是浅拷贝。
对可变数组NSMutableArray做copy操作后,是对源对象内存块拷贝,新指针指向新地址,但是对象内的元素只是做指针拷贝的浅拷贝,此时称为单层拷贝。
NSArray有一个- (instancetype)initWithArray:(NSArray *)array copyItems:(BOOL)flag函数,flag为YES时,副本对象首层元素也做了内存块拷贝,但是源对象第二层元素(首层元素是容器类元素的,首层元素内的元素)以及更深层的元素依然是浅拷贝,源对象自身加上首层原色的深拷贝,这个函数实现了双层深拷贝。
使用归档解档的方法(NSArray *deepCpArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:originArray]];)实现了容器类对象和对象内的每层元素的深拷贝。需要注意的是,归档解档数组内的元素需要实现NSCoding协议。
9、修饰block属性时,为什么用copy?
对于这个问题,得区分 MRC 环境 和 ARC 环境;block使用copy是从MRC遗留下来的,在MRC中,方法内部的block是在栈区的,对于分配在栈区的对象,我们很容易会在释放之后继续调用,导致程序奔溃,所以我们使用的时候需要将栈区的对象移到堆区,来延长该对象的生命周期。
对于 MRC 环境,使用 Copy 修饰 Block,会将栈区的 Block 拷贝到堆区。
对于 ARC 环境,使用 Strong、Copy 修饰 Block,都会将栈区的 Block 拷贝到堆区。
所以,Block 不是一定要用 Copy 来修饰的,在 ARC 环境下面 Strong 和 Copy 修饰效果是一样的。
10、有属性声明 @property(nonatomic, copy)NSString *obj; 重写setter方法如下:
- (void)setObj:(NSString*)obj
{
_obj= obj;
} 有什么问题吗?
有没有问题?直接上代码
@interface ViewController ()
@property(nonatomic, copy)NSString *obj;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableString *str = [[NSMutableString alloc] initWithFormat:@"Hello World!"];
self.obj= str;
//将一个可变字符串 str 赋值给了 obj,然后改变 str,打印 str 和 obj
[str insertString:@" I am coming!" atIndex: str.length];
NSLog(@"\nstr:%@\nobj:%@", str, self.obj);
}
- (void)setObj:(NSString*)obj
{
_obj= obj;
}
看一下输出:
2020-07-10 10:41:53.266417+0800 iOS_property[83857:5014465]
str:Hello World! I am coming!
obj:Hello World! I am coming!
我们将一个可变字符串 str 赋值给了 obj,然后改变 str,obj 居然 跟着 str 一样被改变了,虽然我们声明中使用了copy 修饰!!!
问题就在setObj中 我们使用了 _obj= obj; 直接赋值,直接赋值和strong没有区别,对于使用copy修饰的属性,在重写setter方法时,应该使用_obj= [obj copy]; 赋值。
构造方法也一样,需要用传入copy后的字符串赋值
如上面的例子,如果仅用 obj 来构造控制器ViewController,且写成这样:
- (instancetype)initWithObj:(NSString*)obj
{
if(self= [superinit]) {
//_obj= [obj copy]; //应该讲copy后的值 赋值给obj
_obj = obj; //这样写是有问题的
}
return self;
}
这样写也是有问题的,如果构造的时候传入的是个可变字符串,且在构造完后立马修改它,属性也会跟着改变。因为这里赋值 obj 属性用的是属性的实例变量_obj,没有调用set方法,所以必须把赋值改为_obj= [obj copy]; 。这里如果写成self.obj = obj走set方法来赋值属性本身是可以避免这种情况,但不建议,构造方法一般还是直接操作属性的实例变量_obj合适。