固化是由iOSSDK提供的一種保存和讀取對(duì)象的機(jī)制,使用非常廣泛。當(dāng)應(yīng)用固化某個(gè)對(duì)象時(shí),會(huì)將該對(duì)象的所有屬性存入指定的文件。當(dāng)應(yīng)用解固(unarchive)某個(gè)對(duì)象時(shí),會(huì)從指定的文件讀取相應(yīng)的數(shù)據(jù),然后根據(jù)數(shù)據(jù)還原對(duì)象。
為了能夠固化或解固某個(gè)對(duì)象,相應(yīng)對(duì)象的類(lèi)必須遵守NSCoding協(xié)議,并且實(shí)現(xiàn)兩個(gè)必需方法:encodeWithCoder:和initWithCoder:
無(wú)論編碼哪種類(lèi)型的數(shù)據(jù),必須有相應(yīng)的鍵才能存入NSCoder對(duì)象。這個(gè)鍵是字符串,負(fù)責(zé)標(biāo)識(shí)相應(yīng)的屬性。按照約定,編碼某個(gè)屬性時(shí)要使用的鍵就是該屬性的名稱。
當(dāng)應(yīng)用需要編碼某個(gè)對(duì)象時(shí)(encodeObject:forKey:中的第一個(gè)參數(shù)),會(huì)向該對(duì)象發(fā)送encodeWithCoder:消息。收到該消息的對(duì)象需要編碼自己的屬性,所以也會(huì)向這些屬性發(fā)送encodeWithCoder:消息(見(jiàn)圖181)。因此,對(duì)象的編碼過(guò)程是一個(gè)遞歸過(guò)程:編碼中的對(duì)象會(huì)再編碼其他對(duì)象。
XIB文件也是基于固化機(jī)制的。當(dāng)讀者在Xcode中將某個(gè)視圖拖曳至畫(huà)布時(shí),Xcode會(huì)創(chuàng)建相應(yīng)的對(duì)象。保存XIB文件時(shí),Xcode會(huì)將這些視圖固化至指定的文件(UIView遵守NSCoding協(xié)議)。當(dāng)應(yīng)用需要載入XIB文件時(shí),就會(huì)解固XIB文件中的視圖。和普通的固化文件相比,XIB文件會(huì)略有差別,但是兩者保存和載入的流程大致相同。
應(yīng)用沙盒
每個(gè)iOS應(yīng)用都有自己專屬的應(yīng)用沙盒。應(yīng)用沙盒就是文件系統(tǒng)中的目錄,但是iOS系統(tǒng)會(huì)將每個(gè)應(yīng)用的沙盒目錄與文件系統(tǒng)的其他部分隔離(見(jiàn)圖183)。應(yīng)用必須“待”在自己的沙盒里,并只能訪問(wèn)自己的沙盒。


歸檔,請(qǐng)參考項(xiàng)目study或者網(wǎng)上。

當(dāng)應(yīng)用啟動(dòng)后,會(huì)進(jìn)入激活狀態(tài)(activestate),可以顯示界面、接收事件并處理事件。
當(dāng)應(yīng)用處在激活狀態(tài)時(shí),可能會(huì)被某個(gè)系統(tǒng)事件打斷,臨時(shí)進(jìn)入未激活狀態(tài)(inactivestate)。這類(lèi)系統(tǒng)事件包括收到短消息、收到推送、來(lái)電或鬧鐘到點(diǎn)等。發(fā)生系統(tǒng)事件時(shí),iOS會(huì)顯示相應(yīng)的提示界面并遮住當(dāng)前應(yīng)用的部分界面。當(dāng)應(yīng)用處于未激活狀態(tài)時(shí),其大部分界面是可見(jiàn)的(iOS顯示的提示界面只會(huì)遮住部分窗口),也可以執(zhí)行代碼,但是不會(huì)接收事件。通常情況下,應(yīng)用只會(huì)在未激活狀態(tài)停留很短的時(shí)間。按下位于iOS設(shè)備頂部的鎖定按鈕,當(dāng)前處于激活狀態(tài)的應(yīng)用會(huì)切換至未激活狀態(tài),并且會(huì)保留未激活狀態(tài),直到設(shè)備解鎖。
當(dāng)用戶按下主屏幕按鈕(Homebutton)時(shí),或者通過(guò)某種途徑切換至另一個(gè)應(yīng)用時(shí),當(dāng)前運(yùn)行的應(yīng)用會(huì)從激活狀態(tài)切換為后臺(tái)運(yùn)行狀態(tài)(backgroundstate)(實(shí)際上,應(yīng)用會(huì)先從激活狀態(tài)切換為未激活狀態(tài),停留極短的時(shí)間,然后再進(jìn)入后臺(tái)運(yùn)行狀態(tài))。處于后臺(tái)運(yùn)行狀態(tài)的應(yīng)用仍然可以執(zhí)行代碼,但是其界面不再可見(jiàn),也不能接收事件。默認(rèn)情況下,進(jìn)入后臺(tái)運(yùn)行狀態(tài)的應(yīng)用有大約10秒的時(shí)間,然后會(huì)進(jìn)入掛起狀態(tài)(suspendedstate)。讀者在開(kāi)發(fā)應(yīng)用時(shí),不能依賴這個(gè)不確定的時(shí)間差,而是應(yīng)該盡快保存用戶數(shù)據(jù)并釋放系統(tǒng)資源。
處于掛起狀態(tài)的應(yīng)用不能執(zhí)行代碼,其界面也不可見(jiàn),并且會(huì)釋放在掛起狀態(tài)下無(wú)須使用的所有資源。掛起的應(yīng)用就像是進(jìn)行了“低溫干燥”的處理,可以在用戶再次啟動(dòng)時(shí)快速解凍。

當(dāng)iOS系統(tǒng)認(rèn)為當(dāng)前可用的內(nèi)存過(guò)低時(shí),會(huì)根據(jù)需要終止處于掛起狀態(tài)的應(yīng)用。當(dāng)系統(tǒng)有足夠多的空余內(nèi)存時(shí),處于掛起狀態(tài)的應(yīng)用可以一直保留該狀態(tài)。當(dāng)處于掛起狀態(tài)的應(yīng)用即將被系統(tǒng)終止時(shí),就不會(huì)收到相應(yīng)的通告,系統(tǒng)會(huì)直接將其從內(nèi)存中移除(終止后的應(yīng)用,其圖標(biāo)可能還會(huì)留在多任務(wù)界面中,按下圖標(biāo)會(huì)重新啟動(dòng)應(yīng)用)。
通過(guò)NSData將數(shù)據(jù)寫(xiě)入文件
向某個(gè)NSData對(duì)象發(fā)送writeToFile:atomically:消息,可以將該對(duì)象中的數(shù)據(jù)寫(xiě)入指定的文件。writeToFile:atomically:的第一個(gè)實(shí)參負(fù)責(zé)指定文件路徑。第二個(gè)實(shí)參atomically是一個(gè)布爾值,當(dāng)atomically為YES時(shí),NSData對(duì)象會(huì)先將數(shù)據(jù)寫(xiě)入某個(gè)臨時(shí)文件,然后等寫(xiě)入操作成功后再將文件移至第一個(gè)實(shí)參所指定的路徑,并覆蓋已有的文件。這樣,即使應(yīng)用在寫(xiě)入文件的過(guò)程中崩潰,也不會(huì)損壞現(xiàn)有的數(shù)據(jù)。
需要注意的是,這種將數(shù)據(jù)寫(xiě)入文件的方式不是固化。雖然NSData對(duì)象自身也可以固化,但writeToFile:atomically:的工作原理是將NSData對(duì)象中的數(shù)據(jù)逐字節(jié)復(fù)制到文件中。
NSNotificationCenter和內(nèi)存過(guò)低警告
當(dāng)iOS設(shè)備內(nèi)存不足時(shí),系統(tǒng)會(huì)向運(yùn)行中的應(yīng)用發(fā)送一條內(nèi)存過(guò)低警告通知。應(yīng)用收到該通知后,應(yīng)該立刻釋放當(dāng)前不需要使用的資源以及后期可以重新創(chuàng)建的對(duì)象。視圖控制器在收到該通知的同時(shí)還會(huì)收到didReceiveMemoryWarning消息。
除了視圖控制器,其他對(duì)象中也可能存在需要及時(shí)釋放的數(shù)據(jù)。BNRImageStore對(duì)象就是如此:它可以釋放當(dāng)前沒(méi)有顯示的UIImage對(duì)象,等到需要時(shí)再?gòu)奈募到y(tǒng)載入。
為了使視圖控制器以外的對(duì)象也可以在內(nèi)存不足時(shí)釋放數(shù)據(jù),必須使用通知中心(notificationcenter)。每一個(gè)iOS應(yīng)用中都有一個(gè)NSNotificationCenter對(duì)象,讀者可以將其視作智能的通知欄,對(duì)象可以將自己注冊(cè)為某個(gè)通知的觀察者(observer)
內(nèi)存過(guò)低警告通知的名稱是UIApplicationDidReceiveMemoryWarningNotification。凡是需要自己處理內(nèi)存過(guò)低警告的對(duì)象,都可以注冊(cè)并接收這個(gè)通知。
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clearCache:)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];

當(dāng)系統(tǒng)發(fā)出內(nèi)存過(guò)低警告通知時(shí),注冊(cè)對(duì)象會(huì)收到UIApplicationDidReceiveMemoryWarningNotification通知,并調(diào)用clearCache:方法清除緩存。

NSNotification和NSNotificationCenter與用戶看到的推送通知(push notification)或本地通知(local notification)無(wú)關(guān),NSNotificationCenter只用來(lái)處理應(yīng)用內(nèi)部的對(duì)象通信。
通知允許多個(gè)對(duì)象將回調(diào)函數(shù)注冊(cè)到相同的事件上。同一個(gè)通知可以有許多觀察者,當(dāng)通知發(fā)布后,所有觀察者都會(huì)執(zhí)行之前注冊(cè)的回調(diào)函數(shù)(同時(shí)執(zhí)行,沒(méi)有先后順序)。如果多個(gè)對(duì)象需要監(jiān)聽(tīng)同一個(gè)事件,則通知是最好的解決方案。例如,許多對(duì)象都需要在設(shè)備方向發(fā)生變化時(shí)執(zhí)行一系列操作,因此該事件發(fā)生時(shí)系統(tǒng)會(huì)發(fā)布名為UIDeviceOrientationDidChangeNotification的通知。
模型視圖控制器存儲(chǔ)設(shè)計(jì)模式: Model-View-Controller-Store
標(biāo)準(zhǔn)的模型視圖控制器設(shè)計(jì)模式要求控制對(duì)象負(fù)責(zé)模型對(duì)象的保存和讀取。但在實(shí)際情況中,這樣做的效果并不是很好??刂茖?duì)象的主要任務(wù)是處理模型對(duì)象和視圖對(duì)象之間的交互,如果還要負(fù)責(zé)實(shí)現(xiàn)所有的存取細(xì)節(jié),則可能會(huì)“不堪重負(fù)”。為此,可以將模型對(duì)象的存取邏輯移入另一類(lèi)對(duì)象:存儲(chǔ)對(duì)象。
通過(guò)存儲(chǔ)對(duì)象所提供的方法,控制對(duì)象可以獲取或保存模型對(duì)象。保存和讀取模型對(duì)象的實(shí)現(xiàn)細(xì)節(jié)全部由存儲(chǔ)對(duì)象負(fù)責(zé)。以本章中的Homepwner為例,存儲(chǔ)對(duì)象(BNRItemStore對(duì)象)會(huì)通過(guò)某個(gè)指定的文件來(lái)創(chuàng)建和保存BNRItem對(duì)象。除了文件,存儲(chǔ)對(duì)象也可以使用數(shù)據(jù)庫(kù)、Web服務(wù)或其他途徑來(lái)為控制對(duì)象創(chuàng)建模型對(duì)象。
除了能夠簡(jiǎn)化控制器類(lèi),這種設(shè)計(jì)模式的另一個(gè)好處是:不用修改控制對(duì)象或應(yīng)用的其他部分,就能修改存儲(chǔ)對(duì)象的工作方式。這種修改可以很簡(jiǎn)單,例如修改數(shù)據(jù)的目錄結(jié)構(gòu);也可以很復(fù)雜,例如修改數(shù)據(jù)的格式。因此,無(wú)論某個(gè)應(yīng)用有多少個(gè)需要存取數(shù)據(jù)的控制對(duì)象,都只需要修改相應(yīng)的存儲(chǔ)對(duì)象即可。
除了self,還有一個(gè)名為_(kāi)cmd的隱式變量,它是當(dāng)前方法的選擇器。NSStringFromSelector函數(shù)可以根據(jù)指定的選擇器生成相應(yīng)的字符串。
文件系統(tǒng)的讀取和寫(xiě)入
是在編寫(xiě)iOS應(yīng)用時(shí),標(biāo)準(zhǔn)I/O函數(shù)并不常用。這是因?yàn)檫€有其他的途徑可以讀/寫(xiě)二進(jìn)制數(shù)據(jù)或文本數(shù)據(jù),而且更方便。如果要讀/寫(xiě)二進(jìn)制數(shù)據(jù),則可以使用NSData。如果要讀/寫(xiě)文本數(shù)據(jù),則可以分別使用NSString的實(shí)例方法initWithContentsOfFile:和writeToFile:atomically:encoding:error:
應(yīng)用在執(zhí)行方法時(shí),可能會(huì)因?yàn)楦鞣N原因而導(dǎo)致失敗。例如,將數(shù)據(jù)寫(xiě)入文件時(shí)會(huì)因?yàn)槁窂綗o(wú)效而失敗,也可能會(huì)因?yàn)闆](méi)有足夠的權(quán)限而失敗。NSError對(duì)象的作用是保存失敗的原因。向NSError對(duì)象發(fā)送localizedDescription消息,可以得到相應(yīng)錯(cuò)誤的描述信息。因?yàn)檫@些信息是給人看的(humanreadable),所以可以向用戶顯示或通過(guò)控制臺(tái)輸出。
通常情況下,只有當(dāng)某個(gè)方法在執(zhí)行代碼時(shí)發(fā)生了錯(cuò)誤,才有必要?jiǎng)?chuàng)建NSError對(duì)象。也就是說(shuō),負(fù)責(zé)創(chuàng)建NSError對(duì)象的應(yīng)該是被調(diào)用的方法,而不是調(diào)用方。為了能讓調(diào)用方得到被調(diào)用的方法所創(chuàng)建的NSError對(duì)象,需要將指針變量的地址(&err)作為實(shí)參傳給相應(yīng)的方法。假設(shè)有某個(gè)方法(方法A),該方法的某個(gè)實(shí)參可以返回一個(gè)NSError對(duì)象,那么在調(diào)用方法A前,需要先創(chuàng)建一個(gè)類(lèi)型為NSError*的指針變量(局域變量)。注意,這里不需要?jiǎng)?chuàng)建NSError對(duì)象,因?yàn)檫@是方法A的任務(wù)。然后將這個(gè)局部變量的地址(&err)傳給可能會(huì)出錯(cuò)的方法A。如果方法A在執(zhí)行代碼時(shí)發(fā)生了錯(cuò)誤,就會(huì)創(chuàng)建一個(gè)NSError對(duì)象,并將新創(chuàng)建的對(duì)象的地址賦給傳入的指針。如果調(diào)用方不關(guān)心NSError對(duì)象,則可以傳入nil。
很多語(yǔ)言將所有非預(yù)期(unexpected)錯(cuò)誤作為異常拋出,但是ObjectiveC的異常只用來(lái)處理程序錯(cuò)誤。當(dāng)異常拋出時(shí),詳細(xì)信息都封裝在NSException對(duì)象中。這些信息主要用來(lái)幫助程序員調(diào)試代碼,例如“試圖在只有兩個(gè)對(duì)象的數(shù)組中訪問(wèn)第七個(gè)對(duì)象。”NSException中還包括方法調(diào)用棧信息,指明了拋出異常的代碼位置。
NSException和NSError的使用場(chǎng)景不同。如果需要指出程序員的編碼錯(cuò)誤,則應(yīng)該使用NSException。例如,一個(gè)方法只能接受奇數(shù)作為參數(shù),但是程序員在調(diào)用該方法時(shí)傳入了偶數(shù),這時(shí)應(yīng)該拋出異常,以方便程序員解決代碼錯(cuò)誤。相反,對(duì)于預(yù)期(expected)錯(cuò)誤,如用戶錯(cuò)誤和設(shè)備環(huán)境錯(cuò)誤,應(yīng)該使用NSError。例如,一個(gè)方法需要讀取用戶照片,但是沒(méi)有訪問(wèn)用戶相冊(cè)的權(quán)限,這時(shí)應(yīng)該向方法調(diào)用者返回一個(gè)NSError對(duì)象,指出不能執(zhí)行本次操作的原因。
和NSString類(lèi)似,NSDictionary和NSArray也有writeToFile:和initWithContentsOfFile:。只有當(dāng)collection對(duì)象包含可序列化(propertylistserializable)對(duì)象時(shí),才能通過(guò)writeToFile:這類(lèi)方法將數(shù)據(jù)存入文件。可序列化對(duì)象包括NSString、NSNumber、NSDate、NSData、NSArray和NSDictionary。NSArray對(duì)象或NSDictionary對(duì)象這類(lèi)文件的寫(xiě)入方法生成的都是XML格式的property list文件。
幾乎所有的操作系統(tǒng)都能讀取XML格式的propertylist文件,所以使用這種格式存儲(chǔ)數(shù)據(jù)會(huì)很方便。很多Web服務(wù)程序會(huì)采用這種格式的文件作為輸入和輸出。
iOS應(yīng)用可以在運(yùn)行時(shí)載入應(yīng)用程序包中的文件。要獲得應(yīng)用程序包中的某個(gè)文件的全路徑,需要先得到代表應(yīng)用程序包的NSBundle對(duì)象,然后通過(guò)該對(duì)象得到某個(gè)文件的全路徑,代碼如下:
//獲取代表應(yīng)用程序包的NSBundle對(duì)象
NSBundle* applicationBundle=[NSBundlemainBundle];
//通過(guò)NSBundle對(duì)象,獲得包內(nèi)名為myImage.png的文件的全路徑
NSString* path=[applicationBundle pathForResource:@“myImage” ofType:@“png”];