iOS關於Copy和mutableCopy方法的淺析

數組爲例,來記錄一下CopymutableCopy的使用細節。

我們知道可變數組和不可變數組之間的轉化可以通過下面的方式:

[NSMutableArray copy] --> NSArray
[NSArray mutableCopy] --> NSMutableArray

我們也知道: 

(圖片來自網絡)

那麼當我們調用數組的mutableCopy方法時,是否將數組中所包含的對應也深拷貝了呢?

testClass* class0 = [testClass new];
class0.name = @(1).stringValue;
testClass* class1 = [testClass new];
class1.name = @(11).stringValue;
testClass* class2 = [testClass new];
class2.name = @(111).stringValue;

NSArray* arr = @[class0,class1,class2];
NSMutableArray* arr2 = [arr mutableCopy];
NSArray* arr3 = [arr copy];

看一下結果:

可以看到不論是調用數組的copy還是mutableCopy,數組中的對象指針仍然是指向了最初創建的對象(三個數組中對應對象的地址完全相同)。

經過mutableCopy的數組,生成了一個新的數組arr2,其內存地址和arr不同,是深拷貝。

經過copy的數組,只是生成了一個新的數組對象指針,指向了原始的arr對象,是淺拷貝。

所以可以得出一個結論:對數組進行copy或mutableCopy操作只是對數組這個容器對象進行了深/淺拷貝,而不會對數組中的對象進行操作,最後拷貝完得到的數組中的對象仍舊是指向之前的地址。

這樣就會導致,我們在修改經過深拷貝之後的數組中的對象中屬性時,同時會影響到原始數組中的對象,達不到想要的結果。

我們在上面testClass對象中遵守<NSMutableCopying,NSCopying>兩個協議,並實現協議方法:

-(id)mutableCopyWithZone:(NSZone *)zone{
    testClass* test = [[self class]allocWithZone:zone];
    test.name = _name;
    return test;
}
-(id)copyWithZone:(NSZone *)zone{
    testClass* test = [[self class]allocWithZone:zone];
    test.name = _name;
    return test;
}

然後在這兩個地方打個斷點,可以發現,在包含該對象的數組進行mutableCopy深拷貝的時候,該對象的copy協議方法並未被調用,這也進一步證實了上面的結論。

 

1.那麼我們想要得到一個數組內對象也經過深拷貝的數組要如何操作呢?

首先當然是在自定義的對象中遵守<NSMutableCopying,NSCopying>並實現其協議方法。

然後需要調用:

- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag;
//其他容器對象 如字典集合等也都有這樣類似的方法

當copyItems參數爲YES時,會向數組中每個對象發送copy消息,用拷貝好的對象創建新的數組。

NSMutableArray* arr4 = [[NSMutableArray alloc]initWithArray:arr copyItems:YES];

這樣就得到了合適的新數組。

 

2.那麼當自定義的對象的屬性中有其他自定義對象,該如何處理呢?

首先很自然想到的就是在兩個自定義類中都遵守<NSMutableCopying,NSCopying>並實現其協議方法,然後還是調用上面的

- initWithArray: copyItems: 方法,我們來看一下能否成功:

@interface testClass : NSObject<NSMutableCopying,NSCopying>
@property(nonatomic, strong) NSString* name;
@property(nonatomic, strong) testOther* other;
@end

@implementation testClass
-(id)mutableCopyWithZone:(NSZone *)zone{
    testClass* test = [[self class]allocWithZone:zone];
    test.name = _name;
    test.other = _other;
    return test;
}
-(id)copyWithZone:(NSZone *)zone{
    testClass* test = [[self class]allocWithZone:zone];
    test.name = _name;
    test.other = _other;
    return test;
}

----------

@interface testOther : NSObject<NSCopying>
@property(nonatomic,strong) NSString* title;
@end

@implementation testOther
-(id)copyWithZone:(NSZone *)zone{
    testOther* other = [[self class]allocWithZone:zone];
    other.title = _title;
    return other;
}
@end
testOther* other1 = [testOther new];
other1.title = @"o1";
testOther* other2 = [testOther new];
other2.title = @"o2";
testOther* other3 = [testOther new];
other3.title = @"o3";

testClass* class0 = [testClass new];
class0.name = @(1).stringValue;
class0.other = other1;
testClass* class1 = [testClass new];
class1.name = @(11).stringValue;
class1.other = other2;
testClass* class2 = [testClass new];
class2.name = @(111).stringValue;
class2.other = other3;

NSArray* arr = @[class0,class1,class2];
NSMutableArray* arr2 = [[NSMutableArray alloc]initWithArray:arr copyItems:YES];

可以看到雖然testClass類已經深拷貝了,但是testOther類依舊是同樣的地址。

我們需要這樣處理:

需要在包含其他對象屬性的類的copywithzone方法中,創建一個新的testOther對象,然後賦值給當前拷貝的對象的相關指針:

@implementation testClass
-(id)mutableCopyWithZone:(NSZone *)zone{
    testClass* test = [[self class]allocWithZone:zone];
    test.name = _name;
    testOther* temp = [testOther new];
    temp.title = _other.title;
    test.other = temp;
    return test;
}
-(id)copyWithZone:(NSZone *)zone{
    testClass* test = [[self class]allocWithZone:zone];
    test.name = _name;
    testOther* temp = [testOther new];
    temp.title = _other.title;
    test.other = temp;
    return test;
}
@end

然後再調用- initWithArray: copyItems: 方法。

搞定。

等等,那麼自定義對象中包含數組對象呢?

給個提示: 還是這個方法,在哪裏用應該挺清楚的了吧...

(ps.感覺要開始套娃了)

- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag;
//其他容器對象 如字典集合等也都有這樣類似的方法

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章