這節課主要講幾個部分,一個是final project的準則,然後是持久化(Persistence)問題。
Persistence
持久化(Persistence)意思就是當你退出app的時候它還會存在。NSUserDefaults就是一個非常簡單的持久化方案,不過這有限制,它只能是很小的東西,通常是些使用者選項。
如何把那些大資料的東西持久化?
第一個方法,把東西持久化的第一個簡單的方式有點像用NSUserDefaults裡的property list來實現的進化版,property list是我們自訂的一個概念,是NSArray、NSDictionary、NSNumber、NSString、 NSDate和NSData的組合。所有以上這些都有API可以用來儲存,NSUserDefaults也有些API可以。
NSData、NSArray和NSDictionary裡還有很重要的方法writeToURL:atomically:,這個方法就是把一個array寫到一個檔案系統中URL表示的地方,atomically就是如果檔案已經存在,它會把檔案移到一邊,然後寫到一個新的檔案,關掉這個新的檔案。實際上就是寫到一個臨時命名的檔案,然後關掉,把另一個移走,有點原子化操作的感覺,不能寫到一半就停下來。writeToURL之後,為了把東西取回來,可以用initWithContentsOfURL或者dataWithContentsOfURL。這些讀寫方法,不管是發送到NSData或者NSArray或者NSDictionary,就是想讓一個相同類型的對象去讀回來,它的內部可以就是些property
list的東西。
還有另一個類叫做NSPropertyListSerialization,它所做的事情就是把property list轉化成NSData,反之亦然。這樣它可以將property list轉化成一堆位元,然後就可以寫到磁碟上,或儲存到網路。也可以通過網路讀取一堆位元再通過NSPropertyListSerialization把它們轉化回property list。
關於儲存的另外一個方法就是通過對象的映像圖(arbitrary graphs)來進行歸檔對象(Archiving Objects)的儲存。
把東西儲存到檔案系統當中。
然後是SQLite
關於持久化儲存的重頭部分就是Core Data,這是在SQL上層的一個物件導向的資料庫機制。
Archiving
在做對象圖歸檔的時候有很多陷阱,歸檔很適合做循環圖表。Archiving能發現指標實際所指的對象或者指標相互指著,然後當unarchives的時候又恢複那個指標。但你要考慮清楚來使你構建的對象圖有意義,最好的例子可能是在xcode裡面建立的view層級。
當從物件程式庫中拖一些東西到螢幕上,就是在執行個體化UIView,執行個體化UIViewController,它們都是generic的,所以才需要去inspector面板來改變它們的類。
基本上就是在這個圖裡面的對象都必須實現NSCoding這個protocol,然後這個protocol裡面有兩個重要方法:
- (void)encodeWithCoder:(NSCoder *)coder; - initWithCoder:(NSCoder *)coder;
第一個是把自己放進archive,第二是把你從archive中取出來用的。這就是viewController出現在storyboard時不用調用它們的指定初始化的原因,因為它們用了這套初始化。這套歸檔系統在對它們做alloc initWithCoder,因為它們之前用encodeWithCoder把自己儲存起來了。在UIView裡沒有調用initWithFrame,而是用frame對應的encodeWithCoder和initWithCoder來代替。
- (void)encodeWithCoder:(NSCoder *)coder { [super encodeWithCoder:coder]; [coder encodeFloat:scale forKey:@“scale”]; [coder encodeCGPoint:origin forKey:@“origin”]; [coder encodeObject:expression forKey:@“expression”];}
必須保證initWithCoder和encodeWithCoder是相對應的。
- initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; scale = [coder decodeFloatForKey:@“scale”]; expression = [coder decodeObjectForKey:@“expression”]; origin=[coderdecodeCGPointForKey:@“origin”]; //notethatorderdoesnotmatter}
怎麼來實現這些呢?NSKeyedArchiver中的類方法:
+ (NSData *)archivedDataWithRootObject:(id <NSCoder>)rootObject;
可以傳遞一個根對象,比如在storyboard裡面的根對象就可能是頂層的view controller。你要做的就是確保裡面所有的對象都實現NSCoder協議。
NSKeyedUnarchiver中的類方法:
+ (id <NSCoder>)unarchiveObjectWithData:(NSData *)data;
這正好相反,你提供一個已經歸檔的NSData,然後返回被encode的根對象。
id <NSCoder> object = ...; NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object]; id <NSCoder> dup = [NSKeyedArchiver unarchiveObjectWithData:data];
如果有一個對象,通過第二行將得到關於這個對象的NSData。
如果encode一個view使它的super view被歸檔,你會被拒絕,除非它處於正確的層級,如果它在頂部它會被拒絕,但如果在內部,就會執行。
File System
ios是基於Unix的,底層都是Unix的檔案系統,這裡有檔案系統保護,不可能看到所有的東西,而且也不能隨意的寫入。
只能在sandbox中做寫入,為什嗎?為了安全。當在裝置上刪除app時,也會刪除所有相關的資料。通過在sandbox中進行寫操作,所有應用程式的東西不管是使用者建立的還是應用程式自己在sandbox裡面建立的一旦移除,所有的都會被移除。
sandbox到底是什嗎?這裡面有應用程式的bundle目錄,應用程式不是單獨的大的二進位檔案,它實際上是一個目錄,裡面有可執行程式、二進位檔案、storyboard及拖進來的圖片,所有東西都在裡面,這就是應用程式的bundle。sandbox裡的目錄本身是不可寫的,不能在目錄裡寫入東西。這基本上就是一個用xcode建立的app的唯讀副本。documents目錄是sandbox中的一個重要目錄,這些地方是用來儲存那些被使用者視為是自己的文檔的。還有一個緩衝目錄,這裡都是一些我們寫出來的東西,使用者一般不會認為這些是文檔,而且這些文檔的存在都很短暫,沒有了也不會影響到使用者。documentation裡的關鍵東西是NSSearchPathDirectory。
如何才能得到這些目錄,如何得到這些目錄的URL?如果想在application目錄中寫入,但又不能寫入,那麼要做的就是將application包裡面的東西拷貝到sandbox中任意一個可寫的地方然後在那裡寫入。如果你想將一個資料庫連接到你的app,或寫入那些資料庫,得將它們拷貝出來,無論是拷到文檔目錄,或是緩衝目錄,總之是可以寫入了。
如何搞到這些目錄的路徑呢?使用這個方法:
- (NSArray *)URLsForDirectory:(NSSearchPathDirectory)directory inDomains:(NSSearchPathDomainMask)domainMask; //NSUserDomainMask
NSURL中有個API可以獲得一個URL的清單,但得傳入想要的目錄類型,譬如文檔目錄、緩衝目錄。以上方法和NSURL的方法,它們返回一個路徑的array,所以當我請求緩衝目錄時,將得到一個URL的array,或是路徑字串的數組。
NSFileManager為檔案系統提供實用操作,這不是用來讀寫它們自身檔案的類。你可以用它來找出檔案到底有多大,刪除那些陳舊的需要被踢出的快取檔案,也可以用它來找出譬如當應用程式啟動時,緩衝裡面都有哪些檔案。它是安全執行緒的,只要不在兩個不同的線程中使用同一個執行個體。
NSString也有一些檔案系統有關的東西,特別是製作路徑,一般用NSURL來指定一個連結,有時會用字串來構建URL。
- (NSString *)stringByAppendingPathComponent:(NSString *)component;
這可以在一個路徑中新增內容。還可以把字串的內容寫到檔案裡去,必須要指定用哪種編碼,譬如ASCII、ISOLatin1,
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)flag encoding:(NSStringEncoding)encoding //e.g.ASCII,ISOLatin1,etc. error:(NSError **)error;
也可以從檔案讀取string:
- (NSString *)stringWithContentsOfFile:(NSString *)path usedEncoding:(NSStringEncoding *)encoding error:(NSError **)error;
SQLite
SQLite是指SQL檔案儲存在單一一個檔案裡,儲存在一個單一的檔案中,它速度很快,只佔用很小的記憶體。它基於交易處理,是真正的SQL。這不是基於伺服器的SQL,而是基於檔案的,所以它是並發性的。如果app中有兩個線程,都要寫入到SQL資料庫中,這可以正常運行。但它只是做簡單的鎖線程和並發。
以下就是它的API:
intsqlite3_open(constchar*filename,sqlite3**db); //get a database into db int sqlite3_exec(sqlite3 *db, // execute SQL statements const char *sql, int (*callback)(void *, int, char **, char **), void *context, char **error);int mycallback(void *context,int count,char **values,char **cols); //data returned int sqlite3_close(sqlite3 *db); // close the database
當你開啟SQL檔案時,你會得到一個SQL資料庫指標,然後要執行SQL語句。向資料庫遞交你的SQL語句,一些SQL語句就會回調然後在一張表中將你請求的資料返回給你,返回的也可能是error。回呼函數通常是這樣的格式:
int mycallback(void *context,int count,char **values,char **cols);
然後就可以關閉了。你要做的就是第三行const char *sql,這是執行SQL語句的地方。
原文:點擊開啟連結