NSCopying和NSCoding對(duì)象序列化反序列化基礎(chǔ)詳解

你要知道的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)用copymutableCopy方法就需要實(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ì)象,接下來使用copymutableCopy方法分別獲取不可變副本對(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í)有些問題,errorMutableStrNSMutableString類型的,但我們將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ì)象,copymutableCopy與上述實(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)于使用copystrong修飾符的問題有不明白的讀者可以閱讀本博客另一篇文章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)該使用allocinit方法,如果繼承的父類實(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)真正意義的深拷貝,這里不再舉栗子了。

上述舉的栗子使用NSKeyedArchiverarchiveRootObject: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)不吝賜教。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容