什麼叫做copy/mutableCopy?如何實現淺/深拷貝?

weixin_33850890發表於2016-08-08
  • 什麼叫做copy:利用原物件產生一個副本物件,(副本物件中的內容和原物件內容一致)修改原物件的屬性和行為不會影響副本物件,修改副本物件不會影響原物件。

  • 一般情況下copy/mutableCopy操作會生成一個新的物件,但是不可變物件通過copy不會生成新物件。

    • 產生新物件的原因:
      1. 修改原物件不會影響副本物件,修改副本物件不會影響原物件。
      2. 不可變物件通過mutableCopy生成可變物件,所以必須生成新物件(可變物件通過copy生成不可變物件同理)。
      3. 可變物件通過mutableCopy生成可變物件,當修改原/副本物件的時候不能相互影響所以要生成新物件。
      4. 不可變物件通過copy生成不可變物件,因為兩個物件是都不能修改的,所以不會生成新物件(oc的記憶體優化)。
    • 注意:

        ```
        [NSArray mutableCopy] -> NSMutableArray
        [NSMutableArray copy] -> NSArray
        ```
      
  • 正因為copy有時會生成新物件,有時不會生成新物件,所以就出現了淺拷貝與深拷貝

    • 淺拷貝:沒有生成新物件,本質就是指標拷貝。
      • 注意:原物件引用計數+1(相當於進行retain操作)。
    • 深拷貝:生成新物件,本質就是建立了一個新物件。
      • 注意:原物件引用計數不變。
  • 如果想令自己的類支援拷貝操作就要實現NSCopying或NSMutableCopying協議。如果自定義的物件分為可變和不可變兩個版本,就要同時實現NSCopying和NSMutableCopying協議。

  • NSCopying協議中只有一個方法(生成不可變物件)

      ```
      - (id)copyWithZone:(nullable NSZone *)zone;
      ```
    

    舉例實現:

      ```
      //Person.h
      @property (nonatomic, assign) int age;
      @property (nonatomic, copy) NSString *name;
      
      //Person.m
      - (id)copyWithZone:(NSZone *)zone
      {
          // 1.建立一個新的物件
          Person *p = [[[self class] allocWithZone:zone] init];
          // 2.設定當前物件的內容給新的物件
          p.age = _age;
          p.name = _name;
          // 3.返回新的物件
          return p;
      }
      ```
    
  • NSMutableCopying協議中只有一個方法(生成可變物件)

    ```
    - (id)mutableCopyWithZone:(nullable NSZone *)zone;
    ```
    舉例實現:
    
    ```
    //Person.h
    @property (nonatomic, assign) int age;
    @property (nonatomic, copy) NSString *name;
    
    //Person.m
    - (id)mutableCopyWithZone:(NSZone *)zone
    {
        // 1.建立一個新的物件
        Person *p = [[[self class] allocWithZone:zone] init];
        // 2.設定當前物件的內容給新的物件
        p.age = _age;
        p.name = _name;
        // 3.返回新的物件
        return p;
    }
    ```
    
  • 淺拷貝:只拷貝容器物件本身,而不復制其中的資料。

      ```
      //Person.h
      @property (nonatomic, assign) int age;
      @property (nonatomic, copy) NSString *name;
      
      //Person.m
      - (id)copyWithZone:(NSZone *)zone
      {
          // 1.建立一個新的物件
          Person *p = self;
          // 2.返回新的物件
          return p;
      }
      ```
    
  • 深拷貝:在拷貝物件自身的時候,將底層資料也複製過去。

      ```
      //Person.h
      @property (nonatomic, assign) int age;
      @property (nonatomic, copy) NSString *name;
      
      //Person.m
      - (id)deepCopy
      {
          // 1.建立一個新的物件
          Person *p = [[[self class] allocWithZone:zone] init];
          // 2.設定當前物件的內容給新的物件
          p.age = _age;
          p.name = _name;
          // 3.返回新的物件
          return p;
      }
      ```
    
  • 複製物件的時候一般執行淺拷貝。

  • 如果你的物件需要深拷貝,應該考慮專門執行深拷貝的方法。

  • 字串屬性都要使用copy。(防止外界修改內部資料)

  • 使用copy儲存block,這樣可以保住block中使用的外界物件,避免以後呼叫block的時候,物件已經被外界釋放。(Block_release(blcok),MRC中使用,釋放block,並且block中的物件也可以接收到release訊息)

    • 注意:copy block之後會產生迴圈引用(物件中的block又用到了物件自己),為了避免記憶體洩漏應該講物件修飾為__block.
  • 子類會繼承父類的協議,當父類實現了copy的方法,子類也就相應的實現了。但是如果子類有新的屬性,那麼必須在子類中重寫copyWithZone方法, 在該方法中先呼叫父類建立副本設定值, 然後再設定子類特有的值。

    • 舉例(Son為上文中Person的子類)

      Son.h
      @property (nonatomic, assign) double height;
          
      Son.m
      - (id)copyWithZone:(NSZone *)zone
      {
        // 1.建立一個新的物件
            id obj = [super copyWithZone:zone];
        // 2.設定新物件的資料
         [obj setHeight:_height];
        // 3.返回新物件
        return obj;
      }
          ```

相關文章