美文网首页内存
iOS进阶专项分析(六)、OC内存管理之深拷贝与浅拷贝

iOS进阶专项分析(六)、OC内存管理之深拷贝与浅拷贝

作者: 溪浣双鲤 | 来源:发表于2020-06-22 22:32 被阅读0次

    先来看两个经典的面试题:

    1、属性NSString为什么要用copy修饰?而不是用strong?
    2、NSArrayNSMutableArraycopy修饰还是strong?

    带着这两个问题我们开始本篇的内容:

    • 1、案例1:非容器类对象(例如NSString)的深拷贝与浅拷贝
    • 2、案例2:容器类对象(例如NSArrayNSMutableArray)的深拷贝与浅拷贝
    • 3、OC内存管理:深拷贝与浅拷贝知识点
    • 4、项目中实际用法的经验总结

    一、案例1:非容器类对象(例如NSString)的深拷贝与浅拷贝


    测试方法:

    用可变字符串NSMutableString给这NSString字符串属性赋值,赋值完成后,故意修改了这个可变字符串,看看strongcopy这两种关键字修饰的属性是否变化

    新建工程实现如下代码

    #import "ViewController.h"
    @interface ViewController ()
    
    @property (nonatomic, strong)NSString * testStrongString;//浅拷贝
    @property (nonatomic, copy)NSString * testCopyString;//深拷贝
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        
        //1、字符串验证
        [self conformTestString];
    }
    
    - (void)conformTestString {
        //用可变字符串分别给这两个属性赋值,然后修改可变字符串的值
        
        NSMutableString * tempMutableString = [[NSMutableString alloc] initWithString:@"初始值"];
        self.testStrongString = tempMutableString;
        self.testCopyString = tempMutableString;
        NSLog(@"初始\n浅拷贝的地址:%p\n浅拷贝的值:%@", self.testStrongString, self.testStrongString);
        NSLog(@"初始\n深拷贝的地址:%p\n深拷贝的值:%@", self.testCopyString, self.testCopyString);
        
        //修改可变字符串
        [tempMutableString setString:@"修改后的值"];
        NSLog(@"修改后\n浅拷贝字符串的地址:%p\n浅拷贝字符串的值:%@", self.testStrongString, self.testStrongString);
        NSLog(@"修改后\n深拷贝字符串的地址:%p\n深拷贝字符串的值:%@", self.testCopyString, self.testCopyString);
    }
    
    @end
    
    

    运行上面的代码,打印台:

    2020-06-22 10:33:47.098767+0800 TestProject strong&copy[1287:51282] 初始
    浅拷贝的地址:0x6000036a4630
    浅拷贝的值:1
    2020-06-22 10:33:47.099390+0800 TestProject strong&copy[1287:51282] 初始
    深拷贝的地址:0x60000389fc00
    深拷贝的值:1
    
    
    2020-06-22 10:33:47.099660+0800 TestProject strong&copy[1287:51282] 修改后
    浅拷贝字符串的地址:0x6000036a4630
    浅拷贝字符串的值:9999999
    2020-06-22 10:33:47.099885+0800 TestProject strong&copy[1287:51282] 修改后
    深拷贝字符串的地址:0x60000389fc00
    深拷贝字符串的值:1
    
    

    对比这两种不同关键字修饰的测试字符串,我们发现了一个可怕的结果:

    在我们没有直接对属性进行重新赋值的情况下,发现用strong修饰的字符串的值做了变化!!!而copy修饰的属性值则没有变化

    这也就意味着我们用strong修饰的这个属性的值是随动的!而copy修饰的属性只要我们不重新赋值,值就是固定不变的

    针对这个案例以及文章前第一个问题,做出回答如下:
    问题:

    1、属性NSString为什么要用copy修饰?而不是用strong?

    Copy是为了安全 ,防止 NSMutableString 赋值给 NSString 时,前者修改引起后者值变化而使用的。

    下面继续来探讨第二个问题:

    案例2:容器类对象(例如NSArray,NSMutableArray)的深拷贝与浅拷贝


    1、容器类对象(例如NSArray,NSMutableArray)作为属性时,应该是用strong还是copy?可变容器对象与不可变容器对象在使用上是否有区别?

    1.1 针对不可变数组NSArray作为属性时,应该用copy还是strong?
    测试方法:

    用可变数组NSMutableArrayNSArray容器属性赋值,赋值完成后,故意移除数组中的元素,看看strongcopy这两种关键字修饰的NSArray属性是否发生变化

    代码如下:

    声明两个属性

    @property (nonatomic, strong)NSArray * tempStrongArray;
    @property (nonatomic, copy)NSArray * tempCopyArray;
    
    

    viewDidLoad中调用

    //用可变数组分别给这两个属性赋值,然后移除可变数组中的元素
    - (void)testArray {
        NSMutableArray * aArray = [NSMutableArray arrayWithArray:@[@"1",@"2",@"3",@"4",@"5"]];
        
        self.tempStrongArray = aArray;
        self.tempCopyArray = aArray;
        
        NSLog(@"初始\n浅拷贝的地址:%p\n浅拷贝的成员数量:%ld", self.tempStrongArray, self.tempStrongArray.count);
        NSLog(@"初始\n深拷贝的地址:%p\n浅拷贝的成员数量:%ld", self.tempCopyArray, self.tempCopyArray.count);
        
        [aArray removeLastObject];
        NSLog(@"初始\n浅拷贝的地址:%p\n浅拷贝的成员数量:%ld", self.tempStrongArray, self.tempStrongArray.count);
        NSLog(@"初始\n深拷贝的地址:%p\n浅拷贝的成员数量:%ld", self.tempCopyArray, self.tempCopyArray.count);
        
    }
    
    

    打印结果

    2020-06-22 21:53:08.093548+0800 Test strong&copy[3829:284299] 初始
    浅拷贝的地址:0x600002b0a4f0
    浅拷贝的成员数量:5
    2020-06-22 21:53:08.093857+0800 Teststrong&copy[3829:284299] 初始
    深拷贝的地址:0x60000301cbc0
    浅拷贝的成员数量:5
    2020-06-22 21:53:08.094072+0800 Teststrong&copy[3829:284299] 初始
    浅拷贝的地址:0x600002b0a4f0
    浅拷贝的成员数量:4
    2020-06-22 21:53:08.094252+0800 Teststrong&copy[3829:284299] 初始
    深拷贝的地址:0x60000301cbc0
    浅拷贝的成员数量:5
    
    

    我们发现了,修饰不可变数组NSArray其实和NSString类似,如果用strong修饰,也会发生变化,所以NSArray类型的属性,也是必须要用copy进行修饰的。

    结论:

    1.1 针对不可变数组NSArray作为属性时,应该用copy还是strong?

    和NSString一样,用Copy是为了安全,防止 NSMutableArray 赋值给 NSArray 时,前者修改引起后者值变化而使用的。

    1.2 针对可变数组NSMutableArray作为属性时,应该用copy还是strong?

    先来看下面这段代码

    先声明个属性

    @property (nonatomic, copy)NSMutableArray * mutableArray;
    
    

    viewDidLoad中调用

    - (void)testMutableArray {
        NSMutableArray * bArray = [NSMutableArray arrayWithArray:@[@"1",@"2",@"3",@"4",@"5"]];
        
        self.mutableArray = bArray;
        
        [self.mutableArray removeAllObjects];
    }
    
    

    运行一下,发现崩了

    2020-06-22 22:19:41.360899+0800 深拷贝与浅拷贝strong&copy[4007:303276] -[__NSArrayI removeAllObjects]: unrecognized selector sent to instance 0x600000631880
    
    

    发现我们的可变数组变成了不可变数组!

    其实上面这串代码

    @interface ViewController ()
    
    @property (nonatomic, copy)NSMutableArray * mutableArray;
    
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        
        NSMutableArray * bArray = [NSMutableArray arrayWithArray:@[@"1",@"2",@"3",@"4",@"5"]];
        
        self.mutableArray = bArray;
    }
    
    
    

    等同于

    @interface ViewController ()
    
    @property (nonatomic, strong)NSMutableArray * mutableArray;
    
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        
        NSMutableArray * bArray = [NSMutableArray arrayWithArray:@[@"1",@"2",@"3",@"4",@"5"]];
        
        self.mutableArray = [bArray copy];
    }
    
    

    所以最后进行移除元素操作的时候,等同于不可变数组NSArray调用移除元素removeAllObjects的方法,所以会报错方法找不到。所以用copy修饰可变数组,等同于该数组是不可变数组,所以是行不通的。用strong则没问题,使用可变数组的目的就是要对同一片内存数据进行操作,所以浅拷贝就行了

    结论:

    1.2 针对可变数组NSMutableArray作为属性时,应该用copy还是strong?

    应该用strong,用copy修饰NSMutableArray,就是相当于对NSMutableArray进行copy处理,得到的是不可变数组。如果继续来调用可变数组专用的API,会Crash并提示方法找不到

    三、深拷贝与浅拷贝知识点总结


    我们知道OC内存管理的方式采用的是引用计数机制,其他语言如java使用的是垃圾回收机制,对比其他机制,引用计数机制的好处和缺点如下:

    优点:一旦没有引用,内存直接释放,处理内存的时间分摊到了平时,不像其他机制需要等待到特定时机,内存管理非常高效。

    缺点:维护引用计数会消耗资源,而且会造循环引用问题!循环引用会导致应用程序退出前,这块内存一直存在,始终无法释放!同时会引起应用程序运行期间内存暴涨,更甚者内存飙升的非常厉害,当内存过高会直接被系统杀掉,也就是造成应用程序闪退crash!

    ARC下,我们是不可以对对象调用retain,release方法修改内存的引用计数的,我们必须理解MRCretaincopy以及mutableCopy的特点:

    retain
    始终是浅拷贝,让新对象指针指向原对象,只是原来的内存地址多了一个指针指向,引用计数增加了1(但是系统会在底层进行各种优化,不一定会加,像常量的引用计数就一直保保持-1,不会变动,所以对常量string进行retain也还是不会变)。返回对象是否可变与被复制的对象保持一致。(MRC下的retain等同于ARC下的strong)

    copy
    对于可变对象为深拷贝(开辟新内存,与原对象指向的不是同一个对象了);
    对于不可变对象是浅拷贝(不开辟新内存,只是原内存地址加了一个新的指针指向,引用计数+1)。返回的对象始终是一个不可变的对象。

    mutableCopy:
    始终是深拷贝(开辟新内存,与原来对象指向的内存空间不是同一处)。返回的对象始终是一个可变对象。

    Objective-C中对象的拷贝不光分为深拷贝和浅拷贝,另外还有容器类对象及非容器类对象的差别:

    1、对于非容器类对象(如NSStringNSMUtableString类对象)使用浅拷贝:拷贝的就是对象的地址,没有分配新的内存空间,只是原来的那块内存区域多了一个指针指向。也就是说 新对象与原对象都是指向同一个内存地址,那么内容当然一样。

    2、对于非容器类对象(NSStringNSMutableString类对象)使用深拷贝:拷贝的是整个对象的内容,通过给新对象分配了一块新的内存空间,然后把原对象对应内存中的值一模一样的在新的内存空间再写一份,所以内容是一样的,但是此时新对象和原对象的内存地址是不同的。

    3、对容器类对象(NSArrayNSMutableArray类对象)使用浅拷贝:新的容器类对象也是指向新的内存地址,但是容器内保存的对象没有进行拷贝,指向的内存地址还是和原容器对象内保存的对象指向的内存一样,也就是说你改了其中一个容器对象中的元素对象,那么另一个容器对象中的元素对象也会做相应的修改(因为容器对象内其中包含的元素是同一个内存地址)

    对容器类对象(NSArrayNSMutableArray类对象)使用深拷贝:需要对容器类对象中的每一个元素都进行拷贝

    四、项目中实际用法的经验总结


    1. 在给类添加NSString类型的属性时,要使用copy关键字修饰; 不然可能导致你保存的这个属性值可能会随动,作为一个合格的iOS开发者一定要避免出现这种低级bug,保证代码健壮度!
    2. 添加NSArray类型的属性时,要使用copy关键字修饰; 不然也可能导致你保存的这个属性值可能会随动;而添加NSMutableArray类型的属性时,要使用strong关键字修饰, 因为用copy修饰NSMutableArray,就是相当于对NSMutableArray进行copy处理,得到的是不可变数组。
    3. 容器类属性(例如NSArrayNSMutableArray类属性)在使用时,需要注意NSArray要用copy关键字修饰,NSMutableArray只能用strong修饰(其他容器类似NSArrayNSDictionaryNSSetcopy,而NSMutableArrayNSMutableDictionaryNSMutableSetstrong

    溪浣双鲤的技术摸爬滚打之路

    相关文章

      网友评论

        本文标题:iOS进阶专项分析(六)、OC内存管理之深拷贝与浅拷贝

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