阅读 19

面试题分解—「浅复制/深复制、定义属性使用copy还是strong ?」

引导


关于浅复制和深复制的概念,让我感觉有点绕口,以及定义NSString是使用copy还是使用strong那?花费一天的时间,我对这模块做了概念理解和代码验证(有详细的分析过程),最后总结了这篇文章。由众-不知名开发者,原创文章。对内容有疑问可留言交流。

  1. 对以下内容验证
  2. 对isa指针简单释义一下;
  3. 浅复制和深复制的概念彻底理解;
  4. 非集合类(NSString)对象进行copy、mutableCopy操作;
  5. 面试题:为什么定义NSString要使用copy而不建议使用strong,使用strong会有什么影响❓
  6. 集合类对象(NSArray)进行copy、mutableCopy操作,单层深复制和真正深复制,集合类内部包含对象是指针复制;
  7. 面试题:定义NSArray类型的属性,把修饰词copy换成strong有什么影响❓
  8. 面试题:定义NSMutableArray类型的属性,修饰词把strong换成copy有什么影响❓

代码块不能滚动,不妨来这里阅读下

引用计数


引用计数释义

如今进入ARC的屎蛋,就无需再次输入retain或者release代码。下面很形象一示例来释义什么是对象的引用计数。

照明对比引用计数 对照明设备所做的动作 对oc对象所做的动作 引用技数 oc方法
第一个进入办公室的人 开灯 生成并持有对象 0 --- >1 alloc,new,copy,mutableCopy等方法
之后每当有人进入办公室 需要照明 持有对象 1 --- >2 retain方法
每当有人下班离开办公室 不需要照明 释放对象 2 --- > 1 release方法
最后一个人下班离开办公室 关灯 废弃对象 1 ----> 0 dealloc方法

总结: 1.在oc的对象中存有引用计数这一整数值。 2.调用allocretain方法后,引用计数值加1。 3.调用release后,引用计数值减1。 4.引用计数值为0时,调用dealloc方法废弃对象。 5.当然不管引用计数是否为0,你也可以主动在dealloc方法中废弃对象。

引用计数示例代码
说明:
    1.以下所有示例代码打印用的Log宏,打印:内存地址,指针,真实类型,引用计数,值
    #define LNLog(description,obj) NSLog(@"%@: 内存地址:%p, 指针地址:%p, 真实类型:%@, 引用计数:%lu, 值:%@", (description),(obj),&(obj),(obj).class,(unsigned long)(obj).retainCount,(obj));

    2.打印retainCount
    NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)obj));//ARC_桥接字方式
    NSLog(@"Retain count %@", [obj valueForKey:@"retainCount"]);//ARC_kvc方式
    NSLog(@"retain count = %ld",obj.retainCount);//MRC

    3.打印对象地址有两种情况
    对象指针的地址;打印方式:`NSLog(@"%p",&b) - 0x7ffeebf79a78NSLog(@"%x",&b) - ebf79a78`;
    指针指向对象的内存地址(即保存的内容);打印方式:`NSLog(@"%p",b) - 0x60400022b740`;


- (void)testSringOrMutableStringRetainCount
{
    // 不可变字符串创建几种方式
    NSString * str1 = @"PublicCoderLN";
    LNLog(@"直接复制", str1);
    NSString * str2 = [NSString stringWithString:@"PublicCoderLN"];//会有警告
    LNLog(@"WithString1", str2);
    NSString * str3 = [[NSString alloc] initWithString:@"PublicCoderLN"];
    LNLog(@"WithString2", str3);
    NSString * str4 = [NSString stringWithFormat:@"PublicCoderLN"];
    LNLog(@"WithFormat1", str4);
    NSString * str5 = [[NSString alloc] initWithFormat:@"PublicCoderLN"];
    LNLog(@"WithFormat2", str5);
    /
     打印:
     直接复制:      内存地址:0x104516290, 指针地址:0x7ffeeb6eba78, 真实类型:__NSCFConstantString, 引用计数:18446744073709551615, 值:PublicCoderLN
     WithString1: 内存地址:0x104516290, 指针地址:0x7ffeeb6eba70, 真实类型:__NSCFConstantString, 引用计数:18446744073709551615, 值:PublicCoderLN
     WithString2: 内存地址:0x104516290, 指针地址:0x7ffeeb6eba68, 真实类型:__NSCFConstantString, 引用计数:18446744073709551615, 值:PublicCoderLN
     
     WithFormat1: 内存地址:0x604000235dc0, 指针地址:0x7ffeeb6eba60, 真实类型:__NSCFString, 引用计数:1, 值:PublicCoderLN
     WithFormat2: 内存地址:0x604000237f00, 指针地址:0x7ffeeb6eba58, 真实类型:__NSCFString, 引用计数:1, 值:PublicCoderLN
     */
    
    NSMutableString * strM1 = [[NSMutableString alloc] initWithString:@"PublicCoderLN"];
    LNLog(@"strM1", strM1);
    NSMutableString * strM2 = [[NSMutableString alloc] initWithFormat:@"PublicCoderLN"];
    LNLog(@"strM2", strM2);
    /
     打印:
     strM1: 内存地址:0x6040004412c0, 指针地址:0x7ffeeb6eba50, 真实类型:__NSCFString, 引用计数:1, 值:PublicCoderLN
     strM2: 内存地址:0x6040004413b0, 指针地址:0x7ffeeb6eba48, 真实类型:__NSCFString, 引用计数:1, 值:PublicCoderLN
     */
}
复制代码

总结:

  • 1.NSString前三种创建出来的isa是NSCFConstantString,引用计数是个无限大的数,如果用int类型打印引用计数都为-1,这表示__NSCFConstantString不会被释放.
  • 2.后两种和创建其他object是一样的在堆中占有内存,引用计数为1;

注解:

  • 1.这里对isa指针简单释义一下 runtime.h

isa:是一个Class 类型的指针。可Xcode(cmd+shift+O)快捷搜索objc.h-Line38 和 runtime.h-Line55,验证查看。

  1. 每一个对象本质上都是一个类的实例。其中类定义了成员变量和成员方法的列表。对象通过对象的isa指针指向所属类。
  2. 每一个类本质上都是一个对象,类其实是元类(meteClass)的实例。元类定义了类方法的列表。类通过类的isa指针指向元类。
  3. 元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类通过isa指针最终指向的是一个根元类(root meteClass)。
  4. 根元类的isa指针指向本身,这样形成了一个封闭的内循环。
  • 2.问题:怎么判断对象copy和mutableCopy后返回的是不可变类型还是可变类型❓ 分析:看到这样一个结论,在runtime下NSString的真实类型是"__NSCFConstantString",而NSMutableString的真实类型是"__NSCFString"。通过上面对NSString和NSMutableString打印结果,根据NSString初始化方式不同,打印的对象真实类型有"__NSCFConstantString"和"__NSCFString"都存在的情况。

浅复制和深复制


相关概念释义
  1. 非集合类对象指的是NSString,NSNumber等对象。 集合类对象指的是NSArray,NSDictionary,NSSet等对象。

  2. 在Objective-C中,必须遵守 <NSCopying, NSMutableCopying>,才能通过两个方法 copy和mutableCopy可以执行拷贝操作,其中copy是获得一个不可变对象,而mutableCopy是获得一个可变对象。并且两个方法分别调用copyWithZone和mutableCopyWithZone两个方法来进行拷贝操作。如果对copy返回对象使用mutable对象接口就会crash,或者强制调用这两个方法会发生crash,如:对UILabel(继承UIView,只遵守了NSCopying协议)进行mutableCopy操作,结果会报reason: '-[UILabel mutableCopyWithZone:]: unrecognized selector sent to instance 0x7fbaecf1e880'

  3. 浅复制和深复制

  • 1.浅复制:只复制了对象的指针,指针指向的内存地址还是和源对象指针指向的内存地址一样,对象的引用计数+1;
  • 2.深复制:即复制了对象的指针,也复制了指针指向的内存地址,生成新的指针和内存地址且引用计数为+1,对源对象的引用计数没有影响;

4、图解复制概念

非集合类对象的copy与mutableCopy

定义NSString类型的属性(copy/strong修饰词)指向不可变对象
@property (nonatomic, copy) NSString * stringCopy;
@property (nonatomic, strong) NSString * stringStrong;

- (void)testStringUseRetainOrCopyOrMutableCopy
{
    NSString * string = [NSString stringWithFormat:@"publicCoderLN"];
    LNLog(@"originString", string);
   
    NSLog(@"--------");
    // 场景1:定义NSString类型的属性(copy/strong修饰词)指向不可变对象.
    self.stringCopy = string;
    LNLog(@"_stringCopy", _stringCopy);
    
    self.stringStrong = string;
    LNLog(@"_stringStrong", _stringStrong);
    
    NSLog(@"--------");
    // 场景2:对不可变类型NSString,进行retain、copy、mutableCopy操作
    NSString * strRetain1 = [string retain];
    LNLog(@"strRetain1", strRetain1);
    
    NSString * strCopy1 = [string copy];
    LNLog(@"strCopy1", strCopy1);
    
    NSString * strMCopy1 = [string mutableCopy];
    LNLog(@"strMCopy1", strMCopy1);
 
    /
     打印:
     originString:  内存地址:0x60400003e320, 指针地址:0x7ffee5e6ba78, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
     _stringCopy:   内存地址:0x60400003e320, 指针地址:0x7fa7bd609770, 真实类型:__NSCFString, 引用计数:2, 值:publicCoderLN
     _stringStrong: 内存地址:0x60400003e320, 指针地址:0x7fa7bd609778, 真实类型:__NSCFString, 引用计数:3, 值:publicCoderLN
     --------
     strRetain1:    内存地址:0x60400003e320, 指针地址:0x7ffee5e6ba70, 真实类型:__NSCFString, 引用计数:4, 值:publicCoderLN
     strCopy1:      内存地址:0x60400003e320, 指针地址:0x7ffee5e6ba68, 真实类型:__NSCFString, 引用计数:5, 值:publicCoderLN
     strMCopy1:     内存地址:0x600000251e50, 指针地址:0x7ffee5e6ba60, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
     */
}
复制代码

分析: 1.从打印结果可以看出,copy、strong、retain均使对象的retainCount +1; 2.copy不可变对象,只是复制了对象的指针,和原对象string指针地址不同(文中其实做了2次copy),但指针指向对象的内存地址还是同一个,所以是浅复制; 3.mutableCopy不可变对象,即复制了对象的指针,也复制了指针指向对象的内存地址,生成了新的指针和新的内存地址,所以是深复制; 4.提醒:这里的显示地址不是不变的,地址是系统分配的,可能你代码验证的时候就不是这里显示的地址了,但概念是对的;

    // 场景3:定义NSString类型的属性,修饰词用把copy换成strong后,再修改原对象有什么影响❓
    string = [string substringToIndex:3];
    LNLog(@"originString", string);
    LNLog(@"_stringCopy", _stringCopy);
    LNLog(@"_stringStrong", _stringStrong);
    LNLog(@"strRetain1", strRetain1);
    LNLog(@"strCopy1", strCopy1);
    LNLog(@"strMCopy1", strMCopy1);

    /
     打印:
     originString:  内存地址:0xa000000006275703, 指针地址:0x7ffee5e6ba78, 真实类型:NSTaggedPointerString, 引用计数:18446744073709551615, 值:pub【改原对象】
     _stringCopy:   内存地址:0x60400003e320, 指针地址:0x7fa7bd609770, 真实类型:__NSCFString, 引用计数:5, 值:publicCoderLN
     _stringStrong: 内存地址:0x60400003e320, 指针地址:0x7fa7bd609778, 真实类型:__NSCFString, 引用计数:5, 值:publicCoderLN
     - - -
     strRetain1:    内存地址:0x60400003e320, 指针地址:0x7ffee5e6ba70, 真实类型:__NSCFString, 引用计数:5, 值:publicCoderLN
     strCopy1:      内存地址:0x60400003e320, 指针地址:0x7ffee5e6ba68, 真实类型:__NSCFString, 引用计数:5, 值:publicCoderLN
     strMCopy1:     内存地址:0x600000251e50, 指针地址:0x7ffee5e6ba60, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
     
     分析:
        1、原对象string的类型本身就是不可变类型,再对其进行操作,系统自动分配了新的内存地址(只是对象的指针和没有修改前原对象的指针一样),这里对原对象string操作后生成的内存地址和其他类型(copy/strong/retain)都不一样,所以只有原对象string操作后改变。
        2、【提醒】:有厂友这时候可能会说,从上面修改前和修改后的打印结果来看,我定义NSString使用copy修饰和使用strong,内存地址和值都是一样的,那我为什么不使用strong修饰NSString那❓
            分析:一些情况下从性能考虑定义不可变NSString类型的属性,修饰词也可以使用strongcopy的set方法内部会对传入的字符串进行判断是不可变的还是可变的,如果是不可变字符串,就直接给属性进行赋值,不会生成新的内存地址;
                如果是可变字符串对string属性赋值,会进行Copy操作,重新生成一份新的内存地址。
                这里会对copy修饰的string会进行判断,几个不会影响性能,但是如果很多就会有了吧。
                开发中很多都是定死的NSString不可变字符串的,使用strong也可以。
                // copy的set方法
                - (void)setStringCopy:(NSString *)stringCopy
                {
                    _stringCopy = [stringCopy copy];
                }
      */
复制代码

注解: 上面打印出现了NSTaggedPointerString类型,想更多了解的可参考采用Tagged Pointer的字符串。 简单点说,在字符串小于长度12且为immutable对象时,Apple会让其共用同一地址来节省内存的开销。但中如果出现了中文,则会另外开辟新的内存空间进行存储。

总结:对于不可变对象
1、retain/strong/copy,都会使原对象的引用计数+1,指针指向对象的内存地址和原对象一样; 2、copy操作为浅复制,没有生成新的内存地址;mutableCopy操作为深复制,生成了新的内存地址,新生成的对象引用计数为1; 3、修改原对象string的值,对copy和strong修饰定义的属性目标对象_stringCopy 没有影响,还是原来的值(内存地址和值都是原来的);

定义NSString类型的属性(copy/strong修饰词)指向可变对象
@property (nonatomic, copy) NSMutableString * stringMCopy;
@property (nonatomic, strong) NSMutableString * stringMStrong;

- (void)testMStringUseRetainOrCopyOrMutableCopy
{
    NSMutableString * stringM = [NSMutableString stringWithFormat:@"%@", @"publicCoderLN"];
    LNLog(@"originString", stringM);

    NSLog(@"--------");
    // 场景1:定义NSString类型的属性(copy/strong修饰词)指向可变对象.
    self.stringCopy = stringM;
    LNLog(@"_stringCopy", _stringCopy);
    
    self.stringStrong = stringM;
    LNLog(@"_stringStrong", _stringStrong);
    
    NSLog(@"--------");
    // 场景2:对可变类型NSMutableString,进行retain、copy、mutableCopy操作
    NSMutableString * stringMRetain = [stringM retain];
    LNLog(@"stringMRetain", stringMRetain);
    
    NSMutableString * stringMCopy = [stringM copy];
    LNLog(@"stringMCopy", stringMCopy);
    
    NSMutableString * stringMMutableCopy = [stringM mutableCopy];
    LNLog(@"stringMMutableCopy", stringMMutableCopy);
    
    /
     打印:
     originString:       内存地址:0x604000245cd0, 指针地址:0x7ffee1d38a78, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
     _stringCopy:        内存地址:0x604000236da0, 指针地址:0x7f94d4603980, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
     _stringStrong:      内存地址:0x604000245cd0, 指针地址:0x7f94d4603988, 真实类型:__NSCFString, 引用计数:2, 值:publicCoderLN
     --------
     stringMRetain:      内存地址:0x604000245cd0, 指针地址:0x7ffee1d38a70, 真实类型:__NSCFString, 引用计数:3, 值:publicCoderLN
     stringMCopy:        内存地址:0x60000003b080, 指针地址:0x7ffee1d38a68, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
     stringMMutableCopy: 内存地址:0x600000456860, 指针地址:0x7ffee1d38a60, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
     --------
     分析:
        1、定义NSString类型的属性(copy/strong修饰词)指向可变对象时,可以看到copy修饰的生成了新的内存地址。
        2、对可变对象stringM进行操作,retain操作引用计数+1,而copy/mutableCopy操作均产生了新指针和新的指针指向的内存地址,都为深复制,。
     */
  
    NSLog(@"--------");
    // 场景4:定义NSString类型的属性,修饰词用把copy换成strong后,再修改原对象有什么影响❓
    [stringM appendString:@"+CoderLN"];
    LNLog(@"originString", stringM);
    LNLog(@"_stringCopy", _stringCopy);
    LNLog(@"_stringStrong", _stringStrong);
    LNLog(@"stringMRetain", stringMRetain);
    LNLog(@"stringMCopy", stringMCopy);
    LNLog(@"stringMMutableCopy", stringMMutableCopy);
  
    /
     打印:
     originString:       内存地址:0x604000245cd0, 指针地址:0x7ffee1d38a78, 真实类型:__NSCFString, 引用计数:3, 值:publicCoderLN+CoderLN【改原对象】
     _stringCopy:        内存地址:0x604000236da0, 指针地址:0x7f94d4603980, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
     _stringStrong:      内存地址:0x604000245cd0, 指针地址:0x7f94d4603988, 真实类型:__NSCFString, 引用计数:3, 值:publicCoderLN+CoderLN
     - - -
     stringMRetain:      内存地址:0x604000245cd0, 指针地址:0x7ffee1d38a70, 真实类型:__NSCFString, 引用计数:3, 值:publicCoderLN+CoderLN
     stringMCopy:        内存地址:0x60000003b080, 指针地址:0x7ffee1d38a68, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
     stringMMutableCopy: 内存地址:0x600000456860, 指针地址:0x7ffee1d38a60, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
     
     分析:
        1.上面copy修饰的指向可变对象,生成了新的内存地址,而strong修饰的和原对象stringM的内存地址一样,所以原对象的修改,strong修饰的也被改变了。
        2.NSMutableString属性进行copy、mutableCopy操作,均生成了新的内存地址(指向另一个对象),都为深复制,所以原对象改变不会对他们有影响;
     */
}
复制代码

面试题为什么定义NSString要使用copy而不建议使用strong❓ 分析:将对象声明为NSString不可变类型时,都不希望它改变(外界修改了,不影响自身的值),从上面打印结果可以看出,strong修饰的NSString类型属性遇到赋值可变类型时,修改原对象的值,_stringStrong也同样改变了,而copy修饰的string的值没有改变。 回答定义NSString使用copy,为了防止遇到把一个可变字符串在未使用copy方法时赋值给这个字符串对象后,再修改原字符串时(可变字符),本字符串也会被修改的情况发生(就用strong的情况)

总结:对可变对象 1、strong、retain会使原对象的引用计数+1,指针指向的内存地址和原对象还是一样的;copy、mutableCopy对原对象的引用计数没有影响,使新生成的对象的引用计数为1; 2、进行操作copy、mutableCopy均为深复制,即复制了对象的指针,也复制了指针指向的内存地址;

定义NSMutableString类型的属性,如果把修饰词strong换成copy有什么影响。
@property (nonatomic, copy) NSMutableString * stringMCopy;
@property (nonatomic, strong) NSMutableString * stringMStrong;

- (void)testMutableStringCopyOrStrong
{
    NSMutableString *stringM = [[NSMutableString alloc] initWithFormat:@"%@",@"publicCoderLN"];
    LNLog(@"stringM", stringM);
    self.stringMCopy = stringM;
    LNLog(@"_stringMCopy", _stringMCopy);
    self.stringMStrong = stringM;
    LNLog(@"_stringMStrong", _stringMStrong);
    
    // 面试题:定义NSMutableString类型的属性,如果把修饰词strong换成copy有什么影响❓
    //[self.stringMCopy appendString:@"+CoderLN"];// 会crash
    //[self.stringMStrong appendString:@"+CoderLN"];// strong修饰的正常运行.
    //[[self.stringMCopy mutableCopy] appendString:@"+CoderLN"];// 返回的可变类型.
    
    /
     打印:
        stringM:        内存地址:0x60400025f2f0, 指针地址:0x7ffee4502a78, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
        _stringMCopy:   内存地址:0x60000022bda0, 指针地址:0x7f7f90525de0, 真实类型:__NSCFString, 引用计数:1, 值:publicCoderLN
        _stringMStrong: 内存地址:0x60400025f2f0, 指针地址:0x7f7f90525de8, 真实类型:__NSCFString, 引用计数:2, 值:publicCoderLN
     
     总结:
        1.定义NSMutableString使用copycopy修饰后的对象生成了新的内存地址,为深复制。对新生成的对象进行appendString:操作会发生crash,说明其实copy修饰后返回的是不可变类型。
        2.定义NSMutableString使用strongstrong修饰后对象的内存地址和原对象的内存地址相同,为浅复制,可以进行appendString:操作,说明其实strong修饰后返回的还是是可变类型。
        3.回答:定义NSMutableString使用修饰词strong换成copy,如果对copy后的不可变类型,再进行可变的修改操作,就会造成崩溃。
     */
}
复制代码

集合类对象的copy与mutableCopy

定义NSArray类型的属性(copy/strong修饰词)指向不可变对象
@property (nonatomic ,copy) NSArray *arrayCopy;
@property (nonatomic ,strong) NSArray *arrayStrong;

- (void)testArrayUseCopyOrMutableCopy
{
    NSArray *array = @[@"A", @"B"];
    LNLog(@"originAry", array);
    
    NSLog(@"--------");
    // 场景1:定义NSArray类型的属性(copy|strong修饰)指向不可变对象
    self.arrayCopy = array;
    LNLog(@"_arrayCopy", _arrayCopy);
    self.arrayStrong = array;
    LNLog(@"_arrayStrong", _arrayStrong);

    NSLog(@"--------");
    // 场景2:对不可变类型数组,进行copy|mutableCopy操作
    NSArray * arrayCopy = [array copy];// 浅
    LNLog(@"arrayCopy", arrayCopy);

    NSMutableArray * arrayMCopy = [array mutableCopy];// 深
    LNLog(@"arrayMCopy", arrayMCopy);
    
    NSLog(@"--------");
    // 场景3:对数组不可变类型,进行浅深复制
    NSArray * arrayCopy1 = [array copyWithZone:nil];// 浅
    LNLog(@"arrayCopy1", arrayCopy1);
    
    NSArray * arrayCopy2 = [[NSArray alloc] initWithArray:array copyItems:NO];// 浅
    LNLog(@"arrayCopy2", arrayCopy2);
    NSArray * arrayCopy3 = [[NSArray alloc] initWithArray:array copyItems:YES];// 单层深复制
    LNLog(@"arrayCopy3", arrayCopy3);
    
    // 真正的深复制:先归档再解档
    NSArray * arrayCopy4 = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:array]];
    LNLog(@"arrayCopy4", arrayCopy4);
    
    NSLog(@"--------");
    // 场景4:验证数组中的元素均为指针复制
    NSLog(@"%p",array[0]);
    NSLog(@"%p",arrayCopy[0]);
    NSLog(@"%p",arrayMCopy[0]);
    
    NSLog(@"--------");
    // 面试题:定义NSArray类型的属性,把修饰词copy换成strong有什么影响❓
    // 指向不可变对象,外界修改原对象array
    array = [array arrayByAddingObject:@"Public-CoderLN"];
    LNLog(@"originArray", array);
    LNLog(@"_arrayCopy", _arrayCopy);
    LNLog(@"_arrayStrong", _arrayStrong);
    
    NSLog(@"--------");
    // 场景6:定义NSArray类型的属性(copy|strong修饰词)指向可变对象
    NSMutableArray * arrayM = [[NSMutableArray alloc] initWithArray:@[@"a",@"b"]];
    self.arrayCopy = arrayM;
    self.arrayStrong = arrayM;
    
    // 指向可变对象,外界修改原对象arrayM
    [arrayM removeAllObjects];
    LNLog(@"originArrayM", arrayM);
    LNLog(@"_arrayCopy", _arrayCopy);
    LNLog(@"_arrayStrong", _arrayStrong);
}
复制代码
/
 打印:
 
 originAry:   内存地址:0x604000037f00, 指针地址:0x7ffee8599a50, 真实类型:__NSArrayI, 引用计数:1, 值:(A,B)
 --------
 场景1:
 _arrayCopy:   内存地址:0x604000037f00, 指针地址:0x7fa41771edf0, 真实类型:__NSArrayI, 引用计数:2, 值:(A,B)
 _arrayStrong: 内存地址:0x604000037f00, 指针地址:0x7fa41771edf8, 真实类型:__NSArrayI, 引用计数:3, 值:(A,B)
 
 分析:定义NSArray类型的属性,指向不可变类型,copystrong修饰均没有生成新的内存地址,只是复制了对象的指针且引用计数+1copy修饰为浅复制;
 --------
 场景2:
 arrayCopy: 内存地址:0x604000037f00, 指针地址:0x7ffee8599a48, 真实类型:__NSArrayI, 引用计数:4, 值:(A,B)
 rrayMCopy: 内存地址:0x60000044c090, 指针地址:0x7ffee8599a40, 真实类型:__NSArrayM, 引用计数:1, 值:(A,B)
 
 分析:对原对象array进行Copy操作,没有生成新的内存地址且引用计数+1,为浅复制;进行mutableCopy操作,生成了新的内存地址,所以为深复制。
 --------
 场景3:
 arrayCopy1: 内存地址:0x604000037f00, 指针地址:0x7ffee8599a38, 真实类型:__NSArrayI, 引用计数:5, 值:(A,B)
 arrayCopy2: 内存地址:0x604000037f00, 指针地址:0x7ffee8599a30, 真实类型:__NSArrayI, 引用计数:6, 值:(A,B)
 arrayCopy3: 内存地址:0x6000004200c0, 指针地址:0x7ffee8599a28, 真实类型:__NSArrayI, 引用计数:1, 值:(A,B)
 arrayCopy4: 内存地址:0x604000038060, 指针地址:0x7ffee8599a20, 真实类型:__NSArrayI, 引用计数:1, 值:(A,B)
 
 分析:对原对象array进行 copyWithZone:和initWithArray:copyItems:参数为NO 没有生成新的内存地址,都为浅复制,
      对原对象array进行 initWithArray:copyItems:参数为YES 和 先归档再解档操作,均生成了新的内存地址,都为深复制,
 --------
 场景40x10766a230// 原数组array[0]
 0x10766a230// arrayCopy[0]
 0x10766a230// arrayMCopy[0]
 
 分析:打印数组中的元素内存地址都是同一个,不管是对原数组做copy/mutableCopy操作,数组中的元素均为浅复制。
 --------
【面试题】:定义NSArray类型的属性,把修饰词copy换成strong有什么影响❓
 //指向不可变对象,外界修改原对象array
 originArray:   内存地址:0x60000044c060, 指针地址:0x7ffee8599a50, 真实类型:__NSArrayI, 引用计数:1, 值:(A,B,"Public-CoderLN")【改原对象】
 _arrayCopy:    内存地址:0x604000037f00, 指针地址:0x7fa41771edf0, 真实类型:__NSArrayI, 引用计数:6, 值:(A,B)
 _arrayStrong:  内存地址:0x604000037f00, 指针地址:0x7f9395d0a6f8, 真实类型:__NSArrayI, 引用计数:6, 值:(A,B)
 --------
 //指向可变对象,外界修改原对象arrayM
 originArrayM:  内存地址:0x60000044c1b0, 指针地址:0x7ffeea6c7a18, 真实类型:__NSArrayM, 引用计数:2, 值:( )
 _arrayCopy:    内存地址:0x600000420160, 指针地址:0x7fa41771edf0, 真实类型:__NSArrayI, 引用计数:1, 值:(a,b)
 _arrayStrong:  内存地址:0x60000044c1b0, 指针地址:0x7fa41771edf8, 真实类型:__NSArrayM, 引用计数:2, 值:( )
 */
复制代码

面试题:定义NSArray类型的属性,把修饰词copy换成strong有什么影响❓ 分析: 1、指向不可变对象,外界修改原对象array,这里原对象本身为不可变对象,对它进行修改,系统又重新分配了内存地址与_arrayCopy的内存地址不一样,所以只有原对象改变。 2、指向可变对象,外界修改原对象arrayM,这里原对象本身为可变对象,对它进行修改,由于copy修饰做了深复制生成了新的内存地址,而strong修饰内存地址和原对象arrayM相同,所以原对象改变,strong修饰的也跟着改变了。 回答:定义NSArray使用copy,为了防止遇到把一个可变数组在未使用copy方法时赋值给这个对象后,再修改原数组时,这个对象也会被修改的情况发生(就如strong的情况会被修改)。

定义NSMutableArray类型的属性(copy/strong修饰词)指向可变对象

@property (nonatomic ,copy) NSMutableArray *arrayMCopy;
@property (nonatomic ,strong) NSMutableArray *arrayMStrong;

- (void)testMutableArrayUseCopyOrMutableCopy
{
    NSMutableArray * arrayM = [NSMutableArray arrayWithArray:@[@"A",@"B"]];
    LNLog(@"originAry", arrayM);

    // 场景1:给定义NSMutableArray类型的属性(copy/strong修饰词)赋值可变对象
    self.arrayMCopy = arrayM;
    LNLog(@"_arrayCopy", _arrayCopy);

    self.arrayMStrong = arrayM;
    LNLog(@"_arrayMStrong", _arrayMStrong);

    NSLog(@"--------");
    // 场景2:对可变类型数组,进行copy、mutableCopy操作
    NSArray * arrayC1 = [arrayM copy];
    LNLog(@"arrayC1", arrayC1);
    NSArray * arrayC2 = [arrayM mutableCopy];
    LNLog(@"arrayC2", arrayC2);
    
    NSMutableArray * arrayM1 = [arrayM copy];
    LNLog(@"arrayM1", arrayM1);
    NSMutableArray * arrayM2 = [arrayM mutableCopy];
    LNLog(@"arrayM2", arrayM2);
    
    NSLog(@"--------");
    // 场景3:NSCopying和NSMutableCopying
    NSMutableArray * shallowCopyArrayM = [arrayM mutableCopyWithZone:nil];// 浅复制
    LNLog(@"shallowCopyArrayM", shallowCopyArrayM);
    
    NSMutableArray * deepCopyArrayM1 = [[NSMutableArray alloc] initWithArray:arrayM copyItems:NO];
    LNLog(@"deepCopyArrayM1", deepCopyArrayM1);
    NSMutableArray * deepCopyArrayM2 = [[NSMutableArray alloc] initWithArray:arrayM copyItems:YES];
    LNLog(@"deepCopyArrayM2", deepCopyArrayM2);

    // 面试题:定义NSMutableArray类型的属性,修饰词把strong换成copy有什么影响❓
    //[self.arrayMCopy removeLastObject];// 会crash
    [self.arrayMStrong removeLastObject];// 正常运行

}

/
 打印:
 
 originAry:     内存地址:0x604000253740, 指针地址:0x7ffee591da60, 真实类型:__NSArrayM, 引用计数:1, 值:(A,B)
 _arrayCopy:    内存地址:0x0, 指针地址:0x7fa029f2b400, 真实类型:(null), 引用计数:0, 值:(null)
 _arrayMStrong: 内存地址:0x604000253740, 指针地址:0x7fa029f2b418, 真实类型:__NSArrayM, 引用计数:2, 值:(A,B)
 --------
 arrayC1: 内存地址:0x60000022d780, 指针地址:0x7ffee591da58, 真实类型:__NSArrayI, 引用计数:1, 值:(A,B)
 arrayC2: 内存地址:0x6000004573d0, 指针地址:0x7ffee591da50, 真实类型:__NSArrayM, 引用计数:1, 值:(A,B)
 arrayM1: 内存地址:0x60000022d680, 指针地址:0x7ffee591da48, 真实类型:__NSArrayI, 引用计数:1, 值:(A,B)
 arrayM2: 内存地址:0x600000457520, 指针地址:0x7ffee591da40, 真实类型:__NSArrayM, 引用计数:1, 值:(A,B)
 --------
 shallowCopyArrayM: 内存地址:0x604000253a70, 指针地址:0x7ffee591da38, 真实类型:__NSArrayM, 引用计数:1, 值:(A,B)
 deepCopyArrayM1:   内存地址:0x604000253d10, 指针地址:0x7ffee591da30, 真实类型:__NSArrayM, 引用计数:1, 值:(A,B)
 deepCopyArrayM2:   内存地址:0x600000457430, 指针地址:0x7ffee591da28, 真实类型:__NSArrayM, 引用计数:1, 值:(A,B)
 
 分析:
    1.NSMutableArray类型的属性,copy和mutableCopy操作都是深复制。
  */
复制代码

面试题:定义NSMutableArray类型的属性,修饰词把strong换成copy有什么影响❓ 回答:定义NSMutableArray类型的属性,修饰词用把strong换成copy,遇到可变对象赋值,再对_arrayMCopy做可变操作会崩溃,因为copy后返回的是不可变类型。reason: '-[__NSArrayI removeLastObject]: unrecognized selector sent to instance 0x600000426f00'

整体总结一下:不管是非集合类还是集合类

浅复制深复制示例.png

  • 对于不可变对象,使用copy是浅复制(指向不可变对象为浅复制;指向可变对象时生成了新的内存地址,为深复制),使用mutableCopy是深复制
  • 对于可变对象,不管使用copy或者mutableCopy都是深复制

Reading


  • 各位厂友,由于「时间 & 知识」有限,总结的文章难免有「未全、不足」,该模块将系统化学习,后替换、补充文章内容 ~
  • 熬夜写者不易,不知名开发者 学会交流和分享。
关注下面的标签,发现更多相似文章
评论