編輯:關於Android編程
一、技術概覽
1. Core Data 功能初窺
對於處理諸如對象生命周期管理、對象圖管理等日常任務,Core Data框架提供了廣泛且自動化的解決方案。它有以下特性。
(注:對象圖-Object graph的解釋:在面向對象編程中,對象之間有各種關系,例如對象直接引用另外的對象,或是通過引用鏈間接的引用其他對象,這些關系組成了網狀的結構。我們把這些對象(和它們之間的聯系)成為對象圖。 對象圖可大可小,有繁有簡。 只包含單個字符串對象的數組就是一個簡單的代表;而包含了application對象,引用windows, menus和相關視圖對象、其他對象這樣的結構就是復雜對象圖的例子——這是在說mainwindow.xib。
有時,你可能想要把這樣的對象圖轉化形式,讓它們可以被保存到文件中,以使其他的進程或其他的機器可以再次將保存的內容讀出,重購對象。 這樣的過程常被成之為“歸檔”(Archiving)。
有些對象圖是不完整的——通常稱之為局部對象圖(partial object graphs)。局部對象圖包含了“占位符”(Placeholder)對象,所謂”占位符“,就是一些暫時無內容的對象,它們將再後期被具體化。一個典型的例子就是nib文件中包含的File's Owner對象。
1) 對於key-value coding 和key-value observing完整且自動化的支持
除了為屬性整合KVC和KVO的訪問方法外, Core Data還整合了適當的集合訪問方法來處理多值關系。
2) 自動驗證屬性(property)值
Core Data中的managed object擴展了標准的KVC 驗證方法,以保證單個的數值在可接受的范圍之內,從而使組合的值有意義。(需校准翻譯)
3) 支持跟蹤修改和撤銷操作
對於撤銷和重做的功能,除過用基本的文本編輯外,Core Data還提供內置的管理方式。
4) 關系的維護
Core Data管理數據的變化傳播,包括維護對象間關系的一致性。
5) 在內存中和界面上分組、過濾、組織數據
6) 自動支持對象存儲在外部數據倉庫的功能
7) 創建復雜請求
你不需要動手去寫復雜的SQL語句,就可以創建復雜的數據請求。方法是在“獲取請求”(fetch request)中關聯NSPredicate(又看到這個東東了,之前用它做過正則)。NSPrdicate支持基本的功能、相關子查詢和其他高級的 SQL特性。它還支持正確的Unicode編碼(不太懂,請高人指點), 區域感知查詢(據說就是根據區域、語言設置調整查詢的行為)、排序和正則表達式。
8) 延遲操作(原文為Futures(faulting)直譯為期貨,這裡個人感覺就是延遲操作的形象說法。請高人指教)。
Core Data 使用延遲加載(lazy loading)的方式減少內存負載。 它還支持部分實體化延遲加載,和“寫時拷貝”的數據共享機制。(寫時拷貝,說的是在復制對象的時候,實際上不生成新的空間,而是讓對象共享一塊存儲區域,在其內容發生改變的時候再分配)。
9) 合並的策略
Core Data 內置了版本跟蹤和樂觀鎖定(optimistic locking)來支持多用戶寫入沖突的解決。
注:樂觀鎖,假定數據一般不出現沖突,所以在數據提交更新的時候,才對數據的沖突進行檢測,如果沖突了,就返回沖突信息。
10) 數據遷移
就開發工作和運行時資源來說,處理數據庫架構的改變總是很復雜。Core Data的schema migration工具可以簡化應對數據庫結構變化的任務, 而且在某些情況下,允許你執行高效率的數據庫原地遷移工作。
11) 可選擇針對程序Controller層的集成,來支持UI的顯示同步
Core Data在iPhone OS之上 提供NSFetchedResultsController對象來做相關工作,在Mac OS X上,我們用Cocoa提供的綁定(Binding)機制來完成。
2. 為何要使用Core Data
使用Core Data有很多原因,其中最簡單的一條就是:它能讓你為Model層寫的代碼的行數減少為原來的50%到70%。 這歸功於之前提到的Core Data的特性。更妙的是,對於上述特性你也既不用去測試,也不用花功夫去優化。
Core Data擁有成熟的代碼,這些代碼通過單元測試來保證品質。應用Core Data的程序每天被世界上幾百萬用戶使用。通過了幾個版本的發布,已經被高度優化。 它能利用Model層的信息和運行時的特性,而不通過程序層的代碼實現。 除了提供強大的安全支持和錯誤處理外,它還提供了最優的內存擴展性,可實現有競爭力的解決方案。不使用Core Data的話,你需要花很長時間來起草自己的方案,解決各種問題,這樣做效率不高。
除了Core Data本身的優點之外,使用它還有其他的好處: 它很容易和Mac OS X系統的Tool chain集成;利用Model設計工具可以按圖形化方式輕松創建數據庫的結構;你可以用Instruments的相關模板來測試Core Data的效率並debug。 在Mac OS X的桌面程序中,Core Data還和Interface Builder集成(打開Inspector可以看到有binding的選項,這個東東iPhone上木有。。。),按照model來創建UI變的更簡單了。 這些功能能更進一步的幫助你縮短設計、開發、測試程序的周期。
3. Core Data不是。。。
看了前面的介紹之後,我們還需要了解一下關於Core Data常見的誤解:
1) Core Data不是一個關系型數據庫,也不是關系型數據庫管理系統(RDBMS)。
Core Data 為數據變更管理、對象存儲、對象讀取恢復的功能提供了支持。 它可以使用SQLite作為持久化存儲的類型。 它本身並不是一個數據庫(這點很重要,比如,你可以使用Core Data來記錄數據變更,管理數據,但並不能用它向文件內存儲數據)。
2) Core Data不是銀彈
它並不能取代你寫代碼的工作。雖然可以純粹使用XCode的數據建模工具和Interface Builder來編寫復雜程序,但在更多的程序中,你都自己動手寫代碼。
3) Core Data並不依賴於Cocoa Bindings
Core Data + Cocoa Binding = 減少代碼數量。但Core Data完全可以在沒有bindings的條件下使用。例如,可以編寫一個沒有UI,但包含Core Data的程序。
二、Core Data基礎
1. Core Data基本架構
在大部分程序中,你要能通過某種方式打開一個包含對象歸檔的文件, 這個文件內至少要有一個根對象的引用。另外,還得能將所有的對象歸檔到文件中,如果你想要實現撤銷的功能,就還要記錄對象的更改情況。例如,在 Employee的示例程序中,你要能打開一個包含有employee和department對象歸檔的文件,而且這個文件至少包含了一個根對象——這 裡,是一個包含所有employee的數組——請參考例圖Figure 1。 相應的,你還要能將程序中的employee、department對象歸檔到文件中去。
Figure 1 按照Core Data文檔結構管理的對象示意圖
使用Core Data的框架,大多數的功能都可以自動實現,因為我們有managed object context(管理對象的上下文,有時直接叫"Context")。managed object context就像是一個關卡,通過它可以訪問框架底層的對象——這些對象的集合我們稱之為"persistence stack"(數據持久棧)。 managed object context作為程序中對象和外部的數據存儲的中轉站。棧的底部是persistence object stores(持久化數據存儲),請看Figure 2的示意圖。
Figure 2 使用Core Data的文檔管理示意圖
Core Data的使用並不限制在基於文檔的程序中(document-based application)。你也能創建一個包含Core Data 的Utility程序(請查看Core Data Utility tutorial文檔)。當然其他類型的程序也都可以使用Core Data。
被管理對象和上下文(Managed Objects and Contexts)
你可以把被管理對象上下文想象成一個”聰明“的便箋簿。當你從數據持久層獲取對象時,就把這些臨時的數據拷貝拿到寫在自己的便箋簿上(當然,在便箋上對象會 “恢復”以前的對象圖結構)。然後你就可以隨心所欲的修改這些值了(本子是你的,隨便畫都可以),除非你保存這些數據變化,否則持久層的東西是不會變 的。(跟修改文件後要保存是一個道理)。
附在Core Data框架中模型對象(Model objects)常被稱為“被管理對象”(Managed objects)。所有的被管理對象都要通過上下文進行注冊。使用上下文,你可以在對象圖中添加、刪除對象,並記錄對象的更改(包括單個對象,或是對象間 的關系)。記錄更改後就能支持撤銷和重做的功能。同時,上下文還能保證關系更改後對象圖的完整性。
如果你想要保存所做的修改, 上下文會保證對象的有效性。在驗證有效性後,更改會被寫入到persistent store(持久化存儲層)中。你在程序中的添加和刪除動作都會被作用在存儲的數據中。
在你的一個程序中,可能存在多個上下文。 對於數據存儲(store)中的每個對象,對應的都有唯一的一個被管理對象(managed object)和上下文相關聯(詳情請查看"Faulting and Uniquing"文檔)。換個角度來想,在persistent store中存儲的對象有可能被用在不同的上下文中,每個上下文都有與之對應的被管理對象,被管理對象可以被獨立的修改,這樣就可能在存儲 時導致數據的不一致。Core Data提供了許多解決這個問題的途徑(請查看"Using Managed Object"一章)。
獲取數據的請求(Fetch Requests)
要使用上下文來獲取數據,你需要創建相應的請求(Fetch request)。 Fetch request對象包含你想獲取的對象的描述。例如:“所有 Employee”,或“所有的Employee,department是marketing,按薪資降序排列”。Fetch Request包含三個部分。使用最簡單的寫法,必須指定實體(Entity)的名稱,這就暗示了,每次智能獲得一種類型的實體。 Fetch Request 還可以包含謂詞(predicate)——注:有些地方也把這個叫斷言,個人感覺謂詞更准確些。謂詞將描述對象需要滿足的條件(這就和我們在SQL裡加的 限定條件差不多,正如前面的"All Employees, in the Marketing department")。另外,Fetch Request還可包含一個用於描述排序方式的對象(熟悉的Order by操作)。如圖Figure3所示:
在程序中,你將Fetch Request這個請求發送給上下文,上下文就會從相關的數據源中查找復合條件的對象(也可能找不到),並返回。 所有的被管理對象(managed object)都必須在上下文中注冊,因此通過fetch request獲得的對象自動被注冊。但如前所述,每個在持久存儲層(persistence store)中的對象都對應一個和上下文相關的被管理對象(managed object)因此,如果在上下文中已經存在了fetch request要取的對象,那麼這個被管理對象將被返回。
Core Data追求高執行效率。 它是“需求驅動”的,因此只會創建你確實需要的對象。對象圖不需要保留所有在數據存儲層中的對象。單純指定數據持久層的動作不會將其中所有的數據放到上下 文中去。 當你想從數據存儲層中獲取某些對象的時候,你只會得到那些你請求的(有點羅嗦,總的意思就是需要時獲取,獲取的就是需要的)。如果你不在需要這個對象的時 候,默認情況下它會被釋放。(當然,只是釋放這個對象,而不是從對象圖中移除該對象)。——注:個人感覺有點像重新拷了一個文件的某些部分,不用了就在副 本中刪除,不會影響原件。
持久化存儲助理(Persistent Store Coordinator)
之前提到過,程序中的對 象和外部存儲的數據通過Core Data框架中的一系列對象進行協調,這一系列的對象總的被稱為持久存儲棧(Persistence stack)。在棧頂是被管理對象上下文(Managed object context),而棧底是持久化對象存儲層(Persistence object store)。在它們之間就是持久化存儲助理。
事實上,持久化存儲助理定義了一個棧。從設計方面考慮,它就是可以作為上下 文的”外觀“, 這樣多個數據存儲(Persistence store)看起來就像是一個。 然後上下文就可以根據這些數據存儲來創建對象圖了。持久化存儲助理智能關聯一個被管理對象的模型。如果你像要把不同的實體放到不同的存儲中去,就需要為你 的模型實體做“分區”,方式是通過定義被管理對象模型的configurations。(請參考"Configurations"一章)。
Figure 4演示了這樣的一個結構:employees和departments存儲在一個文件中,customers和companies存儲在另外一個文件中。當你要獲取對象的時候,它們從相關的文件中自動獲取;當保存時,又被歸檔到相應的文件中。
Figure 4存儲棧—改
持久化存儲(Persistent Stores)
持久化存儲是和單獨的一個文件或外部的數據關聯的,它負責將數據和上下文中的對象進行對應。通常,需要你直接和持久化對象存儲打交道的地方,就是指定新的、 和程序進行關聯的外部數據的位置(例如,當用戶打開或保存一個文檔)。大多數需要訪問持久化存儲的動作都由上下文來完成。
程序的代碼—— 特別是和被管理對象相關的部分——不應該對持久化存儲做任何假設(也就是不需要自己考慮存儲的方式或過程)。 Core Data對幾種文件格式有原生的支持。你可以選擇一種自己程序需要的。假設在某個階段你決定換一種文件的格式,而又不想修改程序的框架,而且,你的程序做 了適當的抽象(注:這個就屬於設計方面的東東了),這時,你就能嘗到使用Core Data的甜頭了。例如,在最初的設計中,程序只從本地文件中獲取數據,而你的程序沒有去硬指定對應數據的獲取位置,而是可以在後期指定從遠程位置添加新 的數據類型,這樣你就可以使用新的類型,而不需要修改代碼。(這段還是感覺翻的不太合適)。
重要提示:
雖然Core Dta支持SQLite作為一種存儲類型,但它不能使用任意的SQLite數據庫。Core Data在使用的過程種自己創建這個數據庫。(詳情,請參考"Persistence Store Features")。
持久化文檔(Persistent Documents)
你可以通過代碼的方式創建和配置持久存儲棧,但在多數情況下,你只是想創建一個基於文檔 的應用程序(Document-based application,這個是mac上的)來讀寫文件。這時,用NSDocument的子類NSPersistentDocument可以讓你感受到使 用Core Data的便利。默認狀況下,NSPersistentDocument就已經創建了它自己的持久存儲棧,其中包含了上下文,和單個的持久對象存儲,來處 理這樣文檔和外部數據“一對一”的映射關系。
NSPersistentDocument類提供了訪問文檔的上下文的方法,也實現了標准的NSDocument方法來通過Core Data讀寫文件。 一般說來,你不需要編寫額外的代碼來處理對象的持久化。
持久化文檔的撤銷(undo)操作也被集成在被管理對象的上下文中。
被管理對象和被管理對象模型(Managed Objects and the Managed Object Model)
為 了管理對象圖,也為了提供對象持久化的功能,Core Data需要對對象有很強的描述能力。被管理對象模型就是程序中對象、實體描述的概要圖,如圖Figure 5所示。創建模型的常用做法是通過Xcode的圖形化建模工具Date Model Design tool。但是如果你願意的話,也可以在運行時通過代碼來建模。
Figure 5 有兩個實體的對象模型
模型由多個實體描述對象構成,每個描述提供實體的某項元數據,它們包含實體名、實體在程序中的類名(當然,類名和實體名不需要一致)、屬性還有關系。屬性和關系依次被屬性和關系描述對象所代表,如圖Figure 6所示。
Figure 6 帶有兩個屬性和一個關系的的實體描述
被管理對象必須是NSManagedObject或其子類的實例。 NSManagedObject可用來表示任何實體。它使用內部私有的存儲機制來維護自身的屬性,並執行一個被管理對象所必須的基本操作。一個被管理對象 擁有一份實體描述的引用。在使用時,它通過實體描述來找到自身的元數據,包括實體名和屬性、關系的信息。你也可以繼承NSManagedObject來執 行額外的操作。
被管理對象模型(Managed Object Models)
多數Core Data的功能依賴於你創建的,用來描述程序的實體及其屬性、關系的模型圖。 模型圖由NSManagedObjectModel所表示。一般說來,模型的信息越充實,Core Data能提供的功能就越好。 下文講解了對象模型的特性,以及如何在程序中創建、使用對象模型。
被管理對象模型的特性
被管理對象模型是 NSManagedObjectModel的實例。它描述了你在程序中使用的實體的概要信息。(如果讀者不了解entity、property、 attribute和relationship的含義,請先查看"Core Data Basics"和"Cocoa Design Patterns"文檔中的"Object Modeling"一節)
實體(Entities)
模型包含了NSEntityDescription對象,NSEntityDescription對象指代了模型的實體。關於實體由兩個重要特征:名稱(name)和類名(name of class)。你應該弄清楚實體、實體的類和作為實體實例的被管理對象之間的區別。
NSEntityDescription 對象可包含NSAttributeDescription對象(指代實體的attribute)和NSRelationshipDescription對 象(指代實體間的relationship)。實體也可能包含fetched屬性,該屬性由NSFetchedPropertyDescription指 代,模型中有對應的fetch請求的模板,fetch請求由NSFetchRequest所指代。
實體的繼承關系
實體的繼承和類 的繼承很類似,當然,也同樣有用。 如果你有若干個相似的實體,就可以抽離出它們的共有特性作為一個“父實體”,就省去了在多個實體中都指定相同的屬性。 例如,你可以定義一個包含firstName和lastName的“Person”實體,然後在定義子實體"Employee"和"Customer"。
如果是使用Xcode的可視化建模工具來創建模型,你就可以通過如下圖的方式為一個實體指定父級實體。
Figure1 Xcode中為一個實體指定父實體
如果你想在代碼中創建繼承關系。就需要自頂向下來執行。不能直接指定實體的父實體,而只能給一個實體指定子實體(使用setSubentities:)。這 就是說,如果你想給A實體指定父實體,就只能把A作為數組中的一個元素,調用目標父實體setSubentities:的方式來設置。
抽象實體
你可以把一個實體指定為“抽象實體”,也就是說,你不打算使用這個實體來創建實例。通常,當你想把這個實體作為父實體,而有子實體來實現詳細內容的時候,就 把它聲明“抽象實體”。(和抽象類很像)。例如,在一個繪圖程序中,你可能會設計一個Graphic實體,它包含了x和y坐標信息、顏色、繪制區域,而你 不會去創建一個Graphic的實例,而是使用具體的子實體——Circle、TextArea、Line。(這些基本的東西就不給大牛們再羅嗦 了。。。)
Properties(屬性,這個和Attributes的意思一樣,實在區別不出來,只好上英語了)
實體的 Properties是它的attributes和relationship,包含了fetched屬性(如果有的話)。每個property都有名稱和 類型。 Attribute也可能有默認值。property的名稱不能和NSObject和NSManagedObject類中的無參方法名相同。例如,不能把 property命名為"description"。
臨時屬性(Transient Property)也是作為模型的一部分,但是不作為實體實例的數據保存在持久存儲層。 Core Data也會跟蹤臨時屬性的變化,以備撤銷操作時使用。
注意:如果你用模型外的信息對臨時屬性執行撤銷操作,Core Data將不會使用舊值,調用你的set方法——它只會更新快照信息(snapshot information)。(這段怪怪的,用到的話在修改一下翻譯吧)
Attributes
Core Data內部支持各種attribute的類型,例如string,date,integer(NSString, NSDate, NSNumber)。如果你使用那些不支持的數據,你需要用到在“Non-Standard Persistent Attributes”介紹到的技術。
你可以將一個attribute聲明為“可選”(optional),可選的attribute不 必須有值,但是,不鼓勵你將屬性置空——尤其是數字值(更好的解決方案是使用強制的值,在這裡,我們用默認值,例如0)。 這樣做的原因是為了配合SQL中對於空值NULL做比較的操作:NULL不同於Objective-C中的nil。 數據庫中的NULL不同於0,搜索0值的操作不會匹配到值為NULL的列。
false == (NULL == 0)
false == (NULL != 0)
而且,在數據庫中,NULL也不等於空字符串或是空的數據對象:
false == (NULL == @"")
false == (NULL != @"")
它們之間一點關系都沒有。
關系(Relationships)
Core Data支持對一、對多的關系,也支持fetched屬性。 Fetched property表示了一種“弱”的、單項的關系。 在employees和departments的例子中, department 的一個fetched property可能是“最近雇傭人”(recent hires),而反過來,employee不會擁有這樣的關系。
獲取數據請求的模板(Fetch Request Templates)
我們使用NSFetchRequest類來描述數據請求,利用數據請求從持久存儲(persistent store)中獲取對象。 經常需要多次執行同樣的請求,或是執行某種模式的請求,但是其中包含可變的元素(如查找條件)——這些元素經常有用戶提供。 例如,在運行的時候,你要根據用戶需要獲取某個作者在某個指定日期後的出版的所有出版物。
你可以預定義請求,把它們作為模板存儲在被管理對象模型中。 預定義的模板在你需要的時候就可以取出使用。通常情況下,我們通過Xcode的data modeling tool工具創建請求模板。模板可以包含變量,如圖Figure 2所示。
Figure 2Xcode predicate builder
關於Fetch request templates的詳細信息,請查看"Accessing and Using a Managed Object Model at Runtime"的描述。
用戶信息字典(User Info Dictionaries)
模型中的許多元素,諸如entities, attributes, relationships,都有相關的用戶信息字典。用熟悉的鍵-值對,你可以向其中放置任何你需要的數據。這裡常用的信息有實體的版本詳情,還有針對 fetched property,給謂詞(predicate)用的值。
配置(Configurations)
配置包含了一個名稱和若干個相關的實體。實體的集合是可以重疊的——這就是說,一個實體可以出現在多個配置中。在代碼中,我們使用setEntities: forConfiguration:的方法來指定配置。也可以用Xcode的建模工具來指定(選中某個實體,就在屬性窗口的第三個,就是一個小扳手的符號)。要獲取某項配置的實體,需要用entitiesForConfiguration:的方法。
一般說來,如果你想把不同的實體存放在不同的存儲中去,就可能用到配置。一個持久化存儲助理(persistent store coordinator)只能有一個被管理對象模型。所以,默認情況下,和助理關聯的某個存儲必須包含同樣的實體。要想繞過這個限制,你可以創建一個包含實體子集的模型,然後為每一個子集創建配置,這樣一來,使用這個模型創建助理,當你需要添加存儲時,可使用不同的配置指定對應的存儲屬性。當你創建配置的時候,需要記住,不能創建跨存儲的關系。
使用被管理對象模型
通常可以使用Xcode的建模工具來創建模型(請參考"Create a managed object with Xcode")。你也可以全部使用代碼來創建(請參考"Core Data Utility Tutorial")。
編譯數據模型
數據模型是一種部署資源。 在模型中,除了有實體和屬性的詳細信息外,用Xcode創建的模型還包含了一些額外的視圖信息,包括布局、顏色等等。這些信息在運行時不是必須的。模型文件在編譯的過程中會刪除這些額外信息以保證盡可能高效的加載。xcdatamodel“源”文件會被momc編譯器編譯為mom的目標文件。
"mom" 位於 /Library/Application Support/Apple/Developer Tools/Plug-ins/XDCoreDataModel.xdplugin/Contents/Resources/,如果你想把它用在自己的 build腳本中,格式是:mom source destination, source 就是Core Data Model文件,destination就是輸出的mom文件。
加載數據模型
在一些情況下,你不需要寫任何加載模型的代碼。如果你使用基於文檔的程序框架(Document-based application),NSPersistentDocument會管理諸如查找模型、加載模型的任務。 如果你創建了非Document-based application,而且裡面又用到了Core Data,一般將獲取模型的代碼放在application delegate裡。模型的存儲名稱——也就是文件名,
和運行時的名稱是不相關的,一旦模型被加載,文件名就沒有什麼意義了。也就是說,對模型文件,你可以隨意命名。
如果你想手動加載模型,有兩種方式可用,它們各有各的好處:
你可以從指定的bundle集合裡創建整合模型,使用如下的類方法:
mergeModelFromBundles:
也可以用指定的URL加載單個的模型,使用如下的實例方法:
initWithContentsOfURL: (這個方法相信大家都用過)
若不需要考慮分開加載模型,第一個類方法很適用。例如:在你的程序中和程序鏈接的framework裡都有你想要加載的模型。這個類方法可以讓你很輕松的加載所有的模型,而不需要考慮模型文件的名稱,也不用特定的初始化方法來保證所有的模型都被找到。
但是當你有多個模型要加載,特別是這些模型都代表了一個schema的不同版本,這時,知道要加載哪個模型就很重要了(合並包含相同實體的模型可能導致命名沖突和錯誤,我們之前“一鍋端”的方法不太合適了)。在這種情況下,我們可以用第二個實例方法。 另外,有時我們也需要將模型存儲在bundle之外,也需要用這個方法從指定的URL位置加載模型。
還有一點需要說明:我們還有一個類方法 modelByMergingModels:可以用。像mergedModelFromBundles:方法一樣,它也能合並給定的若干個模型。這樣,我們就可以通過URL來逐一加載模型,然後在創建助理對象之前將它們整合為一個。
改變模型
由於模型描述了存儲層數據的結構,任何改變模型的動作都將使其不在適配於之前創建的存儲層。 如果你改變了模型的結構,就需要將當前存儲層的數據遷移到新版本。(請參考"Core Data Model Versioning and Data Migration Programming Guide"文檔)。例如:如果你添加了新的實體,或新的屬性,你將無法打開舊的存儲;如果你添加了驗證的限制,或者為屬性添加了新的缺省值,你就可以打開舊的存儲。
在運行時訪問和適用被管理對象模型
在運行時,被管理對象模型就是一個簡單的“對象圖”(這個概念之前提到過),認識到這點很重要,尤其是當你需要用代碼來訪問模型的詳細信息時。例如:修改模型(你只能在runtime之前這樣做,請參考 NSManagedObjectModel),取回信息(如本地化實體名,屬性數據類型,或數據請求模板)。
在運行時訪問模型有很多方法,通過持久棧最終從持久化存儲助理得到模型,代碼如下:
[[aManagedObjectContext persistentStoreCoordinator]managedObjectModel];
你也可以通過實體描述得到模型,因此給定一個被管理對象,你就可以得到它的實體描述,進而獲得模型。代碼如下:
[[aManagedObject entity] managedObjectModel];
某些情況下,你要維護模型的“直接”引用,也就是說,一個直接返回模型的方法。NSPersistentDocument提供了 managedObjectModel方法,可以返回一個模型,該模型和在文檔的上下文中使用的持久化存儲助理相關聯。如果你使用Core Data Appplication的模板,application delegate將負責模型的引用。
通過代碼創建獲取數據請求模板(Fetch Request Templates)
你可以通過代碼創建數據請求模板並將其和模型關聯,方法是:setFetchRequestTemplate: forName:如Listing-1所示。 提醒一下:你只能在模型被助理(coordinator)使用之前修改它。
Listing 1 通過代碼創建獲取數據請求模板
NSManagedObjectModel *model = …;
NSFetchRequest * requestTemplate = [[NSFetchRequest alloc]init];
NSEntityDescription *publicationEntity =
[[model entitiesByName] objectForKey: @"Publication"];
[requestTemplate setEntity: publicationEntity];
NSPredicate *predicateTemplate = [NSPredicate predicateWithFormat:
@"(mainAuthor.firstName like[cd] $FIRST_NAME) AND \
(mainAuthor.lastName like[cd] $LAST_NAME) AND \
(publicationDate > $DATE)"];
[requestTemplate setPredicate: predicateTemplate];
[model setFetchRequestTemplate: requestTemplate
forName: @"PublicationForAuthorSinceDate"];
[requestTemplate release];
訪問請求模板
你可以用"Accessing and Using a Managed Object Model at Runtime"裡介紹的代碼片段來獲取並使用請求模板。替換字典必須包含和模板中定義的變量對應的鍵。如果你想測試null值,必須使用NSNull對象——參考"Using Predicates"。(注:這裡的替換字典很好理解,之前的模板中用到了諸如$FIRST_NAME, $LAST_NAME, $DATE這些東西,就相當於我們在模板中創建好的“變量”,我們需要把一個模板“具體化”,就用替換字典,將裡面的變量對應一個值,這裡看代碼就明白了。)
NSManagedObjectModel *model = …;
NSDictionary *substitutionDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
@"Fiona", @"FIRST_NAME", @"Verde", @"LAST_NAME",
[NSDate dateWithTimeIntervalSinceNow: -31356000], @"DATE", nil]; //這裡的FIRST_NAME, LAST_NAME, DATE和我們之前模板裡的$FIRST_NAME, $LAST_NAME和$DATE對應
NSFetchRequest *fetchRequest =
[model fetchRequestFromTemplateWithName: @"PublicationForAuthorSinceDate"
substitutionVariables: substitutionDictionary]; //從之前的model中拿出請求模板,然後設定替換字典
NSArray *results =
[aManagedObjectContext executeFetchRequest: fetchRequest error: &error];
要是模板裡不包含可替換的變量,你要麼
1. 使用fetchRequestFromTemplateWithName: substitutionVariables: 方法,傳遞nil給第二個參數
或者:
2. 使用fetchRequestTemplateForName: 並將結果copy。這個方法不需要傳遞“替換變量”這個參數,但是如果你要用返回值本身,將會有異常拋出(無法在不可變的模型中修改命名的數據請求"Can't modify named fetch request in an immutable model")。
本地化被管理對象模型
你可以對模型的大部分內容做本地化處理,包括實體和屬性名,還有錯誤信息。要明白,“轉成你自己的語言”也是本地化的一部分。 即使你不打算提供外語版本, 顯示“自然語言”的出錯提示信息也會有更好的用戶體驗。例如:“First Name is a required property”就比"firstName is a required property"更好。(後面的這個更像是開發者用的log,顯示的是變量名,這裡不太明顯)。
要想對模型進行本地化處理,需要提供一個本地化字典,模式如下:
Table 1 針對被管理對象模型的本地化字典鍵值對應關系:
Key Value Note
"Entity/NonLocalizedEntityName" "LocalizedEntityName"
"Property/NonLocalizedPropertyName/Entity/EntityName" "LocalizedPropertyName" 1
"Property/NonLocalizedPropertyName" "LocalizedPropertyName"
"ErrorString/NonLocalizedErrorString" "LocalizedErrorString"
備注:(1)在不同實體中的屬性,擁有相同的原始名稱,但需要不同的本地化名稱,適用於該格式。
我們可以通過localizationDictionary方法來訪問本地化字典。注意:在Mac OS X 10.4上,這個方法可能返回nil,除了Core Data為了某些特定目的(如報告本地化的錯誤描述)延遲加載本地化字典。
字符串文件
處理模型的本地化最簡單的方法就是創建對應的字符串文件——字符串文件名和模型文件名一直,但是後綴名用.strings。(例如,模型文件名為 MyDocument.xcdatamodel,對應的字符串文件名就為MyDocumentModel.strings;如果模型文件已經包含了 Model後綴,你必須再附加一個Model,所以,如果模型文件名為JimsModel.xcdatamodel對應的字符串文件名為 JimsModelModel.strings)。字符串文件格式和標准字符串文件類似(請參考"Localizing String Resources"),但是對應的鍵值要遵循Table-1中的規則。
一個模型的字符串文件實例:
"Entity/Emp" = "Employee";
"Property/firstName" = "First Name";
"Property/lastName" = "Last Name";
"Property/salary" = "Salary";
更詳細的示例請參考"NSPersistentDocument Core Data Tutorial"。
代碼實現設置本地化字典
你可以在運行時設定本地化字典,適用NSManagedObjectModel的setLocalizationDictionary:方法即可。你必須創建一個符合Table-1格式的字典,並把它和模型關聯。必須保證在模型被使用(獲取或創建被管理對象)之前做這些工作,因為再使用後模型就不可編輯了。 Listing 3演示了創建包含本地化字典的被管理對象模型。實體名稱叫“Run”,它有兩個屬性: "date"和"processID",分別是date和integer類型。process ID的值不能為負。
Listing 3 通過代碼創建被管理對象模型
NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] init];
NSEntityDescription *runEntity = [[NSEntityDescription alloc] init];
[runEntity setName:@"Run"];
[runEntity setManagedObjectClassName:@"Run"];
[mom setEntities:[NSArray arrayWithObject:runEntity]];
[runEntity release];
NSMutableArray *runProperties = [NSMutableArray array];
NSAttributeDescription *dateAttribute = [[NSAttributeDescription alloc] init];
[runProperties addObject:dateAttribute];
[dateAttribute release];
[dateAttribute setName:@"date"];
[dateAttribute setAttributeType:NSDateAttributeType];
[dateAttribute setOptional:NO];
NSAttributeDescription *idAttribute= [[NSAttributeDescription alloc] init];
[runProperties addObject:idAttribute];
[idAttribute release];
[idAttribute setName:@"processID"];
[idAttribute setAttributeType:NSInteger32AttributeType];
[idAttribute setOptional:NO];
[idAttribute setDefaultValue:[NSNumber numberWithInt:0]];
NSPredicate *validationPredicate = [NSPredicate predicateWithFormat:@"SELF >= 0"];
NSString *validationWarning = @"Process ID < 0";
[idAttribute setValidationPredicates:[NSArray arrayWithObject:validationPredicate]
withValidationWarnings:[NSArray arrayWithObject:validationWarning]];
[runEntity setProperties:runProperties];
NSMutableDictionary *localizationDictionary = [NSMutableDictionary dictionary];
[localizationDictionary setObject:@"Process ID"
forKey:@"Property/processID/Entity/Run"];
[localizationDictionary setObject:@"Date"
forKey:@"Property/date/Entity/Run"];
[localizationDictionary setObject:@"Process ID must not be less than 0"
forKey:@"ErrorString/Process ID < 0"];
[mom setLocalizationDictionary:localizationDictionary];
這段代碼寫的比較多,這裡不再解釋了。本地化字典的代碼在最後。創建一個符合格式的localizationDictionary,然後用model調用即可。
Android中,要自己實現一個掛斷電話方法時,很久之前可以endCall().不過現在已經不行了,要應用反射機制,獲取到"android.os.Service
很多人也許會問:360被卸載之後會跳轉到指定的反饋頁面,是怎麼弄的?其實這個問題的核心就在於:應用被卸載了,如果能夠做到後續的代碼邏輯繼續執行我們再來仔細分析一下場景和流
1.新建一個Android項目,下載好Android-PullToRefresh-master,並解壓,找到library文件夾,我把他放在C:\import目錄下,下面
在 Android 3.0(API level 11) 之後,Google 為 Android添加了屬性動畫(Property Animation),該動畫系統是一個強大