iPhone開發應用之Archiving NSCoder教程是本文要介紹的內容,一個物件導向程式在啟動並執行時候,一般都建立了一個複雜的對象關係圖,經常需要把這樣一個複雜的對象關係圖表示成位元組流.這樣的過程我們叫做Archiving 10.1,
這個位元組流可以在網路中傳送,也可以寫入到檔案中. 例如,我們建立儲存一個nib檔案,Interface Builder把對象寫入到nib檔案就是這樣的arching過程對於Java,這個過程叫serialization)。
而當從位元組流中重新恢複對象關係圖的過程叫做unarchive. 例如,當程式啟動是,將會從nib檔案中unarchive對象雖然對象包含成員變數和方法.但是只有成員變數和類名會被archive. 換句話說,data會被archive,而code不會. 所以,如果程式A archive對象,而程式B unarchive對象.那麼程式A和B都要保證包含了class所串連的code. 舉個例子,在nib檔案中,你使用到了Appkit framework 的NSWindow和NSButton對象.那麼如果我們的程式沒有串連Appkit framework,那麼我們就沒有辦法產生NSWindow和NSButton對象,因為archive中只包含了data,而沒有code
有一個洗髮水的廣告是這樣說得:"我告訴了我的兩個朋友,而他們各自又告訴了自己的兩個朋友,這樣一傳十,十傳百.."寓意就是,你告訴了你的朋友,最後所有的人都開始使用這個洗髮水了. 對象archiving的工作方式和這差不多. 你archiving一個root對象. 它archiving自己相關聯的對象,那些相關聯的對象也會archiving自己相關聯的對象,依次類推,所有相關的對象都被archiving了
archiving由2步來完成. 1,我們需要告知我們的對象要怎麼樣來archive. 2. 我們需要激發archiving動作發生
Objective-C語言有一個機制叫protocol, 就像java中的interface一樣. 一個protocol聲明了一系列方法.但你的類實現一個protocol,那麼就預定了,你的類需要實現protocol中聲明的所有方法
NSCoder 和NSCoding
NSCoding是一個protocol. 如果你的類實現了NSCoding.那麼就要實現這些方法
- - (id)initWithCoder:(NSCoder *)coder;
- - (void)encodeWithCoder:(NSCoder *)coder;
NSCoder是archivie 位元組流的抽象類別.我們可以實現把資料寫入一個coder,也可以從coder中讀取我們寫入的資料. 我們對象的方法initWithCoder:就是從一個coder從讀取資料,然後把資料賦給成員變數. 方法encodeWithCoder: 則是把成員變數的值寫入到coder中. 在這一章中,我們會在Person類中實現這兩個方法
NSCoder是一個抽象類別,我們不會直接使用它來建立對象. 相反,我們會使用從它繼承來的子類. 也就是我們使用 NSKeyedUnarchiver類來從位元組流中讀取資料,而使用NSKeyedArchiver類來把對象寫入到位元組流
Encoding
NSCoder包含了很多方法, 不過大部分人會發現只會使用到其中很少的一部分. 下面是當要archivie資料時用到的一些常用方法
- - (void)encodeObject:(id)anObject forKey:(NSString *)aKey
這個方法把anObject對象寫入到coder中,並把它和aKey關聯起來[下次使用aKey從coder中可以再把anObject讀取出來] 這會是anObject的方法encodeWithCodr得到調用(還記得上面那個洗髮水廣告把.就是這樣傳下去的)
對於C的基本類型(如int float).NSCoder使用下面方法
- - (void)encodeBool:(BOOL)boolv forKey:(NSString *)key
- - (void)encodeDouble:(double)realv forKey:(NSString *)key
- - (void)encodeFloat:(float)realv forKey:(NSString *)key
- - (void)encodeInt:(int)intv forKey:(NSString *)key
添加encoing方法到Person類中.
- - (void)encodeWithCoder:(NSCoder *)coder
- {
- [super encodeWithCoder:coder];
- [coder encodeObject:personName forKey:@"personName"];
- [coder encodeFloat:expectedRaise forKey:@"expectedRaise"];
- }
這裡調用了父類的encodeWithCoder,使得父類有機會把自己的變數寫入到coder中. 因此,類繼承樹中的類只會把自己的成員變數寫入到coder-不會包含父類的成員變數
Decoding
從coder中decoding資料,我們使用這些方法
- - (id)decodeObjectForKey:(NSString *)aKey
- - (BOOL)decodeBoolForKey:(NSString *)key
- - (double)decodeDoubleForKey:(NSString *)key
- - (float)decodeFloatForKey:(NSString *)key
- - (int)decodeIntForKey:(NSString *)key
如果因為某些原因, 位元組流中沒有和aKey關聯的資料,那麼我們會得到0值. 例如,對象沒有把key foo 關聯一個float資料寫入coder,那麼在使用foo key來讀取這個float資料,coder會返回0.0 . 如果key foo關聯的是一個對象資料[使用方法encodeWithCoder 寫入],那麼讀取時coder返回nil
添加decoding到Person類中
- - (id)initWithCoder:(NSCoder *)coder
- {
- [super init];
- personName = [[coder decodeObjectForKey:@"personName"] retain];
- expectedRaise = [coder decodeFloatForKey:@"expectedRaise"];
- return self;
- }
我們沒有調用父類的initWithCoder, 那是因為NSObject沒有實現它. 如過Person類的父類實現了NSCoding協議,那麼這個方法應該這樣寫
- - (id)initWithCoder:(NSCoder *)coder
- {
- [super initWithCoder:coder];
- personName = [[coder decodeObjectForKey:@"personName"] retain];
- expectedRaise = [coder decodeFloatForKey:@"expectedRaise"];
- return self;
- }
你可以會說"在第3章中, designated initializer會完成所有的init工作然後在調用父類的 designated initializer, 也就是說類的其他initializer 方法都會調用designated initializer,Person類有designated initializer- init. 可以這個新加入的initializer方法並沒有調用init方法阿?" 不錯, 你是對的, initWithCoer: 是這個規則的一個特例.
好了.我們實現了NSCoding協議的方法.現在讓Person類實現NSCoding protocol. 我們來編輯Person.h檔案.
- @interface Person : NSObject <NSCoding> {
現在編譯我們的工程. 你也可以運行程式看看.雖然Person類可以encode自己了.不過我們沒有地方讓它這麼做.所以程式看上去沒什麼變化.