iOS--拷贝相关题

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合适。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354