iPhone開(kāi)發(fā)應(yīng)用之Archiving NSCoder教程
iPhone開(kāi)發(fā)應(yīng)用之Archiving NSCoder教程是本文要介紹的內(nèi)容,一個(gè)面向?qū)ο蟪绦蛟谶\(yùn)行的時(shí)候,一般都創(chuàng)建了一個(gè)復(fù)雜的對(duì)象關(guān)系圖,經(jīng)常需要把這樣一個(gè)復(fù)雜的對(duì)象關(guān)系圖表示成字節(jié)流.這樣的過(guò)程我們叫做Archiving 如圖10.1,
這個(gè)字節(jié)流可以在網(wǎng)絡(luò)中傳送,也可以寫(xiě)入到文件中. 例如,我們創(chuàng)建保存一個(gè)nib文件,Interface Builder把對(duì)象寫(xiě)入到nib文件就是這樣的arching過(guò)程(對(duì)于Java,這個(gè)過(guò)程叫serialization)。
而當(dāng)從字節(jié)流中重新恢復(fù)對(duì)象關(guān)系圖的過(guò)程叫做unarchive. 例如,當(dāng)程序啟動(dòng)是,將會(huì)從nib文件中unarchive對(duì)象雖然對(duì)象包含成員變量和方法.但是只有成員變量和類(lèi)名會(huì)被archive. 換句話(huà)說(shuō),data會(huì)被archive,而code不會(huì). 所以,如果程序A archive對(duì)象,而程序B unarchive對(duì)象.那么程序A和B都要保證包含了class所連接的code. 舉個(gè)例子,在nib文件中,你使用到了Appkit framework 的NSWindow和NSButton對(duì)象.那么如果我們的程序沒(méi)有連接Appkit framework,那么我們就沒(méi)有辦法生成NSWindow和NSButton對(duì)象,因?yàn)閍rchive中只包含了data,而沒(méi)有code
有一個(gè)洗發(fā)水的廣告是這樣說(shuō)得:"我告訴了我的兩個(gè)朋友,而他們各自又告訴了自己的兩個(gè)朋友,這樣一傳十,十傳百.."寓意就是,你告訴了你的朋友,最后所有的人都開(kāi)始使用這個(gè)洗發(fā)水了. 對(duì)象archiving的工作方式和這差不多. 你archiving一個(gè)root對(duì)象. 它archiving自己相關(guān)聯(lián)的對(duì)象,那些相關(guān)聯(lián)的對(duì)象也會(huì)archiving自己相關(guān)聯(lián)的對(duì)象,依次類(lèi)推,所有相關(guān)的對(duì)象都被archiving了
archiving由2步來(lái)完成. 1,我們需要告知我們的對(duì)象要怎么樣來(lái)archive. 2. 我們需要激發(fā)archiving動(dòng)作發(fā)生
Objective-C語(yǔ)言有一個(gè)機(jī)制叫protocol, 就像java中的interface一樣. 一個(gè)protocol聲明了一系列方法.但你的類(lèi)實(shí)現(xiàn)一個(gè)protocol,那么就預(yù)定了,你的類(lèi)需要實(shí)現(xiàn)protocol中聲明的所有方法
NSCoder 和NSCoding
NSCoding是一個(gè)protocol. 如果你的類(lèi)實(shí)現(xiàn)了NSCoding.那么就要實(shí)現(xiàn)這些方法
- - (id)initWithCoder:(NSCoder *)coder;
- - (void)encodeWithCoder:(NSCoder *)coder;
NSCoder是archivie 字節(jié)流的抽象類(lèi).我們可以實(shí)現(xiàn)把數(shù)據(jù)寫(xiě)入一個(gè)coder,也可以從coder中讀取我們寫(xiě)入的數(shù)據(jù). 我們對(duì)象的方法initWithCoder:就是從一個(gè)coder從讀取數(shù)據(jù),然后把數(shù)據(jù)賦給成員變量. 方法encodeWithCoder: 則是把成員變量的值寫(xiě)入到coder中. 在這一章中,我們會(huì)在Person類(lèi)中實(shí)現(xiàn)這兩個(gè)方法
NSCoder是一個(gè)抽象類(lèi),我們不會(huì)直接使用它來(lái)創(chuàng)建對(duì)象. 相反,我們會(huì)使用從它繼承來(lái)的子類(lèi). 也就是我們使用 NSKeyedUnarchiver類(lèi)來(lái)從字節(jié)流中讀取數(shù)據(jù),而使用NSKeyedArchiver類(lèi)來(lái)把對(duì)象寫(xiě)入到字節(jié)流
Encoding
NSCoder包含了很多方法, 不過(guò)大部分人會(huì)發(fā)現(xiàn)只會(huì)使用到其中很少的一部分. 下面是當(dāng)要archivie數(shù)據(jù)時(shí)用到的一些常用方法
- - (void)encodeObject:(id)anObject forKey:(NSString *)aKey
這個(gè)方法把a(bǔ)nObject對(duì)象寫(xiě)入到coder中,并把它和aKey關(guān)聯(lián)起來(lái)[下次使用aKey從coder中可以再把a(bǔ)nObject讀取出來(lái)] 這會(huì)是anObject的方法encodeWithCodr得到調(diào)用(還記得上面那個(gè)洗發(fā)水廣告把.就是這樣傳下去的)
對(duì)于C的基本類(lèi)型(如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類(lèi)中.
- - (void)encodeWithCoder:(NSCoder *)coder
- {
- [super encodeWithCoder:coder];
- [coder encodeObject:personName forKey:@"personName"];
- [coder encodeFloat:expectedRaise forKey:@"expectedRaise"];
- }
這里調(diào)用了父類(lèi)的encodeWithCoder,使得父類(lèi)有機(jī)會(huì)把自己的變量寫(xiě)入到coder中. 因此,類(lèi)繼承樹(shù)中的類(lèi)只會(huì)把自己的成員變量寫(xiě)入到coder-不會(huì)包含父類(lèi)的成員變量
Decoding
從coder中decoding數(shù)據(jù),我們使用這些方法
- - (id)decodeObjectForKey:(NSString *)aKey
- - (BOOL)decodeBoolForKey:(NSString *)key
- - (double)decodeDoubleForKey:(NSString *)key
- - (float)decodeFloatForKey:(NSString *)key
- - (int)decodeIntForKey:(NSString *)key
如果因?yàn)槟承┰? 字節(jié)流中沒(méi)有和aKey關(guān)聯(lián)的數(shù)據(jù),那么我們會(huì)得到0值. 例如,對(duì)象沒(méi)有把key foo 關(guān)聯(lián)一個(gè)float數(shù)據(jù)寫(xiě)入coder,那么在使用foo key來(lái)讀取這個(gè)float數(shù)據(jù),coder會(huì)返回0.0 . 如果key foo關(guān)聯(lián)的是一個(gè)對(duì)象數(shù)據(jù)[使用方法encodeWithCoder 寫(xiě)入],那么讀取時(shí)coder返回nil
添加decoding到Person類(lèi)中
- - (id)initWithCoder:(NSCoder *)coder
- {
- [super init];
- personName = [[coder decodeObjectForKey:@"personName"] retain];
- expectedRaise = [coder decodeFloatForKey:@"expectedRaise"];
- return self;
- }
我們沒(méi)有調(diào)用父類(lèi)的initWithCoder, 那是因?yàn)镹SObject沒(méi)有實(shí)現(xiàn)它. 如過(guò)Person類(lèi)的父類(lèi)實(shí)現(xiàn)了NSCoding協(xié)議,那么這個(gè)方法應(yīng)該這樣寫(xiě)
- - (id)initWithCoder:(NSCoder *)coder
- {
- [super initWithCoder:coder];
- personName = [[coder decodeObjectForKey:@"personName"] retain];
- expectedRaise = [coder decodeFloatForKey:@"expectedRaise"];
- return self;
- }
你可以會(huì)說(shuō)"在第3章中, designated initializer會(huì)完成所有的init工作然后在調(diào)用父類(lèi)的 designated initializer, 也就是說(shuō)類(lèi)的其他initializer 方法都會(huì)調(diào)用designated initializer,Person類(lèi)有designated initializer- init. 可以這個(gè)新加入的initializer方法并沒(méi)有調(diào)用init方法阿?" 不錯(cuò), 你是對(duì)的, initWithCoer: 是這個(gè)規(guī)則的一個(gè)特例.
好了.我們實(shí)現(xiàn)了NSCoding協(xié)議的方法.現(xiàn)在讓Person類(lèi)實(shí)現(xiàn)NSCoding protocol. 我們來(lái)編輯Person.h文件.
- @interface Person : NSObject <NSCoding> {
現(xiàn)在編譯我們的工程. 你也可以運(yùn)行程序看看.雖然Person類(lèi)可以encode自己了.不過(guò)我們沒(méi)有地方讓它這么做.所以程序看上去沒(méi)什么變化.
#p#
Document Architecture
多文檔程序有很多的共同性. 比如都可以創(chuàng)建新的document, 打開(kāi)document,保存或打印打開(kāi)的document, 當(dāng)關(guān)閉document窗口或退出程序時(shí)提醒用戶(hù)保存編輯好得document. Apple提供3個(gè)類(lèi)- NSDocumentController,NSDocument,NSWindowController-來(lái)完成這些工作. 它們一起組成了document architecture
創(chuàng)建document architecture的意圖是和我們第8章討論的Model-View-Controller設(shè)計(jì)模式相關(guān)的. 在RaiseMan工程中. 我們的NSDocument子類(lèi)-使用了NSArrayController類(lèi)-就是其中的Controller. 它包含了指向model對(duì)象的指針. 負(fù)責(zé)下面所列的職責(zé) [這里的model 數(shù)據(jù)就是值employyess-person 對(duì)象]
將model 數(shù)據(jù)保存為一個(gè)文件
從一個(gè)文件中加載model數(shù)據(jù)
在view中顯示model數(shù)據(jù)
響應(yīng)用戶(hù)通過(guò)view的輸入,并更新model
Info.plist 和 NSDocumentController
XCode在編譯創(chuàng)建一個(gè)程序時(shí)會(huì)使用到一個(gè)文件 Info.plist(本章后面,我們會(huì)修改這個(gè)文件). 當(dāng)程序啟動(dòng)時(shí),它會(huì)讀取Info.plisst的信息. 告知工作的文件類(lèi)型是什么. 如果它發(fā)現(xiàn)是一個(gè)document-base 程序. 那么會(huì)創(chuàng)建一個(gè)NSDocumentController對(duì)象(圖10.2). 我們很少去直接使用這個(gè)document controller. NSDocumentController對(duì)象在后面會(huì)為我們做一些工作.例如,當(dāng)選擇New 或是 Save All菜單時(shí), document controller會(huì)處理這些請(qǐng)求. 如果你有給document controller發(fā)送消息,你可以這樣做
- NSDocumentController *dc;
- dc = [NSDocumentController sharedDocumentController];
document controller保存了一個(gè)document 對(duì)象的array - 每一個(gè)document對(duì)象就是一個(gè)打開(kāi)的document.
NSDocument
document對(duì)象是NSDocument子類(lèi)的一個(gè)實(shí)例. 在我們的RaiseMan程序中,它就是MyDocument的實(shí)例. 對(duì)于大部分程序,一般我們只有簡(jiǎn)單的擴(kuò)展NSDocument來(lái)完成想要的功能而不需要過(guò)多關(guān)系NSDocumentcontroller或是NSWindowController
saving
菜單項(xiàng)Save,Save As...,Save All,和Close雖然不相同.但是它們都面向同一個(gè)問(wèn)題:把mdoel保存為一個(gè)文件或是文件包(文件包是一個(gè)文件目錄,不過(guò)對(duì)于用戶(hù)就象是一個(gè)文件一樣). 對(duì)于這些菜單項(xiàng). 我們的NSDocument子類(lèi)需要實(shí)現(xiàn)下面3個(gè)方法中的一個(gè)
- - (NSData *)dataOfType:(NSString *)aType
- error:(NSError *)e
你的document對(duì)象將model生成一個(gè)NSData寫(xiě)入文件.[這個(gè)方法中,我們只有把model壓成一個(gè)NSData返回,然后Cocoa會(huì)把NSData在寫(xiě)入文件了] NSData就是字節(jié)buffer. 是簡(jiǎn)單也是通用的實(shí)現(xiàn)saving的方法.如果不能生成一個(gè)NSData 對(duì)象,那么就返回nil,而用戶(hù)會(huì)得到一個(gè)alert提示save失敗. 注意到參數(shù)aType, 它可以容許你將document保存為一個(gè)或多個(gè)類(lèi)型格式. 例如,你編寫(xiě)了一個(gè)圖像程序,你可能容許用戶(hù)將圖像保存為gif或是jpg格式.所以當(dāng)你生成data對(duì)象時(shí), aType就指定了用戶(hù)請(qǐng)求保存的格式.如果你的程序只處理單一類(lèi)型,那么可以忽略aType. 為了說(shuō)明你不能保存,可以返回nil并創(chuàng)建一個(gè)NSError對(duì)象來(lái)說(shuō)明出來(lái)什么樣得錯(cuò)誤
- - (NSFileWrapper *)fileWrapperOfType:(NSString *)aType
- error:(NSError *)e
你的document對(duì)象生成一個(gè)文件包返回. 文件包將被創(chuàng)建在用戶(hù)指定的位置
- - (BOOL)writeToURL:(NSURL *)absoluteURL
- ofType:(NSString *)typeName
- error:(NSError **)outError;
你的docuemnt對(duì)象以指定的type把model數(shù)據(jù)保存在指定的URL(URL就是文件系統(tǒng)上的文件路徑)[這個(gè)方法應(yīng)該在NSDocument類(lèi)中實(shí)現(xiàn)了,里面估計(jì)就是調(diào)用了dataOfType:error: . 得到NSData后將其寫(xiě)入指定URL. 當(dāng)然你也可以從中這個(gè)方法] 如果能夠保存成功返回YES,否則返回NO. 如果返回NO,那么你你應(yīng)該生成一個(gè)NSError對(duì)象來(lái)描述錯(cuò)誤是什么
來(lái)解釋下NSError.它的觀念是,因?yàn)槟承┰?某個(gè)方法沒(méi)有辦法完成這個(gè)功能.那么它就會(huì)生成一個(gè)NSError對(duì)象,并把NSError對(duì)象的指針?lè)诺街付ǖ奈恢? 例如,如果我希望從一個(gè)文件中讀取到一個(gè)NSData,那么我會(huì)提供一個(gè)地址,當(dāng)出錯(cuò)時(shí),我可以從這個(gè)地址中得到錯(cuò)誤信息
NSError *e;
- NSData *d = [NSData dataWithContentsOfFile:@"/tmp/x.txt"
- options:0
- error:&error];
- // Did the read fail?
- if (d == nil) {
- NSLog(@"Read failed: %@", [error localizedDescription];
- }
所以NSData類(lèi)即會(huì)返回一個(gè)data對(duì)象,同時(shí)可能會(huì)創(chuàng)建一個(gè)error對(duì)象
在save和load方法中,我們將有負(fù)責(zé)在失敗的時(shí)候創(chuàng)建NSError對(duì)象
Loading
Open...,Open Recent,和Revert To Saved 菜單項(xiàng)也是一樣,它們都面向同一個(gè)問(wèn)題:從一個(gè)文件或是文件包中得到model. 為了響應(yīng)它們,NSDocuement子類(lèi)需要實(shí)現(xiàn)下面3個(gè)方法中的一個(gè)
- - (BOOL)readFromData:(NSData *)data
- ofType:(NSString *)typeName
- error:(NSError **)outError
包含了用戶(hù)要打開(kāi)的文件內(nèi)容的NSData對(duì)象被傳進(jìn)來(lái). 如果能夠從這個(gè)NSData對(duì)象中生成model那么就返回YES. 如果返回NO,那么用戶(hù)會(huì)得到一個(gè)Alert提示為什么個(gè)不能成功打開(kāi)文件. Alert的內(nèi)容由這個(gè)方法生成的NSError對(duì)象來(lái)指定
- - (BOOL)readFromFileWrapper:(NSFileWrapper *)fileWrapper
- ofType:(NSString *)typeName
- error:(NSError **)outError;
從一個(gè)NSFileWrapper對(duì)象讀取model數(shù)據(jù)
- - (BOOL)readFromURL:(NSURL *)absoluteURL
- ofType:(NSString *)typeName
- error:(NSError **)outError;
#p#
從指定文件中讀取model數(shù)據(jù)
在實(shí)現(xiàn)了一個(gè)save和一個(gè)load方法后,我們的程序就知道怎么樣讀寫(xiě)文件了.在打開(kāi)一個(gè)文件時(shí), document對(duì)象會(huì)在讀取nib文件前讀取document文件[你需要讀取nib文件來(lái)顯示一個(gè)document阿] . 這樣的結(jié)果是,我們不能在loading一個(gè)document文件后馬上去給用戶(hù)界面發(fā)送消息(它們還不存在)[注意load nib文件是document 架構(gòu)為我們做的,這里說(shuō)的立馬調(diào)用是指在 load方法中-這個(gè)是我們?cè)贜SDocument子類(lèi)中實(shí)現(xiàn)的調(diào)用-給UI發(fā)送消息]. [那如果我們想要馬上給UI發(fā)送消息怎么辦?]-為了解決這個(gè)問(wèn)題,我們可以實(shí)現(xiàn)一個(gè)方法-它會(huì)在nib文件被調(diào)用UI創(chuàng)建好了后發(fā)送
- - (void)windowControllerDidLoadNib:(NSWindowController *)x;
[想想 當(dāng)點(diǎn)擊Open菜單,代碼執(zhí)行的過(guò)程是怎么樣 - 有些代碼是cocoa里面實(shí)現(xiàn)的,有些是我們自己實(shí)現(xiàn)的]
在我們的NSDocuemnt 子類(lèi)中,實(shí)現(xiàn)這個(gè)方法刷新UI
NSWindowController
在document architecture中最后要介紹的一個(gè)類(lèi)是NSWindowcontroller .每打開(kāi)一個(gè)Document都會(huì)產(chǎn)生一個(gè)窗口-生成一個(gè)NSWindowController實(shí)例. 對(duì)于大部分程序,每一個(gè)document對(duì)于一個(gè)window, window controller的默認(rèn)實(shí)現(xiàn)已經(jīng)夠用了.所以一般我們只有在下面幾種情況下才會(huì)生成一個(gè)NSWindowController的子類(lèi)
對(duì)于同一個(gè)document,需要使用多個(gè)window. 例如,CAD程序, 你可能需要一個(gè)text窗口來(lái)描述一個(gè)立體,而另外一個(gè)窗口來(lái)顯示這個(gè)立體
你需要把UI controller 和 model controller 放到不同的類(lèi)中
你需要?jiǎng)?chuàng)建不和NSDocument 對(duì)象對(duì)應(yīng)的窗口.我們會(huì)在12章來(lái)做這樣的事
Saving 和 NSKeyedArchiver
現(xiàn)在我們知道了怎樣encode和decode我們自己的類(lèi),現(xiàn)在開(kāi)始給我們的程序添加saving和loading功能了. 當(dāng)我們要保存person到一個(gè)文件,MyDocument類(lèi)會(huì)被請(qǐng)求生成一個(gè)NSData實(shí)例. 一旦創(chuàng)建了NSData實(shí)例并返回,它會(huì)自動(dòng)保存到文件中
為了生成一個(gè)NSData實(shí)例[encode了model數(shù)據(jù)] , 我們使用NSKeyedArchiver類(lèi). 它有這樣一個(gè)方法
- + (NSData *)archivedDataWithRootObject:(id)rootObject
這個(gè)方法將對(duì)象archive成NSData對(duì)象的字節(jié)buffer [字節(jié)buffe-看看NSData的說(shuō)明吧]
再一次回到那個(gè)廣告"我告訴了兩朋友,他們也告訴了自己的朋友...."當(dāng)你encode一個(gè)對(duì)象是, 這個(gè)對(duì)象會(huì)encode它自己連接的對(duì)象,那些對(duì)象也會(huì)encode它們連接的對(duì)象..等等. 這里我們要encode那個(gè)對(duì)象呢?就是array employees了. 它又會(huì)encode所有包含的Person對(duì)象. 而我們?cè)赑eron類(lèi)中實(shí)現(xiàn)了encodeWithCoder:,所以每個(gè)Perosn對(duì)象開(kāi)始encode自己了-encode personName字串和expectedRaise float
編輯方法dataOfType:error:. 添加saving功能
- - (NSData *)dataOfType:(NSString *)aType
- error:(NSError **)outError
- {
- // End editing
- [[tableView window] endEditingFor:nil];
- // Create an NSData object from the employees array
- return [NSKeyedArchiver archivedDataWithRootObject:employees];
- }
這里我們忽略了error參數(shù).將沒(méi)有error產(chǎn)生
- Loading和NSKeyedUnarchiver
現(xiàn)在開(kāi)始添加load文件功能, 再一次說(shuō)明,NSDocument已經(jīng)大部分細(xì)節(jié)
我們會(huì)使用到NSKeyedUnarchiver類(lèi)方法
- + (id)unarchiveObjectWithData:(NSData *)data
編輯MyDocument類(lèi)的readFromData:ofType:error:方法
- ofType:(NSString *)typeName
- error:(NSError **)outError
- {
- NSLog(@"About to read data of type %@", typeName);
- NSMutableArray *newArray = nil;
- @try {
- newArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
- }
- @catch (NSException *e) {
- if (outError) {
- NSDictionary *d = [NSDictionary
- dictionaryWithObject:@"The data is corrupted."
- forKey:NSLocalizedFailureReasonErrorKey];
- *outError = [NSError errorWithDomain:NSOSStatusErrorDomain
- code:unimpErr
- userInfo:d];
- }
- return NO;
- }
- [self setEmployees:newArray];
- return YES;
- }
在nib文件加載后,你需要刷新UI.不過(guò)NSArrayController為你完成了這個(gè)功能.我們不需要在windowControllerDidLoadNib:方法中多做什么. 我們?cè)?3章將會(huì)修改這個(gè)方法
- - (void)windowControllerDidLoadNib:(NSWindowController *)aController
- {
- [super windowControllerDidLoadNib:aController];
- }
注意,在打開(kāi)或創(chuàng)建一個(gè)document時(shí),會(huì)詢(xún)問(wèn)我們的document類(lèi):需要load那個(gè)nib文件.現(xiàn)在我們也不需要修改這個(gè)方法
- - (NSString *)windowNibName
- {
- return @"MyDocument";
- }
因?yàn)槲覀兗せ盍藆ndo 機(jī)制,所以在編輯了document后, window會(huì)自動(dòng)標(biāo)注為編輯過(guò).
現(xiàn)在,我們的程序能夠讀寫(xiě)文件了.編譯運(yùn)行程序,試試看吧,看上去都能工作正常. 不過(guò)我們保存的文件的后綴名為.???? ,我們需要在Info.plist中給它定義一個(gè)后綴名
#p#
設(shè)置后綴名和圖標(biāo)
我們將為RaiseMan 文件添加后綴.rsmn 和一個(gè)圖標(biāo). 首先找到一個(gè).icns文件并拷貝到我們的工程中. 就使用
- /Developer/Examples/Appkit/CompositeLab/BBall.icns
吧.把他從Finder中拖到XCode的Resources組中.如圖10.3
XCode會(huì)彈出一個(gè)頁(yè)面,確保勾選Copy items into destination group's folder 如圖10.4.這樣將會(huì)包icon文件拷貝到我們的工程目錄中
在XCode中選定RaiseMan Target, 從File菜單中選擇Get Info, 來(lái)設(shè)置document-type屬性. 在Properties頁(yè)中,設(shè)置identifier為com.bignerdranch.RaiseMan. 設(shè)置Icon file 為BBall. 在document-types中,設(shè)置name為RaiseMan Doc. Extensions為rsmn. icon file為BBall.參考圖10.5
編譯運(yùn)行程序.我們?cè)俅卧囋嚤4婧痛蜷_(kāi)功能. 在Finder中, 我們的.rsmn文件的圖標(biāo)變成了BBall.icns
一個(gè)程序其實(shí)是一個(gè)目錄. 包含了程序用到的nib 文件, 圖像,聲音和可執(zhí)行代碼. 在Terminal,試試輸入
- cd /Applications/TextEdit.app/Contents
- ls
可以看到3個(gè)有趣的東西
Info.plist文件. 包含了該程序的信息, 文件類(lèi)型和相關(guān)的圖標(biāo). Finder會(huì)使用這些信息
MacOS/目錄. 這里包含了可執(zhí)行代碼
Resources/目錄. 這里包含了程序用到的圖像,聲音和nib文件,你還可以看到不同語(yǔ)言的本地化資源
#p#
思考:避免死循環(huán)
聰明的讀者可能會(huì)懷疑:""如果對(duì)象A使對(duì)象B進(jìn)行encode,對(duì)象B使對(duì)象C進(jìn)行encode,而對(duì)象C又使得對(duì)象A進(jìn)行encode. 這樣不是會(huì)產(chǎn)生無(wú)窮循環(huán)嗎?"" 沒(méi)錯(cuò),確實(shí)會(huì)發(fā)生這種情況,好在NSKeyedArchiver類(lèi)設(shè)計(jì)好了避免這種情況發(fā)送.
當(dāng)encode一個(gè)對(duì)象的時(shí)候,會(huì)將一個(gè)唯一標(biāo)識(shí)同時(shí)放到流中.并建立一個(gè)表,一旦archive對(duì)象,就會(huì)把該對(duì)象和它的唯一標(biāo)識(shí)聯(lián)系起來(lái). 如果下次又要encode同一個(gè)對(duì)象,NSKeyedArchiver會(huì)先瀏覽這個(gè)表,看是否已經(jīng)encode過(guò),并只會(huì)把唯一標(biāo)識(shí)放置到流中.
當(dāng)從流中decode出對(duì)象時(shí), NSKeyedUnarchiver同樣會(huì)生成一個(gè)表,把encode對(duì)象和唯一標(biāo)識(shí)關(guān)聯(lián)起來(lái).如果發(fā)現(xiàn)流中只有唯一標(biāo)識(shí)[說(shuō)明之前有encode這個(gè)對(duì)象],unarchiver就會(huì)在表中來(lái)查找這個(gè)對(duì)象,而不是再生成一個(gè)新的對(duì)象.
NSCoder有一個(gè)方法容易使讀者和上面的思想產(chǎn)生混淆
- - (void)encodeConditionalObject:(id)anObject forKey:(NSString *)aKey
當(dāng)對(duì)象A有一個(gè)指針指向?qū)ο驜, 但是對(duì)象A不需要知道對(duì)象B是否被archive[是否存在]. 不過(guò)如果另外一個(gè)對(duì)象已經(jīng)archive了B,對(duì)象A又希望將對(duì)象B的唯一標(biāo)識(shí)在encode的時(shí)候能夠放置到流中. [也就是說(shuō)對(duì)象A不會(huì)主動(dòng)encode B, 如果存在對(duì)象B ,那么就指向它,否則就指向空]
舉個(gè)例子,我們需要給Engine對(duì)象編寫(xiě)它的encodeWithCoder:方法. 它有一個(gè)成員變量為car,是一個(gè)指向Car對(duì)象的指針(發(fā)動(dòng)機(jī)是汽車(chē)的一部分). 我們?cè)?strong>archiving Engine對(duì)象時(shí),不希望整個(gè)Car對(duì)象被archived. 不過(guò)如果該Car對(duì)象之前在其他地方archived過(guò), 我們又希望 Engine對(duì)象的car指針指向它. 在這種情況下,我們就要要求Engine對(duì)象有條件的來(lái)encode car指針指向的對(duì)象了. 如圖10.6
思考: 創(chuàng)建Protocol
創(chuàng)建自己的Protocol非常簡(jiǎn)單.下面的Protocol有兩個(gè)方法,它可能在Foo.h文件中
- @protocol Foo
- - (void)fido:(int)x;
- - (float)rex;
- @end
在Objective-C 2.0中,新增了語(yǔ)法@optional. 可以用來(lái)指定那些方法是必須那些方法是可選的
- @protocol Foo
- - (void)fido:(int)x;
- - (float)rex;
- @optional
- - (int)rover;
- - (void)spot:(int)x;
- @end
在這個(gè)例子中fido: 和rex方法是必須的,而rover和spot:方法是可選的
如果你有一個(gè)類(lèi)要實(shí)現(xiàn)Foo protocol和NSCoding protocol. 應(yīng)該這樣做
- #import "Spunky.h"
- #import "Foo.h"
- @interface ZsaZsa:Spunky <Foo, NSCoding>
- ...etc...
- @end
我們不需要重新聲明父類(lèi)和protocol中聲明過(guò)的方法.所以,在本例中, ZsaZsa類(lèi)接口文件中不需要再次聲明Spunky和Foo,NSCoding中聲明過(guò)的方法
通用類(lèi)型描述[UTI]
在使用計(jì)算機(jī)時(shí),一直有這樣一個(gè)問(wèn)題:"數(shù)據(jù)是怎么樣展現(xiàn)出來(lái)的". 對(duì)于Mac, 這個(gè)問(wèn)題在不同的幾個(gè)地方都會(huì)遇到:當(dāng)從Finder打開(kāi)一個(gè)文件時(shí).當(dāng)通過(guò)剪貼板拷貝數(shù)據(jù)時(shí),當(dāng)通過(guò)Spotlight索引文件時(shí),當(dāng)使用Quicklook預(yù)覽文件時(shí).這個(gè)問(wèn)題有一些答案: 文件擴(kuò)展名, creator codes,和MIME類(lèi)型
Apple選擇的長(zhǎng)期解決途徑是通用類(lèi)型描述(UTIs). 一個(gè)UTI是一個(gè)描述了文件類(lèi)型的字符串. UTIs按一定層次關(guān)系組織.
我們?cè)贗nfo.plist文件中定義程序可以讀寫(xiě)的UTIs-包括新建的和自定義的UTIs. Info.plist文件是XML格式,包含了目錄以及key-value. 可以使用一個(gè)新key UTExporterTypeDeclarations來(lái)export新的UTIs. 例如,如果你想給RaiseMain Document添加一個(gè)UTI. 可以在Info.plist文件中添加如下描述:
- <array>
- <dict>
- <key>UTTypeIdentifier</key>
- <string>com.bignerdranch.raiseman-doc</string>
- <key>UTTypeDescription</key>
- <string>RaiseMan Document</string>
- <key>UTTypeConformsTo</key>
- <array>
- <string>public.data</string>
- </array>
- <key>UTTypeTagSpecification</key>
- <dict>
- <key>com.apple.ostype</key>
- <string>rsmn</string>
- <key>public.filename-extension</key>
- <array>
- <string>rsmn</string>
- </array>
- </dict>
- </dict>
- </array>
當(dāng)然,我們也通過(guò)properties inspector來(lái)使用UTI.如圖10.7
你可以在Apple的文檔中找到所有的系統(tǒng)定義的UTIs
小結(jié):iPhone開(kāi)發(fā)應(yīng)用之Archiving NSCoder教程的內(nèi)容介紹完了,希望本文對(duì)你有所幫助!