iOS 本地數據持久化存儲:
一、plist
二、歸檔
三、NSUserDefaults
四、NSFileManager
五、數據庫
1、CoreData概述
CoreData是蘋果自帶的管理數據庫的工具。使用Core Data有不少緣由,其中最簡單的一條就是:它能讓你爲Model層寫的代碼的行數減小爲原來的50%到70%。 這歸功於以前提到的Core Data的特性。更妙的是,對於上述特性你也既不用去測試,也不用花功夫去優化。
Core Data擁有成熟的代碼,這些代碼經過單元測試來保證品質。經過了幾個版本的發佈,已經被高度優化。 它能利用Model層的信息和運行時的特性,而不經過程序層的代碼實現。 除了提供強大的安全支持和錯誤處理外,它還提供了最優的內存擴展性,可實現有競爭力的解決方案。不使用Core Data的話,你須要花很長時間來起草本身的方案,解決各類問題,這樣作效率不高。
另外:CoreData主要是iOS對SQLite數據庫的封裝。CoreData有 對象-關係 的映射的功能,能把OC的對象存儲成數據庫或 xml 等。若是數據存儲使用的是coreData,那麼讀取時能夠不使用 SQLite 語句。
2、關於Core Data常見的誤解
一、 Core Data不是一個關係型數據庫,也不是關係型數據庫管理系統(RDBMS)。
Core Data 爲數據變動管理、對象存儲、對象讀取恢復的功能提供了支持。 它可使用SQLite做爲持久化存儲的類型。 它自己並非一個數據庫(這點很重要,好比,你可使用Core Data來記錄數據變動,管理數據,但並不能用它向文件內存儲數據)。
2 、它並不能取代你寫代碼的工做。雖然能夠純粹使用XCode的數據建模工具和Interface Builder來編寫複雜程序,但在更多的程序中,你都本身動手寫代碼。
3、CoreData 的核心概念
關鍵的概念圖
(1)NSManagedObjectModel 託管對象模型(MOM)
(用來加載Core Data數據模型文件,全部的數據模型能夠所有加載到這個對象中。)
這個MOM由實體描述對象,即NSEntityDescription實例的集合組成,實體描述對象介紹見下面第7條。
做用:添加實體的屬性,創建屬性之間的關係
(2)NSManagedObjectContext 託管對象上下文(MOC)
(用於操做數據模型(對象),並檢測數據模型(對象)的變化。)
在概念圖2中,託管對象上下文(MOC)經過持久化存儲協調器(PSC)從持久化存儲(NSPersistentStore)中獲取對象時,這些對象會造成一個臨時副本在MOC中造成一個對象集合,該對象集合包含了對象以及對象彼此之間的一些關係。咱們能夠對這些副本進行修改,而後進行保存,而後MOC會經過 PSC 對 NSPersistentStore 進行操做,持久化存儲就會發生變化。
CoreData中的NSManagedObjectModel 託管對象的數據模型(MOM),經過 MOC 進行註冊。MOC有插入、刪除以及更新數據模型等操做,並提供了撤銷和重作的支持。
做用:插入數據,更新數據,刪除數據
(3)NSPersistentStoreCoordinator 持久化存儲協調器(PSC)
(數據持久化存儲協調器,負責調度上層與底層對數據的操做。)
在應用程序和外部數據存儲的對象之間提供訪問通道的框架對象集合統稱爲持久化堆棧(persistence stack)。在堆棧頂部的是託管對象上下文(MOC),在堆棧底部的是持久化對象存儲(persistent object stores)。在託管對象上下文和持久化對象存儲之間即是持久化存儲協調器(PSC)。應用程序經過類NSPersistentStoreCoordinator的實例訪問持久化對象存儲。
持久化存儲協調器爲一或多個託管對象上下文提供一個訪問接口,使其下層的多個持久化存儲能夠表現爲單一一個聚合存儲。一個託管對象上下文能夠基於持久化存儲協調器下的全部數據存儲來建立一個對象圖。持久化存儲協調器只能與一個託管對象模型(MOM)相關聯。
(4)NSManagedObject 託管對象(MO)
(具體的數據模型對象。)
託管對象必須繼承自NSManagedObject或者NSManagedObject的子類。NSManagedObject可以表述任何實體。它使用一個私有的內部存儲,以維護其屬性,並實現託管對象所需的全部基本行爲。託管對象有一個指向實體描述的引用。NSEntityDescription 實體描述表述了實體的元數據,包括實體的名稱,實體的屬性和實體之間的關係。
(5)Controller 控制器
概念圖1中綠色的 Array Controller,Object Controller,Tree Controller 這些控制器,通常都是經過 control+drag 將 Managed Object Context 綁定到它們,這樣咱們就能夠在 nib 中可視化地操做數據
(6)NSFetchRequest 獲取數據請求
使用託管對象上下文來檢索數據時,會建立一個獲取請求(fetch request)。相似Sql查詢語句的功能。
(7)NSEntityDescription 實體描述
(模型描述類,可以實例化獲得具體的數據模型對象。)
實體描述對象提供了一個實體的元數據,包括實體名(Name),類名(ClassName),屬性(Properties)以及實體屬性與其餘實體的一些關係(Relationships)等。
(8).xcdatamodeld
裏面是.xcdatamodeld文件,用數據模型編輯器編輯,編譯後爲.momd或.mom文件。
咱們能夠選中咱們的應用程序(路徑相似爲/Users/Childhood/Library/Application Support/iPhone Simulator/7.1/Applications/005D926F-5763-4305-97FE-AE55FE7281A4),右鍵顯示包內容,咱們看到是這樣的。
4、Core Data運做機制
1,應用程序先建立或讀取模型文件(後綴爲xcdatamodeld)生成 NSManagedObjectModel 對象。從模型文件(後綴爲 xcdatamodeld)讀取。
2,而後生成 NSManagedObjectContext 和 NSPersistentStoreCoordinator 對象,前者對用戶透明地調用後者對數據文件進行讀寫。
3,NSPersistentStoreCoordinator 負責從數據文件(xml, sqlite,二進制文件等)中讀取數據生成 Managed Object,或保存 Managed Object 寫入數據文件。
4,NSManagedObjectContext 參與對數據進行各類操做的整個過程,它持有 Managed Object。咱們經過它來監測 Managed Object。監測數據對象有兩個做用:支持 undo/redo 以及數據綁定。這個類是最常被用到的。
5,Array Controller, Object Controller, Tree Controller 這些控制器通常與 NSManagedObjectContext 關聯,所以咱們能夠經過它們在 nib 中可視化地操做數據對象。
5、代碼步驟
一、加載Core Data文件
二、將數據的存儲方式設定爲數據庫
三、操做數據:增長
四、操做數據:查詢
五、操做數據:修改
六、操做數據:刪除css
MagicalRecord https://github.com/magicalpanda/MagicalRecordhtml
注意: MagicalRecord 在 ARC 下運做,Core Data 是 ORM 方案,聽說帶來的麻煩比好處多,且 Core Data 創建的表沒有主鍵,但對於對數據庫沒有性能要求,進行簡單的數據操做徹底夠用,能簡化無數的代碼量.ios
In software engineering, the active record pattern is a design pattern found in software that stores its data in relational databases. It was named by Martin Fowler in his book Patterns of Enterprise Application Architecture. The interface to such an object would include functions such as Insert, Update, and Delete, plus properties that correspond more-or-less directly to the columns in the underlying database table.git
在軟件工程中,對象與數據庫中的記錄實時映射是一種設計模式,在處理關係型數據庫的的軟件中多有出現.這種設計模式被記錄在 Martin Fowler 的<Patterns of Enterprise Application Architecture> 中.被操做對象的接口應該包含增刪改查的方法,基本屬性很少很多的恰好與數據庫中的一條記錄相對應.github
Active record is an approach to accessing data in a database. A database table or view is wrapped into a class; thus an object instance is tied to a single row in the table. After creation of an object, a new row is added to the table upon save. Any object loaded gets its information from the database; when an object is updated, the corresponding row in the table is also updated. The wrapper class implements accessor methods or properties for each column in the table or view.sql
實時映射記錄是一種操做數據庫的方式,一個數據庫的表被封裝成一個對象;這個對象中的一個實例會對應着該表中的一條記錄.當建立一個對象時,一條記錄也被插入到表中並保存起來.任何被加載的對象中的屬性信息都從數據庫中讀取;當一個對象更新時,這個數據庫表中對應的記錄也會更新.這個被封裝的類實現了實時操做的方法,且其屬性一一對應於數據庫中表的屬性.數據庫
- Wikipedia設計模式
MagicalRecord was inspired by the ease of Ruby on Rails' Active Record fetching. The goals of this code are:數組
MagicalRecord 靈感來自於簡潔的Ruby語言中 Rails' Active Record 查詢方式. MagicalRecord 這個開源庫的核心思想是:安全
拙劣的翻譯請勿見怪,如下是我在最新的 Xcode 5.1 開 ARC 的環境下的使用教程.
1. 將 MagicalRecord 文件夾拖入到工程文件中,引入 CoreData.frame 框架
2. 在 .pch 文件中引入頭文件 CoreData+MagicalRecord.h
注:只能在.pch文件中引頭文件,不然沒法經過編譯
3. 建立 Model.xcdatamodeld 文件,並建立一個 Student 的 ENTITIES,最後建立出 Student 類
4. 在 Appdelete.m 文件中寫如下代碼
如下是增刪改查的基本操做,但注意一點,在作任何的數據庫操做以前,請先初始化如下,在Appdelete載入時初始化一次便可,不然找不到數據庫而崩潰,你懂的.
//設置數據庫名字
[MagicalRecord setupCoreDataStackWithStoreNamed:@"Model.sqlite"];
增長
增長一條記錄
Student *person = [Student MR_createEntity];
person.name = @"Y.X.";
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
注意:建立了對象後是須要執行存儲操做的
查詢
查詢全部的記錄
NSArray *students = [Student MR_findAll];
根據某個屬性某個條件查詢
NSArray *students = [Student MR_findByAttribute:@"name" withValue:@"Y.X."];
根據排序取得搜索結果
NSArray *students = [Student MR_findAllSortedBy:@"name" ascending:YES];
我不一一列舉了,查看一下頭文件就知道了.
查詢全部記錄
+ (NSArray *) MR_findAll;
根據上下文句柄查詢全部記錄
+ (NSArray *) MR_findAllInContext:(NSManagedObjectContext *)context;
根據某個屬性排序查詢全部記錄
+ (NSArray *) MR_findAllSortedBy:(NSString *)sortTerm ascending:(BOOL)ascending;
根據某個屬性排序以及上下文操做句柄查詢全部記錄
+ (NSArray *) MR_findAllSortedBy:(NSString *)sortTerm ascending:(BOOL)ascending inContext:(NSManagedObjectContext *)context;
根據某個屬性排序用謂詞來查詢記錄
+ (NSArray *) MR_findAllSortedBy:(NSString *)sortTerm ascending:(BOOL)ascending withPredicate:(NSPredicate *)searchTerm;
根據某個屬性排序以及上下文操做句柄用謂詞來查詢記錄
+ (NSArray *) MR_findAllSortedBy:(NSString *)sortTerm ascending:(BOOL)ascending withPredicate:(NSPredicate *)searchTerm inContext:(NSManagedObjectContext *)context;
根據謂詞查詢
+ (NSArray *) MR_findAllWithPredicate:(NSPredicate *)searchTerm;
根據謂詞以及上下文操做句柄來查詢
+ (NSArray *) MR_findAllWithPredicate:(NSPredicate *)searchTerm inContext:(NSManagedObjectContext *)context;
如下都是查詢一個對象時的操做,與上面重複,不一一贅述
+ (instancetype) MR_findFirst;
+ (instancetype) MR_findFirstInContext:(NSManagedObjectContext *)context;
+ (instancetype) MR_findFirstWithPredicate:(NSPredicate *)searchTerm;
+ (instancetype) MR_findFirstWithPredicate:(NSPredicate *)searchTerm inContext:(NSManagedObjectContext *)context;
+ (instancetype) MR_findFirstWithPredicate:(NSPredicate *)searchterm sortedBy:(NSString *)property ascending:(BOOL)ascending;
+ (instancetype) MR_findFirstWithPredicate:(NSPredicate *)searchterm sortedBy:(NSString *)property ascending:(BOOL)ascending inContext:(NSManagedObjectContext *)context;
+ (instancetype) MR_findFirstWithPredicate:(NSPredicate *)searchTerm andRetrieveAttributes:(NSArray *)attributes;
+ (instancetype) MR_findFirstWithPredicate:(NSPredicate *)searchTerm andRetrieveAttributes:(NSArray *)attributes inContext:(NSManagedObjectContext *)context;
+ (instancetype) MR_findFirstWithPredicate:(NSPredicate *)searchTerm sortedBy:(NSString *)sortBy ascending:(BOOL)ascending andRetrieveAttributes:(id)attributes, ...;
+ (instancetype) MR_findFirstWithPredicate:(NSPredicate *)searchTerm sortedBy:(NSString *)sortBy ascending:(BOOL)ascending inContext:(NSManagedObjectContext *)context andRetrieveAttributes:(id)attributes, ...;
+ (instancetype) MR_findFirstByAttribute:(NSString *)attribute withValue:(id)searchValue;
+ (instancetype) MR_findFirstByAttribute:(NSString *)attribute withValue:(id)searchValue inContext:(NSManagedObjectContext *)context;
+ (instancetype) MR_findFirstOrderedByAttribute:(NSString *)attribute ascending:(BOOL)ascending;
+ (instancetype) MR_findFirstOrderedByAttribute:(NSString *)attribute ascending:(BOOL)ascending inContext:(NSManagedObjectContext *)context;
修改
NSArray *students = [Student MR_findByAttribute:@"name" withValue:@"Y.X."];
for (Student *tmp in students) {
tmp.name = @"Jane";
}
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
注意:既然要修改首先得須要找到記錄,根據條件匹配找到記錄,而後修改,而後保存
刪除
NSArray *students = [Student MR_findByAttribute:@"name" withValue:@"Frank"];
for (Student *tmp in students) {
[tmp MR_deleteEntity];
}
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
注意:既然要刪除首先得須要找到記錄,根據條件匹配找到記錄,而後刪除,而後保存
心得體會
若是項目中對於操做數據庫沒有性能要求請使用 CoreData 相關的開源庫吧.
CoreData 操做較爲複雜, MagicalRecord 有着不少的特性,好比能夠根據設置在主線程或者子線程中進行操做,方便快捷,能入榜最佳10大開源庫自有其獨到的地方,會使用 MagicalRecord 須要具有必定的 CoreData 相關知識,本人也只是現學現用,但深知其能夠爲開發帶來的好處,使用數據庫的朋友有着以下的一些選擇.
1. SQLite3 C函數形式(本人以前作過幹嵌入式開發,即便是這樣也不推薦使用面向過程毫無對象概念的SQLite3,有更好的方式爲何不用呢?)
2. FMDB 對SQLite3的封裝,有着對象的概念,熟悉SQ語句的朋友可使用,但尚未作到對象與記錄實時對應
3. CoreData 他作到了對象與記錄實時對應關係,使用其自身的搜索體系(不用SQ語言),但其基本的操做以及配置讓人望而卻步
4. MagicalRecord 對 CoreData 官方的用法進行了人性化的封裝,用過 CoreData 基本操做再使用 MagicalRecord 會深有體會
5. ObjectiveRecord 也是對 CoreData 的人性化封裝,使用更加傻瓜,但傻瓜的代價就是犧牲了一些更強大的功能,在Github上搜索關鍵字便可
附錄:
1.默認的就是在後臺存儲的,不會阻塞主線程
我在 CoreData+MagicalRecord.h 文件中定義了宏 MR_SHORTHAND ,因此在方法中不須要 MR_ 前綴了
如下爲代碼(提供block來通知存儲成功,異步操做)
如下爲打印信息
----------------------------------------------------------------------------------------------------------------------------------------------------
2014-03-13 11:17:43.616 StudyMagicalRecord[26416:60b] +[NSManagedObjectContext(MagicalRecord) MR_contextWithStoreCoordinator:](0x2f4498) -> Created Context UNNAMED
2014-03-13 11:17:43.616 StudyMagicalRecord[26416:60b] +[NSManagedObjectContext(MagicalRecord) MR_setRootSavingContext:](0x2f4498) Set Root Saving Context: <NSManagedObjectContext: 0xe74d910>
2014-03-13 11:17:43.617 StudyMagicalRecord[26416:60b] +[NSManagedObjectContext(MagicalRecord) MR_newMainQueueContext](0x2f4498) Created Main Queue Context: <NSManagedObjectContext: 0xe74e040>
2014-03-13 11:17:43.617 StudyMagicalRecord[26416:60b] +[NSManagedObjectContext(MagicalRecord) MR_setDefaultContext:](0x2f4498) Set Default Context: <NSManagedObjectContext: 0xe74e040>
2014-03-13 11:17:43.618 StudyMagicalRecord[26416:60b] -[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:](0xe74e040) → Saving <NSManagedObjectContext (0xe74e040): *** DEFAULT ***> on *** MAIN THREAD ***
2014-03-13 11:17:43.618 StudyMagicalRecord[26416:60b] -[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:](0xe74e040) → Save Parents? 1
2014-03-13 11:17:43.619 StudyMagicalRecord[26416:60b] -[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:](0xe74e040) → Save Synchronously? 0
2014-03-13 11:17:43.619 StudyMagicalRecord[26416:60b] time
2014-03-13 11:17:43.622 StudyMagicalRecord[26416:60b] -[NSManagedObjectContext(MagicalRecord) MR_contextWillSave:](0xe74e040) Context DEFAULT is about to save. Obtaining permanent IDs for new 1 inserted objects
2014-03-13 11:17:43.623 StudyMagicalRecord[26416:60b] -[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:](0xe74d910) → Saving <NSManagedObjectContext (0xe74d910): *** BACKGROUND SAVING (ROOT) ***> on *** MAIN THREAD ***
2014-03-13 11:17:43.624 StudyMagicalRecord[26416:60b] -[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:](0xe74d910) → Save Parents? 1
2014-03-13 11:17:43.624 StudyMagicalRecord[26416:60b] -[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:](0xe74d910) → Save Synchronously? 0
2014-03-13 11:17:43.625 StudyMagicalRecord[26416:1303] -[NSManagedObjectContext(MagicalRecord) MR_contextWillSave:](0xe74d910) Context BACKGROUND SAVING (ROOT) is about to save. Obtaining permanent IDs for new 1 inserted objects
2014-03-13 11:17:43.626 StudyMagicalRecord[26416:1303] __70-[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:]_block_invoke25(0xe74d910) → Finished saving: <NSManagedObjectContext (0xe74d910): *** BACKGROUND SAVING (ROOT) ***> on *** BACKGROUND THREAD ***
2014-03-13 11:17:43.627 StudyMagicalRecord[26416:60b] YES
----------------------------------------------------------------------------------------------------------------------------------------------------
2.如何關閉 MagicalRecord 提供的打印信息?
修改 MagicalRecord.h 23 行處的值,把 0 改成 1 便可.
Magical Record是用來操做Core Data的一個第三方工具,在介紹Magical Record 以前必需要先了解一下Core Data的基本概念
核心數據堆棧是由一個或多個與單個persistent store coordinator
關聯的managed object contexts
組成,而persistent store coordinator
是和一個或多個persistent stores
關聯在一塊兒。堆棧包含了CoreData的全部組件查詢,建立,操做managed objects
.
簡單來講包含了:
persistent store
.相似於數據庫persistent object store
persistent store coordinator
managed object model
managed objects
的managed object context
容器可能有點繞,不過一看圖世界就清晰了
以下圖:
Managed Object是一個模型對象(模型-視圖-控制器的意義上),它表明了一個持久存儲的記錄。管理對象是實例NSManagedObject或子類NSManagedObject。
管理對象有一個實體的描述對象,告訴它表明着什麼實體的引用。以這種方式,NSManagedObject能夠表示任何實體不須要每一個實體的惟一的子類。若是要實現自定義行爲,例如計算派生屬性值,或者爲了實現驗證邏輯可使用一個子類。
仍是來看圖:
Manage Context Object表明單個對象的空間,,在覈心數據的應用程序。管理對象上下文的一個實例的NSManagedObjectContext。它的主要職責是管理管理對象的集合。這些管理對象表明一個或多個持久存儲的一個內部一致的見解。上下文是在管理對象的生命週期核心做用。
上下文是在覈心數據堆棧中的中心對象。這是你用它來建立和獲取管理對象和管理撤消和恢復操做的對象。內的給定範圍內,有至多一個被管理目標表明在永久存儲器的任何給定的記錄。
上下文被鏈接到一個父對象存儲。這一般是一個持久存儲協調,但多是另外一個管理對象上下文。當你獲取對象,上下文要求其父對象存儲返回那些符合提取請求的對象。您對管理對象的修改,直到您保存的背景下不被提交到父store。
在某些應用中,你可能想保持獨立組來管理對象和編輯這些對象的; 或者你可能須要執行使用一個上下文,同時容許用戶與另外一個對象交互的後臺操做
哎!翻譯太累了。直接上圖吧
這張圖把這個的架構解釋得很是清楚
導入MagicalRecord.h
在項目的預編譯文件*.pch
中。這保證了能夠全局訪問所須要的頭文件。
使用了CocoaPods
或者MagicalRecord.framework
,用以下方式導入:
// Objective-C #import <MagicalRecord/MagicalRecord.h> // Swift import MagicalRecord
若是是把源文件直接放到項目中,則直接#import "MagicalRecord.h"
接下里,在app delegate
的某些地方,好比- applicationDidFinishLaunching: withOptions:
或者-awakeFromNib
,使用下面的某一個方法來配置MagicalRecord
.
+ (void)setupCoreDataStack; + (void)setupAutoMigratingCoreDataStack; + (void)setupCoreDataStackWithInMemoryStore; + (void)setupCoreDataStackWithStoreNamed:(NSString *)storeName; + (void)setupCoreDataStackWithAutoMigratingSqliteStoreNamed:(NSString *)storeName; + (void)setupCoreDataStackWithStoreAtURL:(NSURL *)storeURL; + (void)setupCoreDataStackWithAutoMigratingSqliteStoreAtURL:(NSURL *)storeURL;
每次調用Core Data
的堆棧的實例,提供給了這些實例的getter,setter方法。這些實例被MagicalRecord
很好的管理,被識別爲默認方式。
當經過DEBUG
模式標識使用SQLite
數據庫,不建立新的model
版原本改變model
將會引發MagicalRecord
自動的刪除老的數據庫而且自動的建立一個新的。這樣能夠節約不少時間--不須要每次都卸載重裝app來讓data model
改變,確保你的app不是用的DEBUG模式:當刪除app數據的時候不告訴用戶真的是一種很糟糕的方式
在你的app退出以前,你應該調用類方法+cleanUp
[MagicalRecord cleanUp];
這將會清理MagicalRecord
,好比自定義的錯誤處理,讓經過MagicalRecord
建立的Core Data
堆棧爲nil
.
一些簡單的類方法用來幫助快速的你建立新的上下文
persistent store coordinator
爲新的上下文,有一個NSPrivateQueueConcurrencyType當使用CoreData
,你將不斷的和兩個主要的對象打交道,NSManagedObject
和 NSManagedObjectContext.
MagicalRecord
提升了一個簡單的類方法來獲取默認的NSManagedObjectContext
,這個上下文貫穿了你的app始終,這個上下文的操做會在在主線程中進行,而且對於單線程的app比較適合。
經過以下方式訪問到默認的上下文:
NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext];
這個上下文將在MagicalRecord
任何使用了上下文的方法中使用,可是沒有提供一個具體的NSManagedObjectContext
參數。
若是你須要建立一個再也不主線程中使員工的上下文,使用:
NSManagedObjectContext *myNewContext = [NSManagedObjectContext MR_newContext];
這種方式將會建立一個和default context
有相同的對象和persistent store
.安全在其餘線程使用。它將會默認的將default context
做爲它的父上下文。
若是你想默認讓myNewContext
實例化全部的fetch request
.使用類方法的方式
[NSManagedObjectContext MR_setDefaultContext:myNewContext];
注意:高度建議
default context
使用類型爲NSMainQueueConcurrencyType
的上下文來建立並設置在主線程。
MagicalRecord
提供了方法來設置,協調上下文在後臺線程中使用。後臺保存操做受到了UIView
動畫使用Block
的方式,但也存在了一些不一樣
在你對實體進行改變了的block,絕對不在主線程中執行
單個的NSManagedObjectContext
提供了block使用。
舉個例子,你有一個Person實體,而且須要設置firstName和lastName,下面的代碼展現了你怎樣經過MagicalRecord
來設置後臺上下文進行使用。
Person *person = ...;
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){ Person *localPerson = [person MR_inContext:localContext]; localPerson.firstName = @"John"; localPerson.lastName = @"Appleseed"; }];
在這個方法,具體的block
提供了一個合適的上下文讓你進行操做,不須要擔憂去設置上下文,以便它告訴default context
已經作了。而且應該更新,由於是在其餘線程裏面改變進行的。
當執行完了saveBlock
,你能夠在completion block
作些操做
Person *person = ...;
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){ Person *localPerson = [person MR_inContext:localContext]; localPerson.firstName = @"John"; localPerson.lastName = @"Appleseed"; } completion:^(BOOL success, NSError *error) { self.everyoneInTheDepartment = [Person findAll]; }];
completion block
在主線程中被調用,爲了UI更新更安全。
在默認的上下文中插入一個實體,以下:
Person *myPerson = [Person MR_createEntity];
在具體的上下文中插入一個實體Person *myPerson = [Person MR_createEntityInContext:otherContext];
在默認上下文中刪除:[myPerson MR_deleteEntity];
在具體上下文中刪除:[myPerson MR_deleteEntityInContext:otherContext];
截斷全部實體在默認上下文[Person MR_truncateAll];
截斷全部實體在具體上下文[Person MR_truncateAllInContext:otherContext];
在MagicalRecord
大多數方法是返回一個NSArray
數組。
舉例,若是你有一個person
實體和department
實體關聯,你能夠查詢全部的person
實體從persistent store
經過以下方式實現:
NSArray *people = [Person MR_findAll];
傳入一個具體的參數返回一個排序後的數組:
NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName" ascending:YES];
傳入多個具體的參數返回一個排序後的數組
NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName,FirstName" ascending:YES];
傳入多個不一樣參數值獲得排序結果,若是你不提供任何一個參數的默認值,就會默認使用你在model中的設置。
NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName:NO,FirstName" ascending:YES]; // OR NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName,FirstName:YES" ascending:NO];
若是你有一種惟一從數據庫中查詢單個對象的方法(好比做爲惟一屬性),你能夠經過下面的方法:
Person *person = [Person MR_findFirstByAttribute:@"FirstName" withValue:@"Forrest"];
若是想去具體化你的搜索,你可使用謂詞
NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", @[dept1, dept2]]; NSArray *people = [Person MR_findAllWithPredicate:peopleFilter];
NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", departments]; NSFetchRequest *people = [Person MR_requestAllWithPredicate:peopleFilter];
關於每一行的調用, NSFetchRequest
和 NSSortDescriptor
做爲排序的標配。
Predicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", departments]; NSFetchRequest *peopleRequest = [Person MR_requestAllWithPredicate:peopleFilter]; [peopleRequest setReturnsDistinctResults:NO]; [peopleRequest setReturnPropertiesNamed:@[@"FirstName", @"LastName"]]; NSArray *people = [Person MR_executeFetchRequest:peopleRequest];
能夠執行全部實體類型輸血量在persistent store
NSNumber *count = [Person MR_numberOfEntities];
或者基於查詢的數量NSNumber *count = [Person MR_numberOfEntitiesWithPredicate:...];
這裏有一組方法來返回NSUInteger而不是NSNumber
+ (NSUInteger) MR_countOfEntities; + (NSUInteger) MR_countOfEntitiesWithContext:(NSManagedObjectContext *)context; + (NSUInteger) MR_countOfEntitiesWithPredicate:(NSPredicate *)searchFilter; + (NSUInteger) MR_countOfEntitiesWithPredicate:(NSPredicate *)searchFilter inContext:(NSManagedObjectContext *)context;
NSNumber *totalCalories = [CTFoodDiaryEntry MR_aggregateOperation:@"sum:" onAttribute:@"calories" withPredicate:predicate]; NSNumber *mostCalories = [CTFoodDiaryEntry MR_aggregateOperation:@"max:" onAttribute:@"calories" withPredicate:predicate]; NSArray *caloriesByMonth = [CTFoodDiaryEntry MR_aggregateOperation:@"sum:" onAttribute:@"calories" withPredicate:predicate groupBy:@"month"];
全部的 find
, fetch
, request
方法都有一個inContext:
,方法參數容許具體使用哪個上下文查詢:
NSArray *peopleFromAnotherContext = [Person MR_findAllInContext:someOtherContext]; Person *personFromContext = [Person MR_findFirstByAttribute:@"lastName" withValue:@"Gump" inContext:someOtherContext]; NSUInteger count = [Person MR_numberOfEntitiesWithContext:someOtherContext];
總的來講,當數據發生改變的時候應該保存到persistent store(s)
.一些應用選擇在應用終止的時候才保存。然而,在大多數場景下是不須要的。事實上,只在應用終止的時候保存,會有數據丟失的風險。萬一你的應用崩潰了怎麼辦?用戶將丟失他對數據所作的改變。那樣的話是一種至關糟糕的體驗,能夠簡單的避免。
若是你以爲執行保存話費了大量的時間,有幾件事情須要考慮:
MagicalRecord
提升了很是簡單的API來讓改變的實例按順序的在後臺保存,舉個例子:[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
// Do your work to be saved here, against the `localContext` instance // Everything you do in this block will occur on a background thread } completion:^(BOOL success, NSError *error) { [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }];
當應用在iOS上終止運行,有一個很小的機會去清理,保存數據到磁盤。若是你知道保存操做可能會花一段時間,最好的方式就是去申請一個額外的截止時間。好比:
UIApplication *application = [UIApplication sharedApplication]; __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{ [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }]; [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) { // Do your work to be saved here } completion:^(BOOL success, NSError *error) { [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }];
確保仔細的讀過[
MagicalRecord創建了在和Core Data交互的時候的日誌。當錯誤發生的時候,這些錯誤將會被捕獲。而且將打印到控制檯。
日誌被配置爲輸出調試信息(MagicalRecordLoggingLevelDebug)
在deug編譯的時候默認的,將會輸出日誌信息MagicalRecordLoggingLevelError
在realease下。
日誌經過[MagicalRecord setLoggingLevel:]
配置,使用下面的幾種預約義的日主等級。
日誌默認等級是 MagicalRecordLoggingLevelWarn
大多數人而言,這個不須要,設置日誌等級爲MagicalRecordLogLevelOff將會保證再也不打印日誌信息
甚至當使用了MagicalRecordLogLevelOff,快速檢測檢查可能被調用不管什麼時候日誌被調用。若是想絕對的關閉日誌。你能夠定義以下,當編譯MagicalRecord的時候#define MR_LOGGING_DISABLED 1
請注意:這個以後再增長源碼到項目中才會起做用。你也能夠增長MagicalRecord項目的OTHER_CFLAGS爲-DMR_LOGGING_DISABLED=1
google到了解決的方法副在下面
For the development branch (version 2.3.0 and higher) of Magical Record logging seems to still not work correctly. When imported like this: pod 'MagicalRecord', :git => 'https://github.com/magicalpanda/MagicalRecord', :branch => 'develop' I have no logging output on my Xcode console. But I altered the post_install script of the Cocoapod. The following should enable logging: https://gist.github.com/Blackjacx/e5f3d62d611ce435775e With that buildsetting included in GCC_PREPROCESSOR_DEFINITIONS logging of Magical Record can be controlled in 2.3.0++ by using [MagicalRecord setLoggingLevel:]
- 腳本:
post_install do |installer| installer.project.targets.each do |target| target.build_configurations.each do |config| # Enable the loggin for MagicalRecord # https://github.com/magicalpanda/MagicalRecord/wiki/Logging if target.name.include? "MagicalRecord" preprocessorMacros = config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] if preprocessorMacros.nil? preprocessorMacros = ["COCOAPODS=1"]; end preprocessorMacros << "MR_LOGGING_ENABLED=1" config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = preprocessorMacros end end end end
本身嘗試遇到的坑
2.3.0版本一樣遇到了日誌不能正常輸出到控制檯的問題,雖然可以拿到解決問題的腳步,可是本身在taget,buildsetting裏面都設置了仍是沒有用。本身對cocopods管理的原理還不是很明白。
NSManagedObjectContext
這個類是CoreData裏面很是重要的類。它有父上下文和子上下文的概念。通過了漫長的爬坑,終於在蘋果官方文檔中找到了關於它詳細的介紹。
這裏只截取parent store
這節來說
Managed object contexts
有一個父存儲,經過它來檢索數據,提交改變最開始在iOS5的以前,父存儲一直是
persistent store coordinator
。在iOS5以後。父存儲的類型能夠是其餘的Managed object contexts
。可是最終的根context必須是`persistent store coordinator``。協調者提升被管理的對象模型,調用各類對數據庫的請求。若是父存儲是一個
Managed object contexts
。查詢,保存的操做是被父存儲來協調的而不是persistent store coordinator
。這種方式有兩個好處,
- 1.在其餘線程中執行操做
2.管理廢棄的編輯,好比監視窗口、view
第一種場景,父上下文可以經過不一樣的線程從子中得到請求,重點部分:當在上下文中保存所作的改變的時候,改變只會被提交一次存儲,若是有子的上下文,改變將會推到他的父上下文,改變不會直接保存到數據庫,直到根上下文被保存纔會保存到數據庫(根管理對象的上下文的父上下文爲空)。除此以外,父上下文在保存以前不會從子中拉取數據的改變。若是你想最後提交數據的改變,必須保存子上下文,這樣就能夠推到父上下文中。
上下文的建立時經過線程來控制,也就是上下文和線程相關。[[NSThread currentThread] threadDictionary];
返回的字典就是處理數據方面的。
if ([NSThread isMainThread]) { return [self MR_defaultContext]; } else { int32_t targetCacheVersionForContext = contextsCacheVersion; NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary]; NSManagedObjectContext *threadContext = [threadDict objectForKey:kMagicalRecordManagedObjectContextKey]; NSNumber *currentCacheVersionForContext = [threadDict objectForKey:kMagicalRecordManagedObjectContextCacheVersionKey]; // 保證二者同時存在,或者同時不存在 NSAssert((threadContext && currentCacheVersionForContext) || (!threadContext && !currentCacheVersionForContext), @"The Magical Record keys should either both be present or neither be present, otherwise we're in an inconsistent state!"); // 不存在上下文 if ((threadContext == nil) || (currentCacheVersionForContext == nil) || ((int32_t)[currentCacheVersionForContext integerValue] != targetCacheVersionForContext)) { // 建立新的上下文 threadContext = [self MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]]; [threadDict setObject:threadContext forKey:kMagicalRecordManagedObjectContextKey]; [threadDict setObject:[NSNumber numberWithInteger:targetCacheVersionForContext] forKey:kMagicalRecordManagedObjectContextCacheVersionKey]; } return threadContext; }
在配置的時候就會默認建立兩種上下文,一個根上下文,和協調者直接通訊的,一個是主線程相關的默認上下文。默認上下文是根上下文的子。
MR_saveWithBlock
這個方法,本身在寫的時候就犯錯了。開看看實現
- (void)MR_saveWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(MRSaveCompletionHandler)completion; { NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextWithParent:self]; [localContext performBlock:^{ [localContext MR_setWorkingName:NSStringFromSelector(_cmd)]; if (block) { block(localContext); } [localContext MR_saveWithOptions:MRSaveParentContexts completion:completion]; }]; }
是在當前的上下文中新建子而後經過子去保存,注意這裏的保存方法有個參數MRSaveParentContexts
,會連同父上下文一塊兒一般,
在保存的方法中有一段:
// Add/remove the synchronous save option from the mask if necessary MRSaveOptions modifiedOptions = saveOptions; if (saveSynchronously) { modifiedOptions |= MRSaveSynchronously; } else { modifiedOptions &= ~MRSaveSynchronously; } // If we're saving parent contexts, do so [[self parentContext] MR_saveWithOptions:modifiedOptions completion:completion];
相似於遞歸調用,最終會調用根上下文,也就是保存到了數據庫。
可是在這以前有個邏輯想到重要。也就是保存的上下文該沒有改變。若是被肯定是沒有改變的,那就不會中保存的邏輯。
__block BOOL hasChanges = NO; if ([self concurrencyType] == NSConfinementConcurrencyType) { hasChanges = [self hasChanges]; } else { [self performBlockAndWait:^{ hasChanges = [self hasChanges]; }]; } if (!hasChanges) { MRLogVerbose(@"NO CHANGES IN ** %@ ** CONTEXT - NOT SAVING", [self MR_workingName]); if (completion) { dispatch_async(dispatch_get_main_queue(), ^{ completion(NO, nil); }); } return; }
最後來一段有問題的代碼。
// 在默認的上下文中建立實體 Person *person = [Person MR_createEntity]; // 改變person,引發上下文的改變 person.name = @"test"; person.age = @(100); [[NSManagedObjectContext MR_defaultContext] MR_saveWithBlock:^(NSManagedObjectContext * _Nonnull localContext) { } completion:^(BOOL contextDidSave, NSError * _Nullable error) { }];
這段代碼不會保存成功。
由於在MR_saveWithBlock
建立一個繼承自上下文的心的localContext。然而person所作的改變是在默認上下文中,也便是localContext的父上下文。判斷是否改變是根據localContext來判斷的,結果就是hasChanges爲NO。最終致使保存不成功。
那麼改變一下就能夠了。也便是咱們本身來控制保存。
以下:
// 在默認的上下文中建立實體 Person *person = [Person MR_createEntity]; // 改變person,引發上下文的改變 person.name = @"test"; person.age = @(100); [[NSManagedObjectContext MR_defaultContext] MR_saveWithOptions:MRSaveParentContexts completion:^(BOOL contextDidSave, NSError * _Nullable error) { }];
多看官方文檔,多看三方庫wiki,多總結。
養成有耐心的習慣。勿急躁。