美文网首页iOS底层原理整理
iOS内存管理(3)-MRC、Copy

iOS内存管理(3)-MRC、Copy

作者: 周灬 | 来源:发表于2019-08-13 13:50 被阅读0次

    1. MRC

    ObjC中的内存管理机制跟C语言中指针的内容是同样重要的,要开发一个程序并不难,但是优秀的程序则更测重于内存管理,它们往往占用内存更少,运行更加流畅。虽然在新版Xcode引入了ARC,但是很多时候它并不能完全解决你的问题。在Xcode中关闭ARC:项目属性—Build Settings--搜索“garbage”找到Objective-C Automatic Reference Counting设置为No即可。

    MRC的理解:
    ①. 在iOS中,使用引用计数来管理OC对象的内存.
    ②. 一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间.
    ③. 调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
    ④. 内存管理的经验总结.
         当调用allocnewcopymutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它.
         想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1.
    ⑤. 可以通过以下私有函数来查看自动释放池的情况:extern void_objc_autoreleasePoolPrint(void);.

    如果在MRC的环境下,有如下代码NSString *string = @"abc",当你对string做release操作的时候可能会得到retaincount的值为-1,原因是这时的string是一个Tagged Pointer的对象.

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (int i = 0; i <100; i++) {
    dispatch_async(queue, ^{
    self.name = [NSString stringWithFormat:@"asdfghjklzxcv"];
    });
    }
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for (int i = 0; i <100; i++) {
    dispatch_async(queue, ^{
    self.name = [NSString stringWithFormat:@"asd"];
    });
    }
    上边两段代码执行后会有什么问题?
    第一个执行后会崩溃,因为是正常的OC对象,而不是tagged pointer,所以

    • (void)setName:(NSString *)name {
      if(_name != name){
      [_name release];
      _name = [name retrain];
      }
      }
      由于是异步线程原来的name通过release释放,又有一个线程来释放name,但是现在name的引用计数为0,不能再次释放,所以会崩溃。
      第二个属于tagged pointer,就不会发生这样的问题。

    2. copy

    提到copy,就会想到关键字copy,mutableCopy和深拷贝,浅拷贝等名词,但是这些名词都有是什么,都在什么时候使用,都有什么作用呢?
    copy的目的是产生一个副本,但是这个副本不与源对象产生任何影响.iOS为此提供了两个方法copy和mutableCopy.

    copy和mutableCopy.

    copy:拷贝的结果是一个不可变(imutable)的对象, 无论源对象是可变的还是不可变的,copy之后的都是不可变的类型.

    不可变类型 变量名 = [不可变类型 copy];
    不可变类型 变量名 = [可变类型 copy];

    mutableCopy:可变拷贝的结果的数据类型是一个可变的对象,无论源对象时不可变的还是可变的,可变拷贝之后的数据类型都是可变类型.

    可变类型 变量名 = [不可变类型 mutableCopy];
    可变类型 变量名 = [可变类型 mutableCopy];

    copy与mutableCopy的使用

    一.非集合类对象(NSString,NSMutableString,NSData,NSNumber...)的copy 和 mutableCopy
    NSString *str1 = @"imutable";                                
    NSString *Str2 = [str1 copy];                           
    NSMutableString *Str3 = [str1 mutableCopy];   
    NSMutableString *str4 = [[NSMutableString alloc]initWithString:@"mutable"];
    NSMutableString *str5 = [str4 copy];
    NSMutableString *str6 = [str4 mutableCopy];  
    [str6 appendFormat:@"hello"];
    [str5 appendFormat:@"hello"];   // crash
    
    二. 集合类对象(NSArray,NSDictionary,NSSet...)的copy 和 mutableCopy
    NSArray *array0 = @[@"a",@"b",@"c"];
    NSArray *array1 = [array0 copy];
    NSArray *array2 = [array0 mutableCopy];
    NSMutableArray *array3 = [[NSMutableArray alloc]initWithObjects:@"a",@"b",@"c", nil];
    NSMutableArray *array4 = [array3 copy];
    NSMutableArray *array5 = [array3 mutableCopy];
    

    总结

    • 对系统非容器类不可变对象调用Copy方法其实只是把当前对象的指针指向了原对象的地址。
    • 调用mutableCopy方法则是新分配了一块内存区域并把新对象的指针指向了这块区域。
    • 对于可变对象来说调用Copy和MutableCopy方法都会重新分配一块内存。
    • copy和mutableCopy的区别在于copy在复制对象的时候其实是返回了一个不可变对象,因此当调用方法改变对象的时候会崩溃。
    三. @property中copy关键字

    当我们使用一个copy关键字声明一个对象的时候, 调用 set 方法的时候,copy关键字会为对象自动copy一个副本,举个例子:

    @property (nonatomic, copy) NSArray *array;
    - (void)setArray:(NSArray *)array {
    _array = [array copy];  //这里为array  copy 了一个副本
    }
    

    如果我们直接用strong关键字的话,又是怎样的呢?

    @property (nonatomic, strong) NSArray *array;
    - (void)setArray:(NSArray *)array {
    //他们指向了同一块内存空间,如果此时传入的array是一个NSMutableArray的话,
    //self.array可能会在不知情的情况下被修改。这种情况下面还会再说到
    _array = array;  
    }
    

    为什么用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字?使用strong关键字,会有什么问题?
    在.h定义一个以 strong 修饰的 array:@property (nonatomic , strong) NSArray*array;
    在.m中实现

    NSMutableArray *muArray = [[NSMutableArray alloc] init]; //0x0000000170253290
    self.array = muArray;// self.array  0x0000000170253290 self.array和muArray指向同一块内存地址
    [muArray addObject:@1];// muArray log(1)
    NSLog(@"%@",self.array);// self.array log(1)
    [muArray removeAllObjects];// muArray log()
    NSLog(@"%@",self.array);// self.array log()
    //由上面的结果可以看出,因为self.array和muArray指向同一块内存地址,所以对muArray的操作,会直接影响到self.array
    NSArray *array = @[@1,@2,@3,@4];
    [muArray addObjectsFromArray:array];//muArray 0x0000000170253290
    self.array = muArray.copy;//这里进行了深拷贝,self.array 0x00000001702532f0
    NSLog(@"%@",self.array);// self.array log(1,2,3,4)
    [muArray removeAllObjects];// muArray log()
    NSLog(@"%@",self.array);// self.array log(1,2,3,4)
    

    1.因为父类指针可以指向子类对象(如上面的NSArray对象可以指向一个NSMutableArray对象),使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.
    2.如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性.

    上面解释了为什么用@property声明不可变对象(NSString、NSArray,NSDictionary)时,经常用copy关键字,接下来我们来解释为什么要用strong关键字来声明可变对象(NSMutableString、NSMutableArray、NSMutableDictionary),而不用copy对象?
    假如我们用copy关键字在.h里来声明一个NSMutableArray对象:@property (nonatomic, copy) NSMutableArray *mutableArray;.
    .m实现方法

    NSMutableArray *array1 = [NSMutableArray arrayWithObjects:@1,@2,nil];
    self.mutableArray = array1;
    [self.mutableArray removeObjectAtIndex:0]; //crash
    

    上面执行到 removeObjectAtIndex 会crash,原因是 mutableArray 是用copy关键字声明的,copy返回的是一个不可变对象,也就是NSMutableArray会变成NSArray,然后你再执行removeObjectAtIndex方法,就会报找不到这个方法而crash.

    四. 单层拷贝和完全拷贝
     NSMutableString * str1 =  [NSMutableString stringWithString:@"Bian"] ;
    NSMutableString * str2 = [NSMutableString stringWithString:@"Sun"] ;
    NSMutableArray * mutableArr = [[NSMutableArray alloc] initWithObjects:str1,str2, nil];
    NSMutableArray * copyMutableArr = [mutableArr mutableCopy];
    
    NSLog(@"mutableArr:%p %p %p",mutableArr,mutableArr[0],mutableArr[1]);
    NSLog(@"copyMutableArr:%p %p %p",copyMutableArr,copyMutableArr[0],copyMutableArr[1]);
     // 修改str1的值
     [str1 insertString:@"abc" atIndex:0];
    NSLog(@"mutableArr:%p %p %p",mutableArr,mutableArr[0],mutableArr[1]);
    NSLog(@"copyMutableArr:%p %p %p",copyMutableArr,copyMutableArr[0],copyMutableArr[1]);
    NSLog(@"%@",copyMutableArr[0]);
    /** 打印结果:
    mutableArr:0x600003ddaa90 0x600003ddaa30 0x600003dda9d0
    copyMutableArr:0x600003ddaac0 0x600003ddaa30 0x600003dda9d0
    mutableArr:0x600003ddaa90 0x600003ddaa30 0x600003dda9d0
    copyMutableArr:0x600003ddaac0 0x600003ddaa30 0x600003dda9d0
    copyMutableArr的str1的值:abcBian
    */
    

    单层拷贝:单层深拷贝是指集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制,mutableArr的深拷贝copyMutableArr开辟了新的内存,但是里面值得内存地址还和mutableArr共享一份地址,明显就是指针拷贝,所以说这不是完全意义上的深拷贝,叫单层深拷贝!

    //只需这样创建深拷贝,就是完全深拷贝
     NSMutableArray * copyMutableArr = [[NSMutableArray alloc] initWithArray:mutableArr copyItems:YES];
    

    完全复制:完全复制是指集合对象的内容复制不仅限于对象本身,对象元素也是要复制

    五. 深拷贝和浅拷贝

    说道copy和mutableCopy就不得不说的是深拷贝和浅拷贝.

    1. 深拷贝:内容拷贝,产生新的对象.源对象的引用计数器+1.
    2. 浅拷贝:指针拷贝,不产生新的对象. 源对象的引用计数器不变.
      但是我们如何判断copy是深拷贝还是浅拷贝呢?其实我们主要判断是通过是深拷贝还是浅拷贝就看不拷贝是否对原来的对象的值产生影响.如果有影响就是深拷贝,如果没有影响就是浅拷贝.
      我们可以总结一下:
    copy mutableCopy
    NSString NSString(浅拷贝) NSMutableString(深拷贝)
    NSMutableString NSString(深拷贝) NSMutableString(深拷贝)
    NSArray NSArray(浅拷贝) NSMutableArray(深拷贝)
    NSMutableArray NSArray(深拷贝) NSMutableArray(深拷贝)
    NSDictionary NSDictionary(浅拷贝) NSMutableDictionary(深拷贝)
    NSMutableDictionary NSDictionary(深拷贝) NSMutableDictionary(深拷贝)

    @property(copy,notomic)NSMutableArray *data;会有什么问题
    在set方法会变成
    -(void)setData:(NSMutableArray *)data{
    if(_data != data){
    [_data release];
    _data = [data copy];
    {
    }
    所以这里的可变对象在经过set方法后会变成不可变对象,可变数据的方法使用会报错。

    六. 自定义对象

    在iOS中并不是所有对象都支持Copy和MutableCopy,遵循NSCopying协议的类可以发送Copy协议,遵循NSMutableCopying协议的类可以发送MutableCopy消息。如果一个对象没有遵循这两个协议而发送Copy或者MutableCopy消息那么会发生异常。如果要遵循NSCopying协议,那么必须实现copyWithZone方法。
    如果要遵循NSMutableCopying协议那么必须实现mutableCopyWithZone方法。
    对于自定义对象来说调用Copy和MutableCopy方法都会重新分配一块内存。

    //  Man.h
    #import <Foundation/Foundation.h>
    
    @interface Man : NSObject<NSCopying,NSMutableCopying>
    @property (nonatomic,strong)NSString *name;
    @property (nonatomic,assign)NSInteger year;
    @end
    
    //  Man.m
    #import "Man.h"
    
    @implementation Man
    #pragma mark description方法内部不能打印self,不然会造成死循环
    - (NSString *)description {
        return [NSString stringWithFormat:@"[name = %@,year = %ld]", _name,_year];
    }
    //自定义深拷贝,实现copyWithZone方法
    -(id)copyWithZone:(NSZone *)zone{
        Man *newMan = [[[self class] allocWithZone:zone] init];
        newMan.name = self.name;
        newMan.year = self.year;
        return newMan;
    }
    
    -(id)mutableCopyWithZone:(NSZone *)zone{
        Man *newMan = [[[self class] allocWithZone:zone] init];
        newMan.name = self.name;
        newMan.year = self.year;
        return newMan;
    }
    @end
    
    //调用
        Man *man = [[Man alloc]init];
        man.name = @"张三";
        man.year = 1;
        Man *newMan = [man copy];
        Man *newMutMan = [man mutableCopy];
        NSLog(@"man = %@,man地址 = %p,newMan = %@,newMan地址 = %p,newMutMan = %@, newMutMan地址 =  %p",man,man,newMan,newMan,newMutMan,newMutMan);
        /**
         man = [name = 张三,year = 1],
         man地址 = 0x604000036900,
         newMan = [name = 张三,year = 1],
         newMan地址 = 0x6040004207e0,
         newMutMan = [name = 张三,year = 1],
         newMutMan地址 =  0x60400003c2a0
         */
        newMan.name = @"李四";
        NSLog(@"man = %@,man地址 = %p,newMan = %@,newMan地址 = %p,newMutMan = %@, newMutMan地址 =  %p",man,man,newMan,newMan,newMutMan,newMutMan);
        /**
         man = [name = 张三,year = 1],
         man地址 = 0x604000036900,
         newMan = [name = 李四,year = 1],
         newMan地址 = 0x6040004207e0,
         newMutMan = [name = 张三,year = 1],
         newMutMan地址 =  0x60400003c2a0
         */
        newMutMan.name = @"王五";
        NSLog(@"man = %@,man地址 = %p,newMan = %@,newMan地址 = %p,newMutMan = %@, newMutMan地址 =  %p",man,man,newMan,newMan,newMutMan,newMutMan);
        /**
         man = [name = 张三,year = 1],
         man地址 = 0x604000036900,
         newMan = [name = 李四,year = 1],
         newMan地址 = 0x6040004207e0,
         newMutMan = [name = 王五,year = 1],
         newMutMan地址 =  0x60400003c2a0
         */
    
                                想了解更多iOS学习知识请联系:QQ(814299221)

    相关文章

      网友评论

        本文标题:iOS内存管理(3)-MRC、Copy

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