在編寫程序的過程中,我們經常會想將某些對象進行復制用。在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 區分深淺拷貝并看情況使用