你要知道的NSCopying、NSCoding協(xié)議及對(duì)象序列化和反序列化都在這里
轉(zhuǎn)載請(qǐng)注明出處 http://www.itdecent.cn/p/e5bbf6936c16
本篇文章主要講解NSCopying協(xié)議,以及NSCoding協(xié)議實(shí)現(xiàn)對(duì)象的序列化和反序列化,實(shí)際開發(fā)中如果要自己造輪子這兩個(gè)協(xié)議還是比較重要的。
NSCopying協(xié)議
Foundation框架中為我們提供的基礎(chǔ)的類基本都實(shí)現(xiàn)了NSCopying協(xié)議,因此,我們可以使用copy方法用來獲取對(duì)象的一個(gè)不可變副本對(duì)象,可以使用mutableCopy方法用來獲取對(duì)象的一個(gè)可變副本對(duì)象,當(dāng)需要對(duì)自定義類調(diào)用copy或mutableCopy方法就需要實(shí)現(xiàn)NSCopying協(xié)議。首先我們看一下NSString類的復(fù)制操作。有如下代碼:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString *string = @"Jiaming Chen";
NSString *copyString = [string copy];
NSMutableString *mutableCopyString = [string mutableCopy];
//輸出: 0x1000020a8 0x1000020a8 0x10058ccb0
NSLog(@"%p %p %p", string, copyString, mutableCopyString);
NSMutableString *mutableString = [[NSMutableString alloc] initWithString:@"Jiaming Chen"];
NSString *copyMutableString = [mutableString copy];
NSMutableString *mutableCopyMutableString = [mutableString mutableCopy];
//輸出: 0x100403f90 0x100403ef0 0x100403fd0
NSLog(@"%p %p %p", mutableString, copyMutableString, mutableCopyMutableString);
NSString *str = [[NSMutableString alloc] initWithString:@"Jiaming Chen"];
NSString *copyStr = [str copy];
NSMutableString *mutableCopyStr = [str mutableCopy];
//輸出: 0x100404060 0x7fffa86ddd40 0x1004040a0
NSLog(@"%p %p %p", str, copyStr, mutableCopyStr);
NSString *errorStr = @"Jiaming Chen";
NSMutableString *errorMutableStr = errorStr;
NSString *copyErrorMutableStr = [errorMutableStr copy];
NSMutableString *mutableCopyErrorMutableStr = [errorMutableStr mutableCopy];
//輸出: 0x100002060 0x100002060 0x100002060 0x10440fbb0
NSLog(@"%p %p %p %p", errorStr, errorMutableStr, copyErrorMutableStr, mutableCopyErrorMutableStr);
}
}
上面的栗子,構(gòu)造了四組實(shí)驗(yàn):
第一組創(chuàng)建了一個(gè)NSString類型的對(duì)象,接下來使用copy和mutableCopy方法分別獲取不可變副本對(duì)象和可變副本對(duì)象,最后輸出三者的地址,可以發(fā)現(xiàn)原對(duì)象和不可變副本對(duì)象的地址一致,而可變對(duì)象的地址發(fā)生了變化。
第二組創(chuàng)建了一個(gè)NSMutableString類型的對(duì)象,同樣獲取了不可變和可變副本對(duì)象,輸出的結(jié)果發(fā)現(xiàn)三者的地址都不同了。
第三組實(shí)驗(yàn)使用多態(tài)的方式創(chuàng)建了一個(gè)編譯時(shí)類型為NSString而運(yùn)行時(shí)為NSMutableString的對(duì)象,并獲取可變和不可變副本對(duì)象,輸出地址發(fā)現(xiàn)三者的地址都不同了。
第四組實(shí)驗(yàn)其實(shí)有些問題,errorMutableStr是NSMutableString類型的,但我們將NSString類型的對(duì)象賦給了它,所以運(yùn)行時(shí)它的類型仍為NSString類型,但如果運(yùn)行時(shí)調(diào)用了errorMutableStr的可變方法如appendString:等就會(huì)拋出異常。之所以這樣做是為了和第三組實(shí)驗(yàn)進(jìn)行對(duì)比,同樣來獲取可變和不可變副本對(duì)象,并輸出地址,發(fā)現(xiàn)只有mutableCopy的方法的地址不同。
通過對(duì)比上面四組實(shí)驗(yàn)的mutableCopy復(fù)制可變副本對(duì)象的結(jié)果來看,我們可以發(fā)現(xiàn),不論原對(duì)象是否可變,復(fù)制可變副本對(duì)象都會(huì)獲取一個(gè)全新的對(duì)象,因?yàn)榈刂放c原對(duì)象不同,因此即時(shí)修改也互不影響。接下來觀察copy不可變副本對(duì)象的實(shí)驗(yàn),我們發(fā)現(xiàn)如果原對(duì)象運(yùn)行時(shí)類型是不可變的使用copy以后獲取的是原對(duì)象的地址,并沒有創(chuàng)建一個(gè)全新的對(duì)象,因?yàn)椋瓕?duì)象本身就是不可變的,復(fù)制一個(gè)不可變對(duì)象沒有太大的實(shí)際意義,直接使用原對(duì)象即可,如果原對(duì)象的運(yùn)行時(shí)類型是可變的,使用copy以后獲取的是一個(gè)全新的對(duì)象,這樣就可以避免多態(tài)時(shí)的錯(cuò)誤。
可以總結(jié)出如下表格:
| type | copy | mutableCopy |
|---|---|---|
| NS* | 淺拷貝,只拷貝指針,地址相同 | 單層深拷貝,拷貝內(nèi)容,地址不同 |
| NSMutable* | 單層深拷貝,拷貝內(nèi)容,地址不同 | 單層深拷貝,拷貝內(nèi)容,地址不同 |
由上述表格可以看出,對(duì)于不可變類型,使用copy方法時(shí)是淺拷貝,只拷貝指針,因?yàn)閮?nèi)容是不會(huì)變化的。使用mutableCopy時(shí)由于返回可變對(duì)象因此需要一份拷貝,供其他對(duì)象使用。對(duì)于可變類型,不管是copy還是mutableCopy均會(huì)進(jìn)行單層深拷貝,所指向指針不同。
對(duì)于容器類型的對(duì)象,copy和mutableCopy與上述實(shí)驗(yàn)效果一直,容器類型的復(fù)制不會(huì)進(jìn)行徹底的深拷貝,只會(huì)實(shí)現(xiàn)單層深拷貝,即創(chuàng)建一個(gè)新的容器,但容器里的內(nèi)容只是對(duì)原對(duì)象容器的內(nèi)容進(jìn)行淺拷貝即只拷貝地址,如果要實(shí)現(xiàn)真正的深拷貝需自行實(shí)現(xiàn)相關(guān)方法。
學(xué)習(xí)完了Foundation框架提供的類的相關(guān)復(fù)制操作,接下將針對(duì)自定義類型的復(fù)制操作進(jìn)行講解,如果需要自定義類型支持復(fù)制的操作需要實(shí)現(xiàn)NSCopying協(xié)議,并實(shí)現(xiàn)copyWithZone:方法而不是重寫copy方法,舉個(gè)栗子如下:
@interface Account: NSObject <NSCopying>
@property (nonatomic, strong) NSMutableString *accountNumber;
@property (nonatomic, assign) double balance;
@end
@implementation Account
@synthesize accountNumber = _accountNumber;
@synthesize balance = _balance;
- (instancetype)copyWithZone:(NSZone *)zone
{
//調(diào)用NSObject的類方法allocWithZone創(chuàng)建一個(gè)新的對(duì)象
Account *account = [[self class] allocWithZone:zone];
//使用mutableCopy獲取一個(gè)可變的副本對(duì)象實(shí)現(xiàn)深拷貝
//如果不調(diào)用mutableCopy而直接賦值,則是淺拷貝,另一個(gè)對(duì)象的修改會(huì)影響到當(dāng)前對(duì)象的值
account.accountNumber = [self.accountNumber mutableCopy];
account.balance = self.balance;
return account;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableString *mutableStr = [[NSMutableString alloc] initWithString:@"Jiaming Chen"];
Account *account = [[Account alloc] init];
account.accountNumber = mutableStr;
account.balance = 666.66;
Account *accountBak = [account copy];
//輸出:0x100407460 0x100407580
NSLog(@"%p %p", account, accountBak);
//輸出:0x1004074d0 0x100407600 0x100407be0
NSLog(@"%p %p %p", account.accountNumber, accountBak.accountNumber, mutableStr);
}
return 0;
}
上面舉的栗子很簡單,自定義了一個(gè)Account類,其中為了實(shí)驗(yàn)方便定義的是NSMutableString類型的accountNumber屬性,并且使用了strong修飾符,關(guān)于使用copy、strong修飾符的問題有不明白的讀者可以閱讀本博客另一篇文章iOS @property探究(一): 基礎(chǔ)詳解,該類遵守NSCopying協(xié)議并實(shí)現(xiàn)了copyWithZone:方法,在實(shí)現(xiàn)方法中首先調(diào)用了NSObject類的allocWithZone:方法來創(chuàng)建一個(gè)新的對(duì)象,如果繼承的不是NSObject類,而是繼承其他自定義類,如果繼承的父類沒有實(shí)現(xiàn)NSCopying協(xié)議這里不能使用allocWithZone:方法來創(chuàng)建新的對(duì)象,而應(yīng)該使用alloc和init方法,如果繼承的父類實(shí)現(xiàn)了NSCopying協(xié)議,這里需要調(diào)用父類的方法來初始化,即調(diào)用[super copy]方法來創(chuàng)建一個(gè)全新的對(duì)象,接下來就可以按照需要進(jìn)行深拷貝或淺拷貝了。
對(duì)于NSCopying協(xié)議,需要注意的就是可變對(duì)象和不可變對(duì)象之間的區(qū)別,以及父類有沒有實(shí)現(xiàn)NSCopying協(xié)議來確定調(diào)用何種方法來創(chuàng)建新對(duì)象。
NSCoding協(xié)議與對(duì)象序列化和反序列化
在開發(fā)中可能需要將自定義對(duì)象持久化存儲(chǔ)在本地的文件中,或?qū)?duì)象轉(zhuǎn)換為NSData類的數(shù)據(jù)并通過網(wǎng)絡(luò)發(fā)送,要實(shí)現(xiàn)這些操作的前提就是自定義對(duì)象需要遵守NSCoding協(xié)議,NSCoding協(xié)議是對(duì)象序列化和反序列化的基礎(chǔ),NSCoding協(xié)議只定義了兩個(gè)方法:
/*將對(duì)象編碼
序列化對(duì)象時(shí)調(diào)用該方法,在該方法中序列化對(duì)象的每一個(gè)屬性
一般使用encodeObject:forKey:方法序列化屬性
*/
- (void)encodeWithCoder:(NSCoder *)aCoder;
/*
將數(shù)據(jù)解碼并創(chuàng)建一個(gè)對(duì)象
反序列化時(shí)調(diào)用該方法,在該方法中反序列化對(duì)象的每一個(gè)熟悉
一般使用decodeObject:forKey方法反序列化屬性
*/
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder;
舉個(gè)栗子如下:
@interface Account: NSObject <NSCoding>
@property (nonatomic, copy) NSString *accountNumber;
@property (nonatomic, assign) double balance;
@end
@implementation Account
@synthesize accountNumber = _accountNumber;
@synthesize balance = _balance;
//反序列化
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
/*NSObject沒有遵守NSCoding協(xié)議,因此調(diào)用父類的init構(gòu)造方法
如果繼承的父類遵守NSCoding協(xié)議需要調(diào)用父類的initWithCoder:方法
[super initWithCoder:aDecoder]
*/
if (self = [super init])
{
self.accountNumber = [aDecoder decodeObjectForKey:@"kaccountNumber"];
self.balance = [aDecoder decodeDoubleForKey:@"kbalance"];
}
return self;
}
//序列化
- (void)encodeWithCoder:(NSCoder *)aCoder
{
//序列化與序列化的順序不需要保持一致,因?yàn)槭褂昧薻ey來檢索,可以亂序
[aCoder encodeObject:self.accountNumber forKey:@"kaccountNumber"];
[aCoder encodeDouble:self.balance forKey:@"kbalance"];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Account *account = [[Account alloc] init];
account.accountNumber = @"1603121434";
account.balance = 666.66;
//輸出: <Account: 0x10040c2f0> 1603121434 666.660000
NSLog(@"%@ %@ %lf", account, account.accountNumber, account.balance);
//將對(duì)象持久化到本地文件
[NSKeyedArchiver archiveRootObject:account toFile:@"account.archiver"];
//從本地文件中將數(shù)據(jù)反序列化創(chuàng)建對(duì)象
Account *a = [NSKeyedUnarchiver unarchiveObjectWithFile:@"account.archiver"];
//輸出: <Account: 0x100506860> 1603121434 666.660000
NSLog(@"%@ %@ %lf", a, a.accountNumber, a.balance);
}
return 0;
}
上面的栗子自定義了一個(gè)Account類,遵守NSCoding協(xié)議并實(shí)現(xiàn)了相關(guān)序列化和反序列化方法。栗子比較簡單不再贅述。通過對(duì)象的序列化其實(shí)可以實(shí)現(xiàn)真正意義上的深拷貝,因?yàn)?code>Foundation提供的數(shù)據(jù)類型包括集合類型基本都實(shí)現(xiàn)了NSCoding協(xié)議,因此,都可以實(shí)現(xiàn)序列化,序列化時(shí)將對(duì)象轉(zhuǎn)換為字節(jié)碼,反序列化時(shí)再將字節(jié)碼反序列化為對(duì)應(yīng)數(shù)據(jù)類型的數(shù)據(jù),通過序列化和反序列化就可以實(shí)現(xiàn)真正意義的深拷貝,這里不再舉栗子了。
上述舉的栗子使用NSKeyedArchiver的archiveRootObject:toFile方法只能將一個(gè)對(duì)象持久化在一個(gè)文件中,如果要持久化多個(gè)對(duì)象只能分別存放在多個(gè)文件中,這樣即浪費(fèi)資源又比較復(fù)雜,NSKeyedArchiver提供了使用NSMutableData類的方式來實(shí)現(xiàn)多對(duì)象持久化到一個(gè)NSMutableData中,因此也可以持久化到一個(gè)文件中,這里不再舉相關(guān)栗子了,有需要的讀者可以自行查閱。
關(guān)于NSCoding協(xié)議的使用是比較簡單的,需要注意的就是在initWithCoder:方法中調(diào)用父類構(gòu)造函數(shù)的方式。
備注
由于作者水平有限,難免出現(xiàn)紕漏,如有問題還請(qǐng)不吝賜教。