高效編寫代碼的方法(十八):NSCopying協議

在編寫程序的過程中,我們經常會想將某些對象進行復制用。在OC中,這樣的復制通過copy方法實現,比如[someObject copy]。這樣做的必要條件是該對象遵守NSCopying協議。

對于我們自定義的類,當我們想調用copy進行復制的時候,就需要自己來實現NSCopying協議的方法:

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

NSZone

還是先理解下NSZone這個東西,在實際開發中我們幾乎不會運用它。
目前我也只在2個方法中見過其作為參數存在:

+ (id)allocWithZone:(nullable NSZone *)zone;

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

NSZone其實并不是一個對象,是一個C結構體,用來記錄內存管理的一些信息。
我覺得可以理解為一片內存空間,在App運行的時候,系統默認分配一塊Zone來管理我們這個App的所有內存中的對象,因此我們在開發中幾乎不會去接觸NSZone。
但是隨著對象的不停alloc 和dealloc ,內存會變的碎片化,iOS為了解決這個問題,會將新對象分配到這些碎片的空隙中來進行內存填補,一般來說對象比較少的時候,這樣也不會造成系統性能的虧損。但是對象一多,就不一定了。
由此引出一個問題,我們能不能專門創建一個空間,來保存一系列大量的對象,減少系統尋找“內存空隙”的時間來提高一些性能,答案是可以的。這時候NSZone就可以派上用場,把你的對象一起分配在一個自定的NSZone里面,手動管理起來就會方便很多。
NSZone的釋放還不會用,估計還要切換成MRC,實在不會就不講了。

讓你的對象支持Copy

為了讓你的對象支持Copy,只要兩個步驟:

  • 1 聲明對象遵守<NSCopying> protocol
  • 2 重寫- (id)copyWithZone:(nullable NSZone *)zone;方法

對于簡單的情況,我們可以如下實現:
Person.h :

#import <Foundation/Foundation.h>

@interface Person : NSObject<NSCopying>

@property (nonatomic, copy) NSString *firstName;

@property (nonatomic, copy) NSString *lastName;

- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName;

@end

Person.m :

#import "Person.h"

@implementation Person

- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName
{
    if (self = [super init]) {
        _firstName = [firstName copy];
        _lastName = [lastName copy];
    }
    return self;
}

- (id)copyWithZone:(NSZone *)zone
{
    //直接調用init重新創建一個一模一樣的對象
    return [[[self class] alloc] initWithFirstName:_firstName lastName:_lastName];
}
@end

當然以上部分只適用于簡單對象的copy處理。對于復雜一點的,比如我們的person包含一個數組名為family的數組屬性,這時候copyWithZone:方法又該如何重寫呢?如下

- (id)copyWithZone:(NSZone *)zone
{
    Person *selfCopy = [[[self class] alloc] initWithFirstName:_firstName lastName:_lastName];
    //對特殊屬性特殊Copy處理。
    [selfCopy.family = self.family mutableCopy];
    return selfCopy;
}

OC中的拷貝

mutableCopy和copy

舉個例子好了:

[NSMutableArray copy] -> NSArray
[NSArray mutableCopy] ->NSMutableArray
深淺拷貝

網絡上說明很多,簡單來說就是是否是拷貝指針還是內存空間的重新分配。
區別在于,舉個例子:如果淺拷貝,那么原本對象容器中的數據改變,那么雖然副本容器地址不會變,但是容器中的對象也隨之改變了。深拷貝則不會改變。
所以具體使用哪種還是從實際情況出發。

對于對象的深淺拷貝,還是總結一下:

  • 1非容器類(NSString,NSNumber)
    直接放結論:
對象是不可變的對象:  mutableCopy ->深拷貝 -> 可變對象
                            copy ->淺拷貝 -> 不可變對象
對象是可變對象: mutableCopy ->深拷貝 -> 可變對象
                            copy ->深拷貝 -> 不可變對象
  • 2 容器類(NSArray,NSDictionary)
    對于容器類的本身,也就是對于NSArray對象來說,規則和上面的是一樣的,關鍵是容器內的對象。
NSString *str1 = @"string1";
NSArray *array = @[str1];
NSArray *copyArray = [array copy];
NSLog(@"===========\n array:%p,  copyArray:%p\n  str1:%p  copyArray[0]:%p",array,copyArray,str1,copyArray[0]);

輸出如下:

===========
 array:0x7fa90b415ed0,  copyArray:0x7fa90b415ed0**
  str1:0x10b5a1250  copyArray[0]:0x10b5a1250**
  NSString *str1 = @"string1";
  NSArray *array = @[str1];
  //注意下面這行代碼的改變
  NSMutableArray *copyArray = [array mutableCopy];
  NSLog(@"===========\n array:%p,  copyArray:%p\n  str1:%p  copyArray[0]:%p",array,copyArray,str1,copyArray[0]);

輸出如下:

===========
array:0x7fa37b70ef00,  copyArray:0x7fa37b758060**
str1:0x10834a250  copyArray[0]:0x10834a250**

由此可見,對于容器中的對象,mutableCopy和copy都是淺復制(可變容器也一樣)。

如果涉及到容器中包含容器,那么還有深復制和完全深復制之說,完全深復制就是容器中每一層對象都是深復制,而深復制就是只復制一層的對象。如何使用都是看具體情況,不展開了。

對于任何遵守NSCopying的對象來說,實現的都應該是淺復制,因為淺復制使用是最多的。

總結

  • 1 想讓你的對象支持復制的話,遵守NSCopying協議并實現copyWithZone:方法。
  • 2 如果你的對象支持不可變和可變兩種分類,那么需要實現mutableCopy和copy兩種方法
  • 3 區分深淺拷貝并看情況使用
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容