美文网首页iOS面试题iOS面试题
面试题分解—「浅复制/深复制、定义属性使用copy还是stron

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

作者: 简晰333 | 来源:发表于2018-09-20 00:47 被阅读47次

    引导


    关于浅复制和深复制的概念,让我感觉有点绕口,以及定义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) - 0x7ffeebf79a78、NSLog(@"%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类型的属性,修饰词也可以使用strong。
                    copy的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使用copy,copy修饰后的对象生成了新的内存地址,为深复制。对新生成的对象进行appendString:操作会发生crash,说明其实copy修饰后返回的是不可变类型。
            2.定义NSMutableString使用strong,strong修饰后对象的内存地址和原对象的内存地址相同,为浅复制,可以进行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类型的属性,指向不可变类型,copy和strong修饰均没有生成新的内存地址,只是复制了对象的指针且引用计数+1,copy修饰为浅复制;
     --------
     场景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 和 先归档再解档操作,均生成了新的内存地址,都为深复制,
     --------
     场景4:
     0x10766a230// 原数组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都是深复制
    不知名开发者

    • 各位厂友,由于「时间 & 知识」有限,总结的文章难免有「未全、不足」,该模块将系统化学习,后替换、补充文章内容 ~
    • 熬夜写者不易,学会交流和分享。

    相关文章

      网友评论

        本文标题:面试题分解—「浅复制/深复制、定义属性使用copy还是stron

        本文链接:https://www.haomeiwen.com/subject/opzwnftx.html