OC语法补充之-Copy与MutableCopy

概念

  1. 拷贝的特点:
    1. 生成一个跟原对象一样的副本
    2. 修改副本并不会影响原对象,修改原对象也不会影响副本对象
  2. iOS开发中提供了两种拷贝的方法
    1. copy: 只会产生不可变的副本对象(如: NSString)
    2. MutableCopy:只会产生可变的副本对象(如: NSMutableNSString)
  3. 拷贝的深度分为两种
    1. 深拷贝: 产生新对象的拷贝
    2. 浅拷贝: 没有产生新对象的拷贝
  4. iOS中的拷贝原则都遵从第1条的2个特点.

深拷贝与浅拷贝

  1. 代码举例:

     //不可变对象的拷贝
     - (void)test {
         NSString *string = @"hhhh";
         //没有产生新对象(浅拷贝)
         NSString *str1 = [string copy];
         //产生新对象(深copy)
         NSMutableString *str2 = [string mutableCopy];
         NSLog(@"string====%p===str1===%p==str2==%p",string,str1,str2);
         /*
          打印结果
     string====0x1038fa0c0===str1===0x1038fa0c0==str2==0x60400005fcb0
          */
     }
        
     //可变对象的拷贝
     - (void)test2 {
         NSMutableString *string = [NSMutableString string];
         [string appendString:@"hhhh"];
         //产生新对象(深copy)
         NSString *str1 = [string copy];
         //产生新对象(深copy)
         NSMutableString *str2 = [string mutableCopy];
         NSLog(@"string====%p===str1===%p==str2==%p",string,str1,str2);
         /*
          打印结果如下:
          string====0x60400005fb90===str1===0x10bdcc030==str2==0x60400005fbc0
          */
     }
    
    
  2. 根据test与test2总结:
    1. 当原对象是可变的,无论是copy还是mutablecopy,都会产生新对象,深拷贝.
    2. 当原对象是不可变的,mutablecopy会产生新对象(深拷贝),copy不会产生新对象(浅拷贝)
  3. 内存分配如下:
    1. copy在MRC下的内存管理:
      1. 如果是浅拷贝:不会生成新的对象,但是系统就会对原来的对象进行retain,所以需要对原来的对象进行一次

      2. 如果是深拷贝:会生成新的对象,系统不会对原来的对象进行retain,但是因为生成了新的对象,所以我们需要对新的对象进行release

    2. 下图

    图1

  4. 总结如下: 图1

如何让一个对象具有copy功能

  1. 通常NSString/NSArray/NSDictionary等都遵守了NSCopying协议,并且实现了相应的方法,所以能使用copy
  2. NSObject对象没有遵守,因此NSObject要实现copy,那就必须遵守,跟实现一些条件
  3. 一个对象要想能够调用copy方法,应遵守copy的条件:
    1. 遵守NSCopying协议
    2. 实现copyWithZone:(NSZone *)zone方法
  4. NSObject对象没有必要去实现mutableCopy,因为不存在可变这个概念
  5. 当对象调用了copy方法时,内部就会调用copyWithZone:(NSZone *)zone,如果没有实现,那么就会错:

     -[Person copyWithZone:]: unrecognized selector sent to instance ...
    
  6. 代码举例:

     #import <Foundation/Foundation.h>
    
     @interface Person : NSObject<NSCopying>
     @property (nonatomic,assign) NSInteger age;
     //@property (nonatomic,strong) NSString *name;
     @property (nonatomic,copy) NSString *name;
     @end
        
     #import "Person.h"
    
     @implementation Person
     -(id)copyWithZone:(NSZone *)zone{
         Person *p = [[Person allocWithZone:zone]init];
         p.age = self.age;
         p.name = self.name;
         return self;
     }
     //strong中set方法
     //-(void)setName:(NSString *)name{
     //    _name = name;
     //}
     //copy中set方法与strong的不同
     -(void)setName:(NSString *)name{
         _name = [name copy];
     }
     @end
        
     //应用
    //如何让一个对象实现copy功能
     - (void)test3 {
         Person *p1 = [[Person alloc] init];
         p1.age = 5;
         NSLog(@"p1的对象%p===age = %zd",p1,p1.age);
         Person *p2 = [p1 copy];
         //改变P2并不会影响p1
         p2.age = 10;
         NSLog(@"p2的对象%p===age = %zd",p2,p2.age);
         /*
          打印结果:
          p1的对象0x6040000267a0===age = 5
          p2的对象0x6040000267a0===age = 10
          */
     }
    

copy用于@property中的作用

  1. 代码举例:

     //copy用于@property中的作用
     - (void)test4 {
         NSMutableString *sttt = [NSMutableString stringWithFormat:@"gggg"];
         Person *p1 = [[Person alloc] init];
         p1.age = 5;
         p1.name = sttt;
         [sttt appendString:@"xxxxx"];
         NSLog(@" sttt当前内容为:%@====p1.name= %@",sttt,p1.name);
     }
    
     //此时如果name用的是strong修饰,set方法内部是直接赋值,p1.name直接指向了sttt这个对象,如果改变了sttt,p1.name也会跟着改变.
     //打印结果:  sttt当前内容为:ggggxxxxx====p1.name= ggggxxxxx
     //但是name如果用的是copy修饰,set方法内部进行了copy,如果改变了sttt,p1.name不变.
     //打印结果:  sttt当前内容为:ggggxxxxx====p1.name= gggg
    
  2. 什么时间用strong?什么时间用copy呢?

    1. strong: 对象的属性希望跟着外边的内容变化而变化
    2. copy: 对象的属性不受外边的影响,具有保护对象属性值的作用.

copy用于block中

  1. block默认存储在栈中, 栈中的block访问到了外界的对象, 不会对对象进行retain
  2. block如果在堆中, 如果在block中访问了外界的对象, 会对外界的对象进行一次retain
  3. 代码举例:

     Person *p = [[Person alloc] init];
      //1
     NSLog(@"retainCount = %lu", [p retainCount]);
     void (^myBlock)() = ^{
         NSLog(@"%@", p);
         NSLog(@"retainCount = %lu", [p retainCount]);
     };
      //1
      myBlock();
      // 将block转移到堆中
     Block_copy(myBlock);
      //2
     myBlock();
    
  4. @property中使用copy作用:
    1. 并不是拷贝, 而是转移,将这个block从栈转移到堆中.
    2. 防止外界对象提前销毁,出现野指针错误.
    3. 代码举例

       //Person对象
       #import <Foundation/Foundation.h>
       typedef void (^myBlock)();
       @property (nonatomic, copy) NSString *name;
       // 注意: 如果是block使用copy:并不是拷贝, 而是转移,将这个block从栈转移到堆中.
       @property (nonatomic, copy) myBlock pBlock;
       //@property (nonatomic,assign) myBlock pBlock;
       @end
              
       #import "Person.h"
       @implementation Person
       - (void)dealloc
       {
           // 只要给block发送一条release消息, block中使用到的对象也会收到该消息
           Block_release(_pBlock);
           NSLog(@"%s", __func__);
           [super dealloc];
       }
       @end
              
              
       //使用
          
       // 避免以后调用block的时候, 外界的对象已经释放了,因此出现野指针错误
       Dog *d = [[Dog alloc] init]; // 1
       NSLog(@"retainCount = %lu", [d retainCount]);
       Person *p = [[Person alloc] init];
       p.pBlock = ^{
           //由于pBlock用的是copy修饰,因此这个block在堆中了,所以会对block中的对象+1 = 2
           NSLog(@"%@", d);
       };
       NSLog(@"retainCount = %lu", [d retainCount]); // 2
          
       // 如果狗在调用block之前释放了, 那么程序就会崩溃(如果block没有用copy修饰而是用的assign,那么d就是1了,此时d就会释放了.)
       [d release]; // 1
       p.pBlock();
       [p release];
      

copy修饰block引发循环引用

  1. block引发循环引用的因素:
    1. 一个对象拥有一个block属性,然而这个block内部却又直接或间接使用了这个对象或者对象的属性.
  2. block的循环引用分为2种:
    1. 第一种: self(控制器)有一个block属性,而在block中却又使用了self的属性
      1. 解决办法: __weak typeof(self) weakSelf = self;
    2. 第二种: 如果不是self呢? 是一个P对象有一个block属性,而在block中却又使用了P的属性呢?
      1. 解决办法: 创建这个对象时用__block修饰,此时的作用是当block内部使用了自己时,不让block对自己retain(计数器加1)
      2. 代码举例:

         //如果对象中的block又用到了对象自己, 那么为了避免内存泄露, 应该将对象修饰为__block
         __block Person *p = [[Person alloc] init]; // 1
         p.name = @"lnj";
         NSLog(@"retainCount = %lu", [p retainCount]);
         p.pBlock = ^{
             NSLog(@"name = %@", p.name); // 1
         };
         NSLog(@"retainCount = %lu", [p retainCount]);
         p.pBlock();
                    
         [p release]; // 0
         //    [p release]; // 2B
        

面试题

  1. @property (nonatomic,copy) NSMutableString *name;这句话合适吗?
    1. 不合适,因为属性name的真实类型是不可变的即NSString类型
    2. 该类的set方法为:

       -(void)setName:(NSMutableString *)name{
        _name = [name copy];
        }
      
    3. 由于copy方法返回的是一个不可变对象,然而_name却是NSMutableString类型修饰!!!
    4. 但是如果用strong修饰没有问题,nameNSMutableString
  2. 那些地方用到copy? 为什么?
    1. NSString用到
      1. 对象的属性不受外边的影响,具有保护对象属性值的作用.
    2. block用到了copy
      1. copy的作用不是复制,而是将这个block从栈转移到堆中.
      2. 栈中的block不会对其内部使用的对象自动计数器+1,但是堆中的block会.
      3. 防止外界对象(block内部使用外界的对象)提前销毁,出现野指针错误.
Table of Contents