iOS 數(shù)據(jù)持久化

1.簡(jiǎn)介

數(shù)據(jù)持久存儲(chǔ)是一種非易失性存儲(chǔ)胳搞,在重啟動(dòng)計(jì)算機(jī)或設(shè)備后也不會(huì)丟失數(shù)據(jù)绷落。持久化技術(shù)主要用于MVC模型中的model層姥闪。其中目前再IOS平臺(tái)上主要使用如下的四種技術(shù):

  • 屬性列表
  • 對(duì)象歸檔
  • SQLite3
  • Core Data
2.沙盒(SandBox)

IOS中的沙盒機(jī)制(SandBox)是一種安全體系,它規(guī)定了應(yīng)用程序只能在為該應(yīng)用創(chuàng)建的文件夾內(nèi)讀取文件砌烁,不可以訪問(wèn)其他地方的內(nèi)容筐喳。所有的非代碼文件都保存在這個(gè)地方,比如圖片函喉、聲音避归、屬性列表和文本文件等。

沙盒結(jié)構(gòu)

每個(gè)應(yīng)用程序沙盒主要包含以下三個(gè)目錄:

【注】:也包含其他目錄管呵,但不設(shè)計(jì)數(shù)據(jù)持久化梳毙。比如:AppName.app 目錄:這是應(yīng)用程序的程序包目錄,包含應(yīng)用程序的本身捐下。由于應(yīng)用程序必須經(jīng)過(guò)簽名账锹,所以您在運(yùn)行時(shí)不能對(duì)這個(gè)目錄中的內(nèi)容進(jìn)行修改,否則可能會(huì)使應(yīng)用程序無(wú)法啟動(dòng)坷襟。

****1) Documents:****
????應(yīng)用程序可以將數(shù)據(jù)存儲(chǔ)在Documents目錄中奸柬。在此目錄中的文件可以被共享,其中本文中的4種數(shù)據(jù)持久化技術(shù)都涉及該目錄啤握。
????****2) Library:****
????這個(gè)目錄下有兩個(gè)子目錄: ****Caches 和 Preferences ****
????****① Preferences**** 目錄:包含應(yīng)用程序的偏好設(shè)置文件鸟缕。您不應(yīng)該直接創(chuàng)建偏好設(shè)置文件,而是應(yīng)該使用NSUserDefaults類來(lái)取得和設(shè)置應(yīng)用程序的偏好。
????****② Caches**** 目錄:用于存放應(yīng)用程序?qū)S玫闹С治募樱4鎽?yīng)用程序再次啟動(dòng)過(guò)程中需要的信息授段。
????****3)Tmp:****
????Tmp目錄供應(yīng)用存儲(chǔ)臨時(shí)文件。在不需要這些文件時(shí)番甩,應(yīng)用要負(fù)責(zé)刪除tmp中等待文件侵贵,以免占用文件系統(tǒng)的空間。

獲取目錄

獲取沙盒主路徑:NSHomeDirectory()
獲取Document路徑:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]
獲取Library路徑:[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject]
ps:第一個(gè)參數(shù)指明要查找的內(nèi)容缘薛,第二參數(shù)指明查找的范圍窍育,其中返回值說(shuō)明返回的是數(shù)組,但是由于在沙盒中只有一個(gè)documents宴胧,或是只有一個(gè)library漱抓,那么返回的數(shù)組只有一個(gè)元素,我們只需取得第一個(gè)元素即可恕齐。
獲取Temp路徑:NSTemporaryDirectory( )
獲取應(yīng)用包路徑:[[NSBundle mainBundle] pathForAuxiliaryExecutable:@”“]
3.NSFileManager 文件管理類

1)NSFileManager可以完成沙盒路徑下的文件管理工作乞娄,包括目錄創(chuàng)建、文件創(chuàng)建显歧、刪除仪或、移動(dòng)、復(fù)制等士骤。
2)NSFileManager使用單例方法訪問(wèn):
??NSFileManager *fileManager = [NSFileManager defaultManager];
3)判斷指定路徑下是否存在文件:

- (BOOL)fileExistsAtPath:(NSString *)path;
- (BOOL)fileExistsAtPath:(NSString *)path isDirectory:(BOOL *)isDirectory;

4)文件處理方法:

// 1)創(chuàng)建文件
- (BOOL)createFileAtPath:(NSString *)path contents:(NSData *)data attributes:(NSDictionary *)attr;
// 2)創(chuàng)建目錄
- (BOOL)createDirectoryAtPath:(NSString *)path attributes:(NSDictionary *)attributes;
// 3)復(fù)制文件
- (BOOL)copyItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error;
// 4)移動(dòng)文件
- (BOOL)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error;
// 5)刪除文件
- (BOOL)removeItemAtPath:(NSString *)path error:(NSError *)error;
4范删、NSUserDefaults(偏好設(shè)置)

NSUserDefaults單例以key-value的形式存儲(chǔ)了一系列偏好設(shè)置,key是名稱拷肌,value是相應(yīng)的數(shù)據(jù)到旦。存取數(shù)據(jù)時(shí)可以使用方法objectForKey:和setObject:forKey:來(lái)把對(duì)象存儲(chǔ)到相應(yīng)的plist文件中,或者讀取廓块,既然是plist文件厢绝,那么對(duì)象的類型則必須是plist文件可以存儲(chǔ)的類型:

  • NSNumber(NSInteger、float带猴、double)
  • NSString
  • NSDate
  • NSArray
  • NSDictionary
  • BOOL

1)NSUserDefaults 存儲(chǔ)數(shù)據(jù):

- (void)setObject:(id)value forKey:(NSString *)defaultName;

- (void)setInteger:(NSInteger)value forKey:(NSString *)defaultName;

- (void)setFloat:(float)value forKey:(NSString *)defaultName;

- (void)setDouble:(double)value forKey:(NSString *)defaultName;

- (void)setBool:(BOOL)value forKey:(NSString *)defaultName;

- (void)setURL:(NSURL *)url forKey:(NSString *)defaultName;

2)NSUserDefaults 讀取數(shù)據(jù):

- (id)objectForKey:(NSString *)defaultName;

- (NSInteger)integerForKey:(NSString *)defaultName;

- (float)floatForKey:(NSString *)defaultName;

- (double)doubleForKey:(NSString *)defaultName;

- (BOOL)boolForKey:(NSString *)defaultName;

- (NSURL *)URLForKey:(NSString *)defaultName;

3)NSUserDefaults同步數(shù)據(jù)到文件系統(tǒng):
- (BOOL)synchronize;

代碼示例:

// 如果想要將數(shù)據(jù)永久保存到NSUserDefaults中,代碼實(shí)現(xiàn)為:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

[defaults setObject:@”li“ forKey:@"name"];
[defaults setInteger:10 forKey:@"age"];
[defaults synchronize];

ps:其中懈万,方法synchronise是為了強(qiáng)制存儲(chǔ)拴清,其實(shí)并非必要,因?yàn)檫@個(gè)方法會(huì)在系統(tǒng)中默認(rèn)調(diào)用会通,但是你確認(rèn)需要馬上就存儲(chǔ)口予,這樣做是可行的。

// 將數(shù)據(jù)取出涕侈,只需要取出key 對(duì)應(yīng)的值就好了沪停,代碼如下:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

NSString *name = [defaults objectForKey:@"name"]
NSInteger age = [defaults integerForKey:@"age"];
5、屬性列表

屬性列表文件是一種xml文件,拓展名為plist(Property List)木张,F(xiàn)oundation框架中的數(shù)組和字典都可以與屬性列表文件互相轉(zhuǎn)換众辨。簡(jiǎn)單的說(shuō)就是調(diào)用數(shù)組或字典的方法(read或write)進(jìn)行xml文件的讀或?qū)懖僮鳌?br>  雖然可以將數(shù)組和字典轉(zhuǎn)換為XML文件,但只有某些對(duì)象才能被放置到集合(即數(shù)組和字典)中舷礼,來(lái)實(shí)現(xiàn)轉(zhuǎn)換鹃彻。這些可被放置到集合的類有如下:

  • Array、NSArray妻献、NSMutableArray蛛株;
  • Dictionary、NSDictionary育拨、NSMutableDictionary谨履;
  • NSData、NSMutableData熬丧;
  • String屉符、NSString、NSMutableString锹引;
  • NSNumber矗钟;
  • NSDate
集合 方法(Object-C) 描述
NSArray +arrayWithContentsOfFile(讀) 靜態(tài)創(chuàng)建工廠方法,用于從屬性列表文件中讀取數(shù)據(jù)嫌变,創(chuàng)建 NSArray對(duì)象吨艇。Swift沒(méi)有對(duì)應(yīng)的構(gòu)造器。
NSArray initWithContentsOfFile(讀) 構(gòu)造器腾啥,用于從屬性列表文件中讀取數(shù)據(jù)东涡,創(chuàng)建NSArray對(duì)象。Swift表示為convenience init?(contentsOfFile aPath:String)倘待。
NSArray -writeToFile:atomically(寫(xiě)) 該方法把NSArray對(duì)象寫(xiě)入屬性列表文件中疮跑。Swift是writeToFile。
NSDictionary +dictionaryWithContentsOfFile(讀) 靜態(tài)工廠方法凸舵,從屬性列表文件中讀取數(shù)據(jù)祖娘,創(chuàng)建NSDictionary對(duì)象。Swift沒(méi)有對(duì)應(yīng)的構(gòu)造器啊奄。
NSDictionary -initWithContentsOfFile(讀) 構(gòu)造器渐苏,從屬性列表文件中讀取數(shù)據(jù),創(chuàng)建NSDictionary對(duì)象菇夸。Swift表示成convenience init?(contentsOfFile aPath:String)琼富。
NSDictionary -writeToFile:atomically(寫(xiě)) 將NSDictionary對(duì)象寫(xiě)入到屬性列表文件中,Swift是writeToFile庄新。

【注】:由于Swift代碼中的writeToFile(toFile:鞠眉,atomically:)方法實(shí)際屬于ObjectC的NSArray或NSDictionary類薯鼠。所以要使用這個(gè)方法時(shí),需要將Swift的Array(Dictionary)強(qiáng)制轉(zhuǎn)換為NSArray(NSDictionary)械蹋。如:
let nsArray = array as! NSArray

代碼示例:

// 用swift簡(jiǎn)寫(xiě)一下代碼的執(zhí)行邏輯出皇,至于怎么優(yōu)化格式封裝方式這里不做說(shuō)明.
class ViewController: UIViewController {
    open func create() {
        // 創(chuàng)建swift的數(shù)組
        let array:[String] = ["1","2","3"];
        // 將swift的數(shù)組轉(zhuǎn)換為ObjectC的數(shù)組,并將數(shù)組寫(xiě)入屬性列表文件
        let writeArray:NSArray = array as NSArray;
        // 寫(xiě)入到屬性列表文件中
        writeArray.write(toFile: self.applicationDocumentsDirectoryFile(), atomically: true);
        
        // 從屬性列表文件中讀取數(shù)組
        let readArray = NSArray(contentsOfFile: self.applicationDocumentsDirectoryFile()) as! [String];
        // 輸出驗(yàn)證
        for str in readArray {
            print(str);
        }
    }

    // 獲得數(shù)據(jù)庫(kù)文件路徑
    func applicationDocumentsDirectoryFile() -> String {
        let documentDirectory: NSArray = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) as NSArray
        let path = (documentDirectory[0] as AnyObject).appendingPathComponent("NotesList.plist") as String
        print("path : \(path)")
        return path
    }
    
    // 【注】:這里用的時(shí)候直接self.朝蜘,每次都要get一下翘瓮,在真正項(xiàng)目中盡量不要這樣調(diào)用媒殉,可以設(shè)置一個(gè)私有的沙箱目錄中屬性列表文件路徑。
    // fileprivate var plistFilePath: String!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        // 調(diào)用函數(shù)
        self.create()
    }
}
6、對(duì)象歸檔

歸檔與屬性列表方式不同窃页,屬性列表只有指定的一些對(duì)象才能進(jìn)行持久化晌杰,而歸檔是任何實(shí)現(xiàn)了NSCopying協(xié)議的對(duì)象都可以被持久化翼悴,其中歸檔涉及兩個(gè)類:NSKeyedArchiver和NSKeyedUnarchiver刁赦。歸檔和反歸檔都是采用健值對(duì)的形式編碼。

實(shí)現(xiàn)協(xié)議:

方法 描述
-(void)encodeWithCoder:(NSCoder *)encoder 對(duì)象進(jìn)行序列化的方法煮剧,把對(duì)象信息封裝在NSCoder對(duì)象中斥滤。
-(instancetype)initWithCoder:(NSCoder *)decoder 對(duì)象的反序列化方法,通過(guò)NSCoder對(duì)象獲取相應(yīng)數(shù)據(jù)勉盅。

其中encoder和decoder是提供給用戶進(jìn)行編碼和解碼的流對(duì)象佑颇,兩個(gè)都是采用健值對(duì)的形式進(jìn)行操作,并根據(jù)不同的數(shù)據(jù)類型提供不同的寫(xiě)入和讀取的方法草娜,如encodeInt挑胸、encodeFloat、decodeIntForKey和decodeFloatForKey等方法宰闰。
如下是Note類實(shí)現(xiàn)的兩個(gè)協(xié)議的程序:

@interface Note : NSObject<NSCoding>

@property(nonatomic, strong) NSDate *date;
@property(nonatomic, strong) NSString *content;

@end

------m------
@implementation Note

@synthesize date = _date;
@synthesize content = _content;

// - 歸檔(也叫序列化)
// - 當(dāng)將一個(gè)自定義對(duì)象保存到文件的時(shí)候就會(huì)調(diào)用該方法茬贵;
// - 在該方法中,說(shuō)明如何存儲(chǔ)自定義對(duì)象的屬性,也就是說(shuō)在該方法中說(shuō)清楚存儲(chǔ)自定義對(duì)象的哪些屬性移袍;
- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:_date forKey:@"date"];
    [aCoder encodeObject:_content forKey:@"content"];
}

// - 反歸檔(也叫反序列化)
// - 當(dāng)文件中讀取一個(gè)對(duì)象的時(shí)候就會(huì)調(diào)用該方法解藻;
// - 在該方法中說(shuō)明如何讀取保存在文件中的對(duì)象,也就是說(shuō)在該方法中說(shuō)清楚怎么讀取文件中的對(duì)象
- (id)initWithCoder:(NSCoder *)aDecoder {
    if (self=[super init]) {
    self.date = [aDecoder decodeObjectForKey:@"date"];
    self.content = [aDecoder decodeObjectForKey:@"content"];
    }
    return self;
}
@end

【注】:要將一個(gè)自定義的類進(jìn)行歸檔,那么類里面的每個(gè)屬性都必須是可以被歸檔的葡盗,如果是不能歸檔的類型螟左,我們可以把他轉(zhuǎn)化為NSValue進(jìn)行歸檔,然后在讀出來(lái)的時(shí)候在轉(zhuǎn)化為相應(yīng)的類戳粒。通過(guò)****plist保存的數(shù)據(jù)是直接顯示的****路狮,不安全。通過(guò)歸檔方法保存的數(shù)據(jù)在文件中打開(kāi)是亂碼的蔚约,****加密的****,更安全涂籽。

歸檔與反歸檔
 ****所謂歸檔:****將復(fù)雜對(duì)象轉(zhuǎn)化為NSData類型數(shù)據(jù)(復(fù)雜-->歸檔-->NSData--->WriteToFile)
注意:歸檔是將對(duì)象轉(zhuǎn)化為數(shù)據(jù)字節(jié),以文件的形式存儲(chǔ)在磁盤上

歸檔化過(guò)程是使用NSKeyedArchiver對(duì)象歸檔數(shù)據(jù)苹祟,其操作步驟如下:
1) 創(chuàng)建NSMutableData對(duì)象:只需使用構(gòu)造函數(shù)init()創(chuàng)建為空的對(duì)象;
2) 創(chuàng)建NSKeyedArchiver對(duì)象:用其構(gòu)造函數(shù)initForWritingWithMutableData()創(chuàng)建對(duì)象;
3) 歸檔對(duì)象:調(diào)用NSKeyedArchiver對(duì)象的encodeObject()方法寫(xiě)入被歸檔的對(duì)象树枫;
4) 完成操作:調(diào)用NSKeyedArchiver對(duì)象的finishEncoding()方法完成寫(xiě)入操作直焙;
5) 寫(xiě)入文件:調(diào)用NSMutableData對(duì)象的writeToFile()寫(xiě)入到指定的目錄下;

****所謂反歸檔:****將NSData類型數(shù)據(jù)轉(zhuǎn)化為復(fù)雜對(duì)象(讀取文件-->NSData-->反歸檔--->復(fù)雜對(duì)象)

對(duì)象反歸檔的過(guò)程與對(duì)象歸檔過(guò)程類似砂轻,不同的是在創(chuàng)建NSMutableData對(duì)象時(shí)奔誓,需要指定目錄路徑,且不需要寫(xiě)入文件中搔涝。其操作步驟如下:
1) 創(chuàng)建NSMutableData對(duì)象:指定文件路徑調(diào)用構(gòu)造函數(shù)initWithContentsOfFile()創(chuàng)建對(duì)象厨喂;
2) 創(chuàng)建NSKeyedUnarchiver 對(duì)象:用其構(gòu)造函數(shù)initForReadingWithData()創(chuàng)建對(duì)象;
3) 反歸檔對(duì)象:調(diào)用NSKeyedUnarchiver 對(duì)象的decodeObjectForKey()方法寫(xiě)入被歸檔的對(duì)象庄呈;
4) 完成操作:調(diào)用NSKeyedUnarchiver 對(duì)象的finishEncoding()方法完成寫(xiě)入操作蜕煌;

代碼示例:

#define FILE_NAME @"NotesList.archive"
#define ARCHIVE_KEY @"NotesList"

// 獲取Documents的路徑,并創(chuàng)建NotesList.archive的路徑 
- (NSString *)applicationDocumentsDirectoryFile {
    NSString *documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *path = [documentDirectory stringByAppendingPathComponent:FILE_NAME];
    NSLog(@"路徑:%@", path);//Documents
    return path;
}

// 創(chuàng)建被保存的數(shù)據(jù)對(duì)象
Note *note = [[Note alloc] init];
note.date = [[NSDate alloc] init];
note.content = @"li'xiang";

NSString *path = [self applicationDocumentsDirectoryFile];
// 設(shè)置數(shù)據(jù)區(qū)诬留,并將其連接到一個(gè)NSKeyedArchiver對(duì)象
NSMutableData *theData = [NSMutableData data];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]
                                  initForWritingWithMutableData:theData];
// 進(jìn)行數(shù)據(jù)歸檔 
[archiver encodeObject:array forKey:ARCHIVE_KEY];
// 發(fā)出歸檔完成消息
[archiver finishEncoding];
// 將存檔的數(shù)據(jù)區(qū)寫(xiě)入文件中斜纪。
[theData writeToFile:path atomically:YES];

// 進(jìn)行數(shù)據(jù)反歸檔 
NSString *path = [self applicationDocumentsDirectoryFile];
NSData *theData = [NSData dataWithContentsOfFile:path];
NSKeyedUnarchiver *archiver = [[NSKeyedUnarchiver alloc]
                                        initForReadingWithData:theData];
Note *listData = [archiver decodeObjectForKey:ARCHIVE_KEY];
// 結(jié)束反歸檔
[archiver finishDecoding];

編碼和反編碼C語(yǔ)言類型
 NSKeyedArchiver 和NSKeyedUnarchiver類不能對(duì)structures, arrays, 和bit fields類型進(jìn)行編碼或反編碼。
1)指針類型
 由于不能歸檔指針類型文兑,所以若需要只能歸檔指針?biāo)赶虻膶?duì)象盒刚。但對(duì)于C語(yǔ)言的字符串類型(char*)卻是支持的,它比較特殊绿贞,可以使用 encodeBytes:length:forKey:方法進(jìn)行歸檔因块。
2)基本數(shù)據(jù)類型的數(shù)組
 一種基本的方法是一個(gè)元素一個(gè)元素歸檔,如"theArray[0]", "theArray[1]"這樣一個(gè)個(gè)的進(jìn)行歸檔樟蠕,非常簡(jiǎn)單贮聂。
3)對(duì)象類型的數(shù)組
 對(duì)于C語(yǔ)言的數(shù)組且元素類型是對(duì)象,那么最簡(jiǎn)單的方式是將該數(shù)組用NSArray進(jìn)行封裝寨辩。這樣就可以進(jìn)行歸檔了吓懈;當(dāng)進(jìn)行反歸檔時(shí),也是獲得NSArray對(duì)象靡狞,然后一個(gè)個(gè)地拆封為C語(yǔ)言的數(shù)組元素耻警。
4)數(shù)據(jù)結(jié)構(gòu)類型
 可以將結(jié)構(gòu)體的封裝為一個(gè)對(duì)象,對(duì)象的每個(gè)成員是結(jié)構(gòu)體的每個(gè)成員甸怕。若需要?dú)w檔則先將結(jié)構(gòu)體封裝為OC對(duì)象甘穿,然后再歸檔;而反歸檔則是將其解析為OC對(duì)象梢杭,然后轉(zhuǎn)換為C語(yǔ)言結(jié)構(gòu)體温兼。

7、SQLite3

SQLite是目前主流的第三方的數(shù)據(jù)庫(kù)嵌入式關(guān)系型數(shù)據(jù)庫(kù)武契,其最主要的特點(diǎn)就是輕量級(jí)募判、跨平臺(tái)荡含,當(dāng)前很多嵌入式都將其作為數(shù)據(jù)庫(kù)首選。SQLite提供多種方式的接口届垫,有命令行的接口释液、客戶端及多種語(yǔ)言的API接口。在iOS中需要使用C語(yǔ)言語(yǔ)法進(jìn)行數(shù)據(jù)庫(kù)操作装处、訪問(wèn)(無(wú)法使用ObjC直接訪問(wèn)误债,因?yàn)閘ibqlite3框架基于C語(yǔ)言編寫(xiě))。

配置
 在項(xiàng)目中的Build Phases選項(xiàng)中的Link Binary With Libraries下妄迁,點(diǎn)擊其"+"號(hào)寝蹈,從而添加所需要的libsqlite3.tbd或者是libsqlite3.0.tbd庫(kù)。
在需要操作sqlite數(shù)據(jù)的文件中導(dǎo)入如下頭文件:
oc直接添加#import "sqlite3.h"
 swift添加橋接文件判族,新建一個(gè).h文件躺盛,在里邊添加#import "SQLite3.h"然后在進(jìn)入Buil Setting選項(xiàng)中,搜索Bridging Header形帮,雙擊添加相對(duì)路徑:$(SRCROOT)/文件在工程下路徑槽惫,如在主目錄這里刪除/SQLite_Bridge.h

【注】:實(shí)際上libsqlite3.0.tbd本身是個(gè)替身,它指向libsqlite3.tbd辩撑。也就是說(shuō)在項(xiàng)目里如果你添加libsqlite3.tbd和添加libsqlite3.0.tbd其實(shí)是添加了同一個(gè)文件界斜,如果引用的是libsqlite3.0.tbd,SQLite庫(kù)更新項(xiàng)目中不必修改了合冀。


libsqlite.png
方法 描述
sqlite3 *db 數(shù)據(jù)庫(kù)句柄各薇,跟文件句柄FILE很類似
sqlite3_stmt *stmt 這個(gè)相當(dāng)于ODBC的Command對(duì)象,用于保存編譯好的SQL語(yǔ)句
sqlite3_open() 打開(kāi)數(shù)據(jù)庫(kù)君躺,沒(méi)有數(shù)據(jù)庫(kù)時(shí)創(chuàng)建
sqlite3_exec() 執(zhí)行非查詢的sql語(yǔ)句
sqlite3_step() 在調(diào)用sqlite3_prepare后峭判,使用這個(gè)函數(shù)在記錄集中移動(dòng)
sqlite3_close() 關(guān)閉數(shù)據(jù)庫(kù)文件
sqlite3_column_text() 取text類型的數(shù)據(jù)
sqlite3_column_blob() 取blob類型的數(shù)據(jù)
sqlite3_column_int() 取int類型的數(shù)據(jù)

代碼示例:

OC實(shí)在沒(méi)啥可寫(xiě)的,這里就用swift簡(jiǎn)單寫(xiě)一下邏輯棕叫,詳細(xì)的就不多提了林螃。

class ViewController: UIViewController {
    
    // 定義數(shù)據(jù)庫(kù)變量
    var db:OpaquePointer? = nil //sqlite3 *db
    
    // 查詢數(shù)據(jù)方法
    public func findAll() {
        let path = self.applicationDocumentsDirectoryFile()
        let cpath = path.cString(using: String.Encoding.utf8)
        
        var statement:OpaquePointer? = nil
        
        if sqlite3_open(cpath!, &db) != SQLITE_OK {
            // 數(shù)據(jù)庫(kù)打開(kāi)失敗。
            NSLog("數(shù)據(jù)庫(kù)打開(kāi)失敗俺泣。")
        } else {
            let sql = "SELECT cdate,content FROM Note"
            let cSql = sql.cString(using: String.Encoding.utf8)
            //預(yù)處理過(guò)程
            if sqlite3_prepare_v2(db, cSql!, -1, &statement, nil) == SQLITE_OK {
                
                // 執(zhí)行
                while sqlite3_step(statement) == SQLITE_ROW {
                    if let strContent = getColumnValue(index:0, stmt:statement!) {
                        NSLog("數(shù)據(jù)打印:%@", strContent)
                    }
                }
            }
        }
        
        defer {
            print("關(guān)閉數(shù)據(jù)庫(kù)")
            sqlite3_close(db)
        }
        defer {
            print("釋放語(yǔ)句對(duì)象")
            sqlite3_finalize(statement)
        }
    }
    
    // 插入數(shù)據(jù)與查詢數(shù)據(jù)
    public func create() {
        let path = self.applicationDocumentsDirectoryFile()
        let cpath = path.cString(using: String.Encoding.utf8)
        
        var statement: OpaquePointer? = nil
        
        if sqlite3_open(cpath!, &db) != SQLITE_OK {
            //數(shù)據(jù)庫(kù)打開(kāi)失敗疗认。
            NSLog("數(shù)據(jù)庫(kù)打開(kāi)失敗。")
        } else {
            let sql = "INSERT OR REPLACE INTO note (cdate, content) VALUES (?,?)"
            let cSql = sql.cString(using: String.Encoding.utf8)
            // 預(yù)處理過(guò)程
            if sqlite3_prepare_v2(db, cSql!, -1, &statement, nil) == SQLITE_OK {
                
                let cContent = "lixiang".cString(using: String.Encoding.utf8)
                // 綁定參數(shù)開(kāi)始
                sqlite3_bind_text(statement, 1, cContent!, -1, nil)
                // 執(zhí)行插入
                if sqlite3_step(statement) != SQLITE_DONE {
                    // 插入數(shù)據(jù)失敗伏钠。
                    NSLog("插入數(shù)據(jù)失敗横漏。")
                }
            }
        }
        
        defer {
            print("釋放語(yǔ)句對(duì)象")
            sqlite3_finalize(statement)
        }
        
        defer {
            print("關(guān)閉數(shù)據(jù)庫(kù)")
            sqlite3_close(db)
        }
    }
    
    // 獲得字段數(shù)據(jù)
    private func getColumnValue(index: CInt, stmt: OpaquePointer)->String? {
        
        if let ptr = UnsafeRawPointer.init(sqlite3_column_text(stmt, index)) {
            let uptr = ptr.bindMemory(to:CChar.self, capacity:0)
            let txt = String(validatingUTF8:uptr)
            return txt
        }
        return nil
    }
    
    // 創(chuàng)建數(shù)據(jù)庫(kù)
    func createEditableCopyOfDatabaseIfNeeded() {
        
        let path = self.applicationDocumentsDirectoryFile()
        let cpath = path.cString(using: String.Encoding.utf8)
        
        // 第一個(gè)參數(shù):數(shù)據(jù)庫(kù)文件路徑  第二個(gè)參數(shù):數(shù)據(jù)庫(kù)對(duì)象
        if sqlite3_open(cpath!, &db) != SQLITE_OK {
            NSLog("數(shù)據(jù)庫(kù)打開(kāi)失敗。")
        } else {
            // 建表的SQL語(yǔ)句
            let sql = "CREATE TABLE IF NOT EXISTS Note (cdate TEXT PRIMARY KEY, content TEXT)"
            let cSql = sql.cString(using: String.Encoding.utf8)
            
            if (sqlite3_exec(db,cSql!, nil, nil, nil) != SQLITE_OK) {
                // 建表失敗熟掂。
                NSLog("建表失敗缎浇。")
            }
            
        }
        defer {
            print("關(guān)閉數(shù)據(jù)庫(kù)")
            sqlite3_close(db)
        }
    }
    // 獲得數(shù)據(jù)庫(kù)文件路徑
    private func applicationDocumentsDirectoryFile() ->String {
        let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) as NSArray
        let path = (documentDirectory[0] as AnyObject).appendingPathComponent("NotesList.sqlite3") as String
        print("path : \(path)")
        return path
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        // 調(diào)用函數(shù)
        self.createEditableCopyOfDatabaseIfNeeded()
        self.create()
        self.findAll()
    }
}

ps:別忘了引入的時(shí)候創(chuàng)建橋接文件哦!
8、Core Data

Core Data是iOS的一個(gè)持久化框架赴肚,它提供了對(duì)象-關(guān)系映射(ORM)的功能华畏,即能夠?qū)⒊绦蛑械膶?duì)象(swift或Object-C中類的實(shí)例)轉(zhuǎn)化成數(shù)據(jù)鹏秋,保存在SQLite數(shù)據(jù)庫(kù)文件中尊蚁,也能夠?qū)⒈4嬖跀?shù)據(jù)庫(kù)中的數(shù)據(jù)還原成程序中的對(duì)象亡笑。在此數(shù)據(jù)操作期間,我們不需要編寫(xiě)任何SQL語(yǔ)句横朋。

對(duì)象-關(guān)系模型.png

左邊是關(guān)系模型仑乌,即數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)里面有張person表琴锭,person表里面有id晰甚、name、age三個(gè)字段决帖,而且有2條記錄厕九;右邊是對(duì)象模型,可以看到地回,有2個(gè)OC對(duì)象扁远;利用Core Data框架,我們就可以輕松地將數(shù)據(jù)庫(kù)里面的2條記錄轉(zhuǎn)換成2個(gè)OC對(duì)象刻像,也可以輕松地將2個(gè)OC對(duì)象保存到數(shù)據(jù)庫(kù)中畅买,變成2條表記錄,而且不用寫(xiě)一條SQL語(yǔ)句细睡。

Core Data搭建結(jié)構(gòu)

描述
NSManagedObjectModel 稱為被管理對(duì)象模型類谷羞,是系統(tǒng)中的"實(shí)體",與數(shù)據(jù)庫(kù)中的表對(duì)象對(duì)應(yīng)溜徙,可以了解為圖4中對(duì)象的結(jié)合湃缎,該模型是通過(guò)項(xiàng)目中的.xcdatamodeld文件進(jìn)行聲明的。
NSPersisntentStoreCoordinator 稱為持久化存儲(chǔ)協(xié)調(diào)器類蠢壹,在持久化對(duì)象存儲(chǔ)之上提供了一個(gè)接口嗓违,可以把它考慮成為數(shù)據(jù)庫(kù)的連接。即相當(dāng)是SQLite數(shù)據(jù)庫(kù)中的SQLite3類型知残。
NSManageedObjectContext 稱為被管理對(duì)象上下文類靠瞎,在上下文中可以查找、刪除和插入對(duì)象求妹,然后通過(guò)棧同步到持久化對(duì)象存儲(chǔ)乏盐,即相當(dāng)是SQLite數(shù)據(jù)庫(kù)中的語(yǔ)句(sqlite3_Stmt類型)。其中程序員主要使用該實(shí)例對(duì)象間接地與數(shù)據(jù)庫(kù)進(jìn)行交互制恍。
Core Data結(jié)構(gòu).png

創(chuàng)建模型文件的過(guò)程:
創(chuàng)建模板:
1)創(chuàng)建項(xiàng)目時(shí)父能,勾選"Use core Data"的復(fù)選框方式創(chuàng)建。

創(chuàng)建工程時(shí)創(chuàng)建.png

2)如果創(chuàng)建工程時(shí)沒(méi)有創(chuàng)建净神,可以同過(guò)command+N何吝,的方式創(chuàng)建溉委。


后期創(chuàng)建.png

添加實(shí)體:
 以一對(duì)一雙向關(guān)聯(lián)的兩個(gè)實(shí)體為例,即Person中有Card屬性爱榕,Card中有Person屬性瓣喊。
 人實(shí)體中有:姓名(年齡),年齡(age)黔酥,卡(身份證)三個(gè)屬性
 卡實(shí)體中有:無(wú)(號(hào)碼)藻三,人(人)兩個(gè)屬性

添加實(shí)體.png

為兩個(gè)實(shí)體添加實(shí)體屬性:


實(shí)體1.png
實(shí)體2.png

建立兩個(gè)實(shí)體的關(guān)系:


表示人中有個(gè)卡類型的卡屬性,目的就是建立人跟卡之間的一對(duì)一關(guān)聯(lián)的關(guān)系.png
表示卡中有個(gè)人類型的人屬性跪者,目的就是建立卡跟人之間的一對(duì)一關(guān)聯(lián)的關(guān)系.png

代碼示例:
1.搭建上下文環(huán)境:

 // 從應(yīng)用程序包中加載模型文件
    NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
    // 傳入模型對(duì)象棵帽,初始化NSPersistentStoreCoordinator
    NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
    // 構(gòu)建SQLite數(shù)據(jù)庫(kù)文件的路徑
    NSString *docs = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES)lastObject];
    NSURL *url = [NSURL fileURLWithPath:[docs stringByAppendingPathComponent:@"person.data" ]];
    // 添加持久化存儲(chǔ)庫(kù),這里使用SQLite作為存儲(chǔ)庫(kù)
    NSError *error = nil;
    NSPersistentStore *store = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error];
    if(store == nil) {  // 直接拋異常
        [NSException raise:@"添加數(shù)據(jù)庫(kù)錯(cuò)誤" 格式:@"%@",[error localizedDescription]];
    }
    // 初始化上下文渣玲,設(shè)置persistentStoreCoordinator屬性
    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
    context.persistentStoreCoordinator = psc;  

2.添加數(shù)據(jù)到數(shù)據(jù)庫(kù):

    // 傳入上下文逗概,創(chuàng)建一個(gè)人實(shí)體對(duì)象
    NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person"  inManagedObjectContext:context];
    // 設(shè)置人的簡(jiǎn)單屬性
    [person setValue:@"XN" forKey:@"name"];
    [person setValue:[NSNumber numberWithInt:24] forKey:@"age"];
    // 傳入上下文,創(chuàng)建一張卡實(shí)體對(duì)象
    NSManagedObject *card = [NSEntityDescription insertNewObjectForEntityForName:@"Card"  inManagedObjectContext:context];
    [card setValue:@"1234567890" forKey:@"no"];
    // 設(shè)置人與卡之間的關(guān)聯(lián)關(guān)系
    [person setValue:card forKey:@"card"];
    // 利用上下文對(duì)象忘衍,將數(shù)據(jù)同步到持久化存儲(chǔ)庫(kù)
    NSError *error = nil;
    BOOL success = [上下文保存:&error];
    if(!success) {
        [NSException raise:@"訪問(wèn)數(shù)據(jù)庫(kù)錯(cuò)誤"格式:@"%@", [error localizedDescription]];
    }
    // 如果是想做更新操作:只要在更改了實(shí)體對(duì)象的屬性后調(diào)用[context save:&error]逾苫,就能將更改的數(shù)據(jù)同步到數(shù)據(jù)庫(kù)

3.從數(shù)據(jù)庫(kù)中查詢數(shù)據(jù):

    // 初始化一個(gè)查詢請(qǐng)求
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    //設(shè)置要查詢的實(shí)體
    request.entity = [NSEntityDescription entityForName:@"Person"  inManagedObjectContext:context];
    // 設(shè)置排序(按照年齡降序)
    NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"age"  ascending:NO];
    request.sortDescriptors = [NSArray arrayWithObject:sort];
    // 設(shè)置條件過(guò)濾(搜索名稱中包含字符串“Itcast-1”的記錄,注意:設(shè)置條件過(guò)濾時(shí)間淑履,數(shù)據(jù)庫(kù)SQL語(yǔ)句中的%要用*來(lái)代替隶垮,所以%Itcast-1%應(yīng)該寫(xiě)成* 1 *)
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name like%@", @"* Itcast-1 *"];
    request.predicate = 謂詞;
    // 執(zhí)行請(qǐng)求
    NSError *error = nil;
    NSArray *objs = [context executeFetchRequest:request error:&error];
    
    // 遍歷數(shù)據(jù)
    for(NSManagedObject *obj in objs) {
        NSLog(@"name =%@", [obj valueForKey:@"name"])
    }

4.刪除數(shù)據(jù)庫(kù)中的數(shù)據(jù):

    // 傳入需要?jiǎng)h除的實(shí)體對(duì)象
    [context deleteObject:managedObject];
    // 將結(jié)果同步到數(shù)據(jù)庫(kù)
    NSError * error = nil;
    [上下文保存:&error];
    if(error) {
        [NSException raise:@"刪除錯(cuò)誤" 格式:@"%@", [error localizedDescription]];
    }

打開(kāi)CoreData的SQL語(yǔ)句輸出開(kāi)關(guān):
1.打開(kāi)產(chǎn)品,點(diǎn)擊EditScheme ...
2.點(diǎn)擊參數(shù)秘噪,在參數(shù)中啟動(dòng)中添加2項(xiàng)
1> -com.apple.CoreData.SQLDebug
2> 1

SQL輸出開(kāi)關(guān)1.png
SQL輸出開(kāi)關(guān)2.png

創(chuàng)建NSManagedObject的子類
 默認(rèn)情況下狸吞,利用核心數(shù)據(jù)取出的實(shí)體都是NSManagedObject類型的,能夠利用鍵 - 值對(duì)來(lái)存取數(shù)據(jù)指煎。但是一般情況下蹋偏,實(shí)時(shí)在存取數(shù)據(jù)的基礎(chǔ)上,有時(shí)還需要添加一些業(yè)務(wù)方法來(lái)完成一些其他任務(wù)至壤,那么就必須創(chuàng)建NSManagedObject子的類威始,實(shí)體類。

實(shí)體分類.png

代碼示例:

添加數(shù)據(jù):
Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];  
person.name = @"XN";  
person.age = [NSNumber numberWithInt:24];  
  
Card *card = [NSEntityDescription insertNewObjectForEntityForName:@”Card" inManagedObjectContext:context];  
card.no = @”1234567890";  
person.card = card;  
// 最后調(diào)用[context save&error];保存數(shù)據(jù)  
9像街、FMDB

iOS中原生的SQLite API在進(jìn)行數(shù)據(jù)存儲(chǔ)的時(shí)候黎棠,需要使用C語(yǔ)言中的函數(shù),操作比較麻煩镰绎。于是脓斩,就出現(xiàn)了一系列將SQLite API進(jìn)行封裝的庫(kù),FMDB畴栖。
 FMDB是一款簡(jiǎn)潔随静、易用的封裝庫(kù)。它是對(duì)libsqlite3框架的封裝,用起來(lái)的步驟與SQLite使用類似燎猛,并且它對(duì)于多線程的并發(fā)操作進(jìn)行了處理恋捆,所以是線程安全的。

核心類
FMDatabase
 FMDatabase對(duì)象就代表一個(gè)單獨(dú)的SQLite數(shù)據(jù)庫(kù)重绷,用來(lái)執(zhí)行SQL語(yǔ)句
 FMDatabase這個(gè)類是線程不安全的沸停,如果在多個(gè)線程中同時(shí)使用一個(gè)FMDatabase實(shí)例,會(huì)造成數(shù)據(jù)混亂等問(wèn)題
 為了保證線程安全论寨,F(xiàn)MDB提供方便快捷的FMDatabaseQueue類
FMResultSet
 使用FMDatabase執(zhí)行查詢后的結(jié)果集
FMDatabaseQueue
 用于在多線程中執(zhí)行多個(gè)查詢或更新星立,它是線程安全的,解決多個(gè)線程同時(shí)訪問(wèn)同個(gè)表而導(dǎo)致的崩潰問(wèn)題葬凳,串行隊(duì)列,不支持(block塊內(nèi))串行嵌套任務(wù)執(zhí)行

FMDB使用步驟
 下載FMDB文件,并將FMDB文件夾添加到項(xiàng)目中(也可使用CocoaPods導(dǎo)入)
 導(dǎo)入libsqlite3.0框架室奏,導(dǎo)入頭文件FMDatabase.h
 代碼實(shí)現(xiàn)火焰,與SQLite使用步驟相似,創(chuàng)建數(shù)據(jù)庫(kù)路徑胧沫,獲得數(shù)據(jù)庫(kù)路徑昌简,打開(kāi)數(shù)據(jù)庫(kù),然后對(duì)數(shù)據(jù)庫(kù)進(jìn)行增绒怨、刪纯赎、改、查操作南蹂,最后關(guān)閉數(shù)據(jù)庫(kù)犬金。

數(shù)據(jù)庫(kù)創(chuàng)建
 創(chuàng)建FMDatabase對(duì)象時(shí)參數(shù)為SQLite數(shù)據(jù)庫(kù)文件路徑,該路徑有三種情況六剥。

  • 具體文件路徑:如果不存在會(huì)自動(dòng)創(chuàng)建
  • 空字符串@"":會(huì)在臨時(shí)目錄創(chuàng)建一個(gè)空的數(shù)據(jù)庫(kù)晚顷,當(dāng)FMDatabase連接關(guān)閉時(shí),數(shù)據(jù)庫(kù)文件也被刪除
  • nil:會(huì)創(chuàng)建一個(gè)內(nèi)存中臨時(shí)數(shù)據(jù)庫(kù)疗疟,當(dāng)FMDatabase連接關(guān)閉時(shí)该默,數(shù)據(jù)庫(kù)會(huì)被銷毀

代碼示例:
1、使用FMDataBase類建立數(shù)據(jù)庫(kù):

  // 打開(kāi)數(shù)據(jù)庫(kù)-創(chuàng)建表
- (void)createEditableCopyOfDatabaseIfNeeded {
    // 獲得數(shù)據(jù)庫(kù)文件的路徑
    NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)  lastObject];
    NSString *fileName = [doc stringByAppendingPathComponent:@"student.sqlite"];
    // 獲得數(shù)據(jù)庫(kù)
    FMDatabase *db = [FMDatabase databaseWithPath:fileName];
    // 使用如下語(yǔ)句策彤,如果打開(kāi)失敗栓袖,可能是權(quán)限不足或者資源不足。通常打開(kāi)完操作操作后店诗,需要調(diào)用 close 方法來(lái)關(guān)閉數(shù)據(jù)庫(kù)裹刮。在和數(shù)據(jù)庫(kù)交互之前,數(shù)據(jù)庫(kù)必須是打開(kāi)的必搞。如果資源或權(quán)限不足無(wú)法打開(kāi)或創(chuàng)建數(shù)據(jù)庫(kù)必指,都會(huì)導(dǎo)致打開(kāi)失敗。
    if ([db open]) {// 數(shù)據(jù)庫(kù)打開(kāi)成功
        // 創(chuàng)建表
        BOOL result = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_student (id integer PRIMARY KEY AUTOINCREMENT, name text NOT NULL, age integer NOT NULL);"];
        if (result) {
            NSLog(@"創(chuàng)建表成功");
        } else {
            NSLog(@"創(chuàng)建表失敗");
        }
    }
    
    self.db = db;
}

// 插入數(shù)據(jù)
- (void)create {
    
    for (int i = 0; i < 10; i++) {
        NSString *name = [NSString stringWithFormat:@"miao-%d", arc4random_uniform(100)];
        // 不確定的參數(shù)用?來(lái)占位(后面參數(shù)必須是oc對(duì)象恕洲,塔橡;代表語(yǔ)句結(jié)束)
        [self.db executeUpdate:@"INSERT INTO t_student (name, age) VALUES (?, ?);", name, @(arc4random_uniform(40))];
        // 參數(shù)是數(shù)組的使用方式
        // [self.db executeUpdate:@"INSERT INTO t_student (name, age) VALUES (?, ?);" withArgumentsInArray:@[name, @(arc4random_uniform(40))]];
        // 不確定的參數(shù)用%@梅割、%d等來(lái)占位(參數(shù)為原始數(shù)據(jù)類型,執(zhí)行語(yǔ)句不區(qū)分大小寫(xiě))
        // [self.db executeUpdateWithFormat:@"INSERT INTO t_student (name, age) VALUES (%@, %d);", name, arc4random_uniform(40)];
    }
}

// 刪除數(shù)據(jù)
- (void)remove {
    // 刪除表數(shù)據(jù)
    // [self.db executeUpdate:@"DELETE FROM t_student;"];
    // 刪除整個(gè)表
    [self.db executeUpdate:@"DROP TABLE IF EXISTS t_student;"];    
    
    // 按照主鍵刪除
    // 不確定的參數(shù)用葛家?來(lái)占位 (后面參數(shù)必須是oc對(duì)象,需要將int包裝成OC對(duì)象)
    // [self.db executeUpdate:@"delete from t_student where id = ?;",@1];
    // 不確定的參數(shù)用%@户辞,%d等來(lái)占位
    // [self.db executeUpdateWithFormat:@"delete from t_student where name = %@;",@"李響"];
}

// 修改
- (void)modify {
    // [self.db executeUpdate:@"update t_student set name = ? where name = ?",newName,oldName];
}

// 查詢
- (void)findAll
{
    // 查詢整個(gè)表
    FMResultSet *resultSet = [self.db executeQuery:@"SELECT * FROM t_student"];
    
    // 根據(jù)條件查詢
    // FMResultSet *resultSet = [self.db executeQuery:@"select * from t_student where id=?",@1];
    
    // 遍歷結(jié)果
    while ([resultSet next]) {
        int ID = [resultSet intForColumn:@"id"];
        NSString *name = [resultSet stringForColumn:@"name"];
        int age = [resultSet intForColumn:@"age"];
        NSLog(@"%d %@ %d", ID, name, age);
    }
}

// 數(shù)據(jù)庫(kù)銷毀命令SQLdrop ...
- (void)destroy {
    // 如果表格存在 則銷毀
    [self.db executeUpdate:@"drop table if exists t_student;"];
}

2、使用FMDatabaseQueue類實(shí)現(xiàn)多線程操作:
 首先說(shuō)一下事物與非事物癞谒,事物是一個(gè)并發(fā)控制的基本單元底燎,所謂的事務(wù),它是一個(gè)操作序列弹砚,這些操作要么都執(zhí)行双仍,要么都不執(zhí)行,它是一個(gè)不可分割的工作單位桌吃。
 事物與非事物朱沃,簡(jiǎn)單的舉例來(lái)說(shuō)就是,事物就是把所有的東西打包在一起茅诱,一次性處理它逗物。而非事務(wù)就是一條一條的來(lái)執(zhí)行并且處理。

代碼示例:

// 打開(kāi)數(shù)據(jù)庫(kù)-創(chuàng)建表
- (void)establish {
    // 獲得數(shù)據(jù)庫(kù)文件的路徑
    NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)  lastObject];
    NSString *dbPath = [doc stringByAppendingPathComponent:@"test.sqlite"];
    NSLog(@"路徑:%@", dbPath);
    // 創(chuàng)建一個(gè)隊(duì)列
    self.dataBaseQueue = [FMDatabaseQueue databaseQueueWithPath:dbPath];
    [self.dataBaseQueue inDatabase:^(FMDatabase *db) {
        if ([db open]) {// 數(shù)據(jù)庫(kù)打開(kāi)成功
            // 判斷指定表是否存在
            if (![self checkTableExist:@"testTable" withFMData:db]) {
                // 創(chuàng)建表
                BOOL result =  [db executeUpdate:@"create table if not exists testTable (id integer PRIMARY KEY AUTOINCREMENT, name text)"];
                if (result) {
                    NSLog(@"創(chuàng)建表成功");
                } else {
                    NSLog(@"創(chuàng)建表失敗");
                }
            }
            
        }
        else
        {
            // 關(guān)閉數(shù)據(jù)庫(kù)
            [db close];
        }
    }];
}

// 判斷指定表是否存在
- (BOOL)checkTableExist:(NSString *)tableName withFMData:(FMDatabase *)dbDatabase {
    BOOL result;
    
    tableName = [tableName lowercaseString];
    
    FMResultSet *rs = [dbDatabase executeQuery:@"select [sql] from sqlite_master where [type] = 'table' and lower(name) = ?", tableName];
    
    result = [rs next];
    
    [rs close];
    
    return result;
    
}

插入數(shù)據(jù):

- (void)testResult {
    // 非事務(wù)插入數(shù)據(jù)
    NSDate *one = [NSDate date];
    [self.dataBaseQueue inDatabase:^(FMDatabase *db) {
        for (int i = 0; i < 500; i++) {
            BOOL iserror = [db executeUpdate:@"insert into testTable (name) values(?)",[NSString stringWithFormat:@"name-%d",i]];
            if (!iserror) {
                NSLog(@"插入失敗");
            }
        }
    }];
    
    NSDate *two = [NSDate date];
    NSTimeInterval first = [two timeIntervalSinceDate:one];
    NSLog(@"first = %lf",first);
    
    // 開(kāi)啟事務(wù)插入
    NSDate *three = [NSDate date];
    [self.dataBaseQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
        BOOL result = YES;
        for (int i = 500; i < 1000; i++) {
            result =  [db executeUpdate:@"insert into testTable (name) values(?)",[NSString stringWithFormat:@"name-%d",i]];
            if (!result) {
                NSLog(@"插入失敗");
                *rollback = YES;
                break;
            }
        }
    }];
    
    NSDate *four = [NSDate date];
    NSTimeInterval second = [four timeIntervalSinceDate:three];
    NSLog(@"second = %lf",second);
    
    // 手動(dòng)事物插入
    NSDate *five = [NSDate date];
    [self.dataBaseQueue inDatabase:^(FMDatabase *db) {
        
        /**
         *  操作放入事物中(加入事物操作)
         */
        [db beginTransaction];
        
        for (int i = 1000; i < 1500; i++) {
            BOOL iserror = [db executeUpdate:@"insert into testTable (name) values(?)",[NSString stringWithFormat:@"name-%d",i]];
            if (!iserror) {
                NSLog(@"插入失敗");
            }
        }
        
        /**
         *  提交事物
         */
        [db commit];
    }];
    
    NSDate *Six = [NSDate date];
    NSTimeInterval third = [Six timeIntervalSinceDate:five];
    NSLog(@"third = %lf",third);
}

輸出打由蟆:

2017-05-31 00:42:47.773 FMDB應(yīng)用[50737:15432041] 創(chuàng)建表成功
2017-05-31 00:42:48.079 FMDB應(yīng)用[50737:15432041] first = 0.306702
2017-05-31 00:42:48.084 FMDB應(yīng)用[50737:15432041] second = 0.004107
2017-05-31 00:42:48.089 FMDB應(yīng)用[50737:15432041] third = 0.004684

刪除數(shù)據(jù):

// 刪除數(shù)據(jù)
- (void)deleteData {
    //[self.dataBaseQueue inDatabase:^(FMDatabase *db) {
    //    [db executeUpdate:@"DELETE FROM testTable WHERE id=?",@1];
    //}];

    NSDate *five = [NSDate date];
    [self.dataBaseQueue inDatabase:^(FMDatabase *db) {
        [db executeUpdate:@"delete from testTable where id < 500"];
    }];
    
    NSDate *six = [NSDate date];
    NSTimeInterval third = [six timeIntervalSinceDate:five];
    NSLog(@"third = %lf",third);
    
    
    NSDate *seven = [NSDate date];
    [self.dataBaseQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
        [db executeUpdate:@"delete from testTable where  id >= 500"];
    }];
    
    NSDate *eight = [NSDate date];
    NSTimeInterval fourth = [eight timeIntervalSinceDate:seven];
    NSLog(@"fourth = %lf",fourth);
}

輸出打郁嶙俊:

2017-05-31 00:51:56.617 FMDB應(yīng)用[50890:15440944] third = 0.001500
2017-05-31 00:51:56.619 FMDB應(yīng)用[50890:15440944] fourth = 0.001703

更新數(shù)據(jù):

// 更新
- (void)update {
    //[self.dataBaseQueue inDatabase:^(FMDatabase *db) {
    //    [db executeUpdate:@"UPDATE testTable SET name='lx' WHERE id=?",@8];
    //}];
    
    
    // 如果要保證多個(gè)操作同時(shí)成功或者同時(shí)失敗,用事務(wù)摆寄,即把多個(gè)操作放在同一個(gè)事務(wù)中失暴。
    [self.dataBaseQueue inDatabase:^(FMDatabase *db) {
        // 開(kāi)啟事務(wù)
        [db beginTransaction];
        BOOL flag = [db executeUpdate:@"UPDATE testTable SET name='xiugai' WHERE id=?",@2];
        [db executeUpdate:@"UPDATE testTable SET name='zheli' WHERE id=?",@3];
        
        if (flag) {
            NSLog(@"數(shù)據(jù)更新成功");
            
        } else {
            NSLog(@"數(shù)據(jù)更新失敗");
            // 事務(wù)回滾
            // 發(fā)現(xiàn)情況不對(duì)時(shí),主動(dòng)回滾用下面語(yǔ)句椭迎。否則是根據(jù)commit結(jié)果锐帜,如成功就成功,如不成功才回滾
            [db rollback];
            [db executeUpdate:@"UPDATE testTable SET name='Eric' WHERE id=?",@4];
        }
        // 提交事務(wù)
        [db commit];
        
    }];
    
    __block BOOL flag;
    // FMDB中畜号,也可以直接利用隊(duì)列進(jìn)行事務(wù)操作缴阎,隊(duì)列中的打開(kāi)、關(guān)閉简软、回滾事務(wù)等都已經(jīng)被封裝好了蛮拔。
    [self.dataBaseQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
        flag = [db executeUpdate:@"UPDATE testTable SET name='xiaoqiang' WHERE id=?",@2];
        [db executeUpdate:@"UPDATE testTable SET name='xiaoming' WHERE id=?",@3];
        
        if (flag) {
            NSLog(@"數(shù)據(jù)更新成功");
            *rollback = !flag;
        } else {
            NSLog(@"數(shù)據(jù)更新失敗");
            // 發(fā)現(xiàn)情況不對(duì)時(shí),主動(dòng)回滾用下面語(yǔ)句痹升。
            *rollback = YES;
            flag = NO;
            
            [db executeUpdate:@"UPDATE testTable SET name='Eric' WHERE id=?",@4];
        }
    }];
}
// 其他線程操作
- (void)multithreading {
    // 異步并發(fā)全局隊(duì)列
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self.dataBaseQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
            BOOL result = YES;
            for (int i = 500; i < 1000; i++) {
                result =  [db executeUpdate:@"insert into testTable (name) values(?)",[NSString stringWithFormat:@"name-%d",i]];
                if (!result) {
                    NSLog(@"break");
                    *rollback = YES;
                    break;
                }
            }
            
        }];
    });
}
10建炫、Magical Record

Magical Record借用了Ruby on Rails中的Active Record模式,使得你可以非常容易的添加疼蛾、查找肛跌、刪除數(shù)據(jù),降低了Core Data的使用門檻。

創(chuàng)建模型文件:
 創(chuàng)建一個(gè)名為Model的模型文件衍慎。 (File > New File… > Core Data > Data Model)
 點(diǎn)擊左下角的Add Entity转唉,更改Entity的名字為Person。
 為Entity添加三個(gè)Attribute:age(Integer16)稳捆、firstname(string)赠法、lastname(string)。
 點(diǎn)擊Editor > Create NSManagedObject Subclass… 創(chuàng)建實(shí)體分類乔夯。

使用Magical Record:
引入頭文件

#import "MagicalRecord.h" 或 #import <MagicalRecord/MagicalRecord.h>

如果在創(chuàng)建工程之初勾選了使用Core Data的選項(xiàng)砖织,系統(tǒng)會(huì)自動(dòng)在AppDelegate中生成大量的Core Data代碼。但是完可以使用一行代碼代替末荐,如下:

初始化
標(biāo)準(zhǔn)初始化

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    [MagicalRecord setupCoreDataStackWithStoreNamed:@"MyDatabase.sqlite"];
    // 或者
    /*
    + (void)setupCoreDataStack;
    + (void)setupAutoMigratingCoreDataStack;
    + (void)setupCoreDataStackWithInMemoryStore;
    + (void)setupCoreDataStackWithStoreNamed:(NSString *)storeName;
    + (void)setupCoreDataStackWithAutoMigratingSqliteStoreNamed:(NSString *)storeName;
    + (void)setupCoreDataStackWithStoreAtURL:(NSURL *)storeURL;
    + (void)setupCoreDataStackWithAutoMigratingSqliteStoreAtURL:(NSURL *)storeURL;
    */
    return YES;
}

每次調(diào)用都會(huì)實(shí)例化一塊 CoreData 棧侧纯,并且對(duì)該實(shí)例提供 getter 和 setter 方法。 MagicalRecord 將這些實(shí)例作為默認(rèn)使用的棧鞠评。

當(dāng)在DeBug模式下使用默認(rèn)SQLite 數(shù)據(jù)存儲(chǔ)時(shí)茂蚓,如果沒(méi)有創(chuàng)建新數(shù)據(jù)模型就更改了數(shù)據(jù)模型,MagicalRecord 將會(huì)自動(dòng)刪除舊的存儲(chǔ)并創(chuàng)建新的存儲(chǔ)剃幌。這將為你節(jié)省大量時(shí)間——每當(dāng)更改數(shù)據(jù)模型,不必再卸載、重裝應(yīng)用晾浴。但請(qǐng)確保發(fā)布的應(yīng)用的不是DeBug版本:否則在未告知用戶的情況下刪除應(yīng)用數(shù)據(jù)负乡,這可不是好事!

退出前,你應(yīng)該調(diào)用類方法+cleanUp:

- (void)applicationWillTerminate:(UIApplication *)application {
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    [MagicalRecord cleanUp];
}

這用于使用MagicalRecord后的整理工作:解除我們自定義的錯(cuò)誤處理器并把MagicalRecord創(chuàng)建的所有的Core Data 棧設(shè)為 nil脊凰。

開(kāi)啟iCloud 持久化存儲(chǔ)
 為了更好地使用蘋(píng)果的iCloud Core Data 同步機(jī)制抖棘,使用下面初始化方法中的 一種 來(lái)替換來(lái)替換前面列出的標(biāo)準(zhǔn)初始化方法:

     + (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID
     localStoreNamed:(NSString *)localStore;
     
     + (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID
     contentNameKey:(NSString *)contentNameKey
     localStoreNamed:(NSString *)localStoreName
     cloudStorePathComponent:(NSString *)pathSubcomponent;
     
     + (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID
     contentNameKey:(NSString *)contentNameKey
     localStoreNamed:(NSString *)localStoreName
     cloudStorePathComponent:(NSString *)pathSubcomponent
     completion:(void (^)(void))completion;
     
     + (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID
     localStoreAtURL:(NSURL *)storeURL;
     
     + (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID
     contentNameKey:(NSString *)contentNameKey
     localStoreAtURL:(NSURL *)storeURL
     cloudStorePathComponent:(NSString *)pathSubcomponent;
     
     + (void)setupCoreDataStackWithiCloudContainer:(NSString *)containerID
     contentNameKey:(NSString *)contentNameKey
     localStoreAtURL:(NSURL *)storeURL
     cloudStorePathComponent:(NSString *)pathSubcomponent
     completion:(void (^)(void))completion;

如果你需要同時(shí)管理多個(gè)iCloud 存儲(chǔ)內(nèi)容,推薦使用可以設(shè)置 contentNameKey 的方法狸涌。不能設(shè)置contentNameKey的方法則會(huì)根據(jù)應(yīng)用的 bundle identifier(CFBundleIdentifier)自動(dòng)生成 NSPersistentStoreUbiquitousContentNameKey作為標(biāo)示切省。

操作被管理的對(duì)象上下文
 對(duì)象上下文環(huán)境是你操作Core Data內(nèi)數(shù)據(jù)的基礎(chǔ),只有正確獲取到了上下文環(huán)境帕胆,才有可能進(jìn)行相關(guān)的讀寫(xiě)操作朝捆。換句話說(shuō),程序的任意位置,只要能正確獲取上下文懒豹,都能進(jìn)行Core Data的操作.這也是使用Core Data共享數(shù)據(jù)的基礎(chǔ)之一芙盘。相較于傳統(tǒng)的方式,各個(gè)頁(yè)面之間只需要與一個(gè)透明的上下文環(huán)境進(jìn)行交互脸秽,即可進(jìn)行頁(yè)面間數(shù)據(jù)的共享儒老。

創(chuàng)建新的對(duì)象上下文:

+ [NSManagedObjectContext MR_context]: 設(shè)置默認(rèn)的上下文為它的父級(jí)上下文.并發(fā)類型為 NSPrivateQueueConcurrencyType .
+ [NSManagedObjectContext MR_newMainQueueContext]: 并發(fā)類型為 ** NSMainQueueConcurrencyType**.
+ [NSManagedObjectContext MR_newPrivateQueueContext]: 并發(fā)類型為 NSPrivateQueueConcurrencyType .
+ [NSManagedObjectContext MR_contextWithParent:…]: 允許自定義父級(jí)上下文.并發(fā)類型為 NSPrivateQueueConcurrencyType .
+ [NSManagedObjectContext MR_contextWithStoreCoordinator:…]:允許自定義持久化存儲(chǔ)協(xié)調(diào)器.并發(fā)類型為 NSPrivateQueueConcurrencyType .

例:

NSManagedObjectContext *localContext = [NSManagedObjectContext MR_context];
// 在當(dāng)前上下文環(huán)境中創(chuàng)建一個(gè)新的 Person 對(duì)象.
Person *person  = [Person MR_createEntityInContext:localContext];
person.firstname = @"默";
person.lastname = @"六";
person.age = 24;
    
// 保存修改到當(dāng)前上下文中.
[localContext MR_saveToPersistentStoreAndWait];

默認(rèn)上下文:
 當(dāng)使用Core Data時(shí),你經(jīng)常使用的兩個(gè)類主要對(duì)象是:NSManagedObject和NSManagedObjectContext记餐。
 MagicalRecord 提供了一個(gè)簡(jiǎn)單類方法來(lái)獲取一個(gè)默認(rèn)的NSManagedObjectContext對(duì)象驮樊,這個(gè)對(duì)象在整個(gè)應(yīng)用全局可用。這個(gè)上下文對(duì)象,在主線程操作囚衔,對(duì)于簡(jiǎn)單的單線程應(yīng)用來(lái)說(shuō)非常強(qiáng)大挖腰。
獲取方法:

NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext];

這個(gè)上下文對(duì)象,在MagicalRecord的任何需要使用上下文對(duì)象方法中都可以使用佳魔,但是并不需要給這些方法顯示提供一個(gè)指定對(duì)象管理上下文對(duì)象參數(shù)曙聂。
 如果你想創(chuàng)建一個(gè)新的對(duì)象管理上下文對(duì)象,以用于非主線程鞠鲜,可使用下面的方法:

NSManagedObjectContext *myNewContext = [NSManagedObjectContext MR_context];

這將會(huì)創(chuàng)建一個(gè)新的對(duì)象管理上下文,和默認(rèn)的上下文對(duì)象有相同的對(duì)象模型和持久化存儲(chǔ);但是在另一個(gè)線程中使用時(shí)宁脊,是線程安全的,它自動(dòng)設(shè)置默認(rèn)上下文對(duì)象為父級(jí)上下文贤姆。
 如果你想要將你的myNewContext實(shí)例作為所有獲取請(qǐng)求默認(rèn)的上下文對(duì)象,使用下面的類方法:

[NSManagedObjectContext MR_setDefaultContext:myNewContext];

[注意]: 建議默認(rèn)的上下文對(duì)象在主線程使用并發(fā)類型為NSMainQueueConcurrencyType的對(duì)象管理上線文對(duì)象創(chuàng)建和設(shè)置榆苞。

在后臺(tái)線程中執(zhí)行任務(wù)
 MagicalRecord 提供了創(chuàng)建及使用后臺(tái)線程上下文的方法,按順序的在后臺(tái)保存霞捡。后臺(tái)保存操作為代碼塊的形式坐漏。

  • 用于更改實(shí)體的block將永遠(yuǎn)不會(huì)在主線程執(zhí)行。
  • 在你的block內(nèi)部提供一個(gè)單一的 NSManagedObjectContext 上下文對(duì)象碧信。

例如赊琳,如果我們有一個(gè)Person實(shí)體對(duì)象,并且我們需要設(shè)置它的firstName和lastName字段砰碴,下面的代碼展示了如何使用MagicalRecord來(lái)設(shè)置一個(gè)后臺(tái)保存的上下文對(duì)象:

    // 獲取上下文環(huán)境
    NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext];
    // 在當(dāng)前上下文環(huán)境中創(chuàng)建一個(gè)新的 Person 對(duì)象.
    Person *person  = [Person MR_createEntityInContext:defaultContext];
    person.firstname = @"firstname";
    person.lastname  = @"lastname";
    person.age       = 100; 
    // 保存修改到當(dāng)前上下文中. 
    [defaultContext MR_saveToPersistentStoreAndWait];
    
    [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){
        Person *localPerson = [person MR_inContext:localContext];
        localPerson.firstname = @"Yan";
        localPerson.lastname = @"Feng";
    }];

在這個(gè)方法中靠欢,指定的block給你提供了一個(gè)合適的上下文對(duì)象來(lái)執(zhí)行你的操作犬绒,你不需要擔(dān)心這個(gè)上下文對(duì)象的初始化來(lái)告訴默認(rèn)上線文它準(zhǔn)備好了适袜,并且應(yīng)當(dāng)更新,因?yàn)樽兏窃诹硪粋€(gè)線程執(zhí)行堪侯。
 為了在保存block完成時(shí)執(zhí)行某個(gè)操作,你可以使用 completion block:

    // 獲取上下文環(huán)境
    NSManagedObjectContext *defaultContext = [NSManagedObjectContext MR_defaultContext];
    // 在當(dāng)前上下文環(huán)境中創(chuàng)建一個(gè)新的 Person 對(duì)象猖辫。
    Person *person  = [Person MR_createEntityInContext:defaultContext];
    person.firstname = @"firstname";
    person.lastname  = @"lastname";
    person.age       = 100; 
    // 保存修改到當(dāng)前上下文中酥泞。
    [defaultContext MR_saveToPersistentStoreAndWait];
    
    [MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){
        Person *localPerson = [person MR_inContext:localContext];
        localPerson.firstname = @"Yan";
        localPerson.lastname = @"Feng";
    }  completion:^(BOOL success, NSError *error) {
        NSArray * persons = [Person MR_findAll];
        [persons enumerateObjectsUsingBlock:^(Person * obj, NSUInteger idx, BOOL * _Nonnull stop) {
            NSLog(@"firstname: %@, lastname: %@\n", obj.firstname, obj.lastname);
        }];
    }];
    // 這個(gè)完成的block,在主線程(隊(duì)列)中調(diào)用啃憎,所以可以在此block里安全觸發(fā)UI更新芝囤。

創(chuàng)建實(shí)體對(duì)象
 為了創(chuàng)建并插入一個(gè)新的實(shí)體實(shí)例到默認(rèn)上下文對(duì)象中,你可以使用:

Person *myPerson = [Person MR_createEntity];

創(chuàng)建實(shí)體實(shí)例荧飞,并插入到指定的上下文中:

Person *myPerson = [Person MR_createEntityInContext:otherContext];

刪除實(shí)體對(duì)象

刪除默認(rèn)上下文中的實(shí)體對(duì)象:
[myPerson MR_deleteEntity];
刪除指定上下文中的實(shí)體對(duì)象:
[myPerson MR_deleteEntityInContext:otherContext];
刪除默認(rèn)上下文中的所有實(shí)體:
[Person MR_truncateAll];
刪除指定上下文中的所有實(shí)體:
[Person MR_truncateAllInContext:otherContext];

獲取實(shí)體對(duì)象
基礎(chǔ)查找

MagicalRecord中的大多數(shù)方法返回NSArray結(jié)果.

舉個(gè)例子凡人,如果你有一個(gè)名為 Person 的實(shí)體,和實(shí)體 Department 關(guān)聯(lián),你可以從持久化存儲(chǔ)中獲取所有的 Person 實(shí)體:
NSArray *people = [Person MR_findAll];

可以指定以某個(gè)屬性排序:
NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName" ascending:YES];

可以使用多個(gè)屬性進(jìn)行排序:
NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName,FirstName" ascending:YES];

當(dāng)使用多個(gè)屬性進(jìn)行排序時(shí)叹阔,可以單獨(dú)指定升序或降序.
// 按照屬性LastName降序挠轴、FirstName升序排序
NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName:NO,FirstName" ascending:YES]; 
// 或者 NSArray *peopleSorted = [Person MR_findAllSortedBy:@"LastName,FirstName:YES" ascending:NO];

如果你有辦法通過(guò)某種方式從數(shù)據(jù)庫(kù)中獲取唯一的一個(gè)對(duì)象(比如,給對(duì)象一個(gè)特定的唯一標(biāo)記),你可以使用下面方法獲取某個(gè)實(shí)體對(duì)象:
Person *person = [Person MR_findFirstByAttribute:@"FirstName" withValue:@"名字"];

高級(jí)查找
使用 謂詞 查詢特定實(shí)體:

NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", @[dept1, dept2]];
NSArray *people = [Person MR_findAllWithPredicate:peopleFilter];

使用 謂詞 查詢特定實(shí)體:
使用謂詞獲取對(duì)應(yīng)查詢條件的 NSFetchRequest 對(duì)象

NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", departments];
NSFetchRequest *people = [Person MR_requestAllWithPredicate:peopleFilter];
每執(zhí)行一次,就創(chuàng)建一個(gè)這些查詢條件對(duì)應(yīng)的NSFetchRequest和NSSortDescriptor.

自定義查詢請(qǐng)求

NSPredicate *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];

查詢實(shí)體數(shù)目

你還可以獲取指定類型的實(shí)體個(gè)數(shù):
NSNumber *count = [Person MR_numberOfEntities];

使用謂詞或其它過(guò)濾器查詢實(shí)體數(shù)目:
NSNumber *count = [Person MR_numberOfEntitiesWithPredicate:...];

以下方法返回類型則是NSUInteger:
+ (NSUInteger) MR_countOfEntities;
+ (NSUInteger) MR_countOfEntitiesWithContext:(NSManagedObjectContext *)context;
+ (NSUInteger) MR_countOfEntitiesWithPredicate:(NSPredicate *)searchFilter;
+ (NSUInteger) MR_countOfEntitiesWithPredicate:(NSPredicate *)searchFilter
                                     inContext:(NSManagedObjectContext *)context;

合計(jì)操作

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"];

在指定上下文中查找實(shí)體

所有的 find,fetch耳幢,request 方法都可以通過(guò) inContext: 參數(shù)指定查詢的上下文:

NSArray *peopleFromAnotherContext = [Person MR_findAllInContext:someOtherContext];

Person *personFromContext = [Person MR_findFirstByAttribute:@"lastName" withValue:@"名字" inContext:someOtherContext];

NSUInteger count = [Person MR_numberOfEntitiesWithContext:someOtherContext];

保存實(shí)體
 大多數(shù)情況下岸晦,當(dāng)數(shù)據(jù)發(fā)生變化的時(shí)候就執(zhí)行保存操作欧啤。有一些應(yīng)用只是在應(yīng)用關(guān)閉的時(shí)候才保存,但這樣增加了丟失數(shù)據(jù)的風(fēng)險(xiǎn)启上。當(dāng)應(yīng)用崩潰的時(shí)候就會(huì)造成數(shù)據(jù)丟失邢隧,用戶會(huì)丟失他們的數(shù)據(jù)。應(yīng)當(dāng)避免這種情況的發(fā)生冈在。
 如果保存操作花費(fèi)的時(shí)間太長(zhǎng)倒慧,你可以采取以下措施:

1.在后臺(tái)線程中執(zhí)行保存操作:
 MagicalRecord 為更改、保存實(shí)體提供了簡(jiǎn)潔的API包券。

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
    // Do your work to be saved here, against the `localContext` instance
    // 在此塊中的所有操作均在后臺(tái)執(zhí)行
} completion:^(BOOL success, NSError *error) {
    [application endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}];

將任務(wù)拆分成小任務(wù)保存:
 像一次性導(dǎo)入大量數(shù)據(jù)的任務(wù)纫谅,你需要將數(shù)據(jù)拆分成小塊來(lái)操作。一次處理的數(shù)據(jù)大小并沒(méi)有統(tǒng)一標(biāo)準(zhǔn)溅固,你需要使用類似 Apples’ Instruments 的工具來(lái)測(cè)試調(diào)整付秕,獲取最佳大小。

處理長(zhǎng)時(shí)儲(chǔ)存
ios平臺(tái)
 當(dāng)iOS平臺(tái)app退出時(shí)侍郭,app會(huì)獲得短暫時(shí)間向硬盤中保存整理數(shù)據(jù)询吴。如果你的應(yīng)用存儲(chǔ)時(shí)間較長(zhǎng),最好在應(yīng)用完全終止前亮元,請(qǐng)求延長(zhǎng)后臺(tái)執(zhí)行時(shí)間猛计。

UIApplication *application = [UIApplication sharedApplication];
 
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
    [application endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}];
 
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
    // 此處執(zhí)行保存操作
 
} completion:^(BOOL success, NSError *error) {
    [application endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}];

請(qǐng)確保認(rèn)真閱讀beginBackgroundTaskWithExpirationHandler ,因?yàn)椴贿m當(dāng)或不必要的延長(zhǎng)應(yīng)用生命周期爆捞,申請(qǐng)上架的時(shí)候可能會(huì)被 App Store 拒絕有滑。

OS X 平臺(tái)
 OS X Mavericks (10.9) 及更高版本中,App Nap 可以讓你 app 看起來(lái)像關(guān)閉了嵌削,但仍在后臺(tái)運(yùn)行。如果有長(zhǎng)時(shí)間的保存操作望艺,最好的方式是禁用自動(dòng)終止和突然終止( automatic and sudden termination):

NSProcessInfo *processInfo = [NSProcessInfo processInfo];
 
[processInfo disableSuddenTermination];
[processInfo disableAutomaticTermination:@"Application is currently saving to persistent store"];
 
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
    // Do your work to be saved here
 
} completion:^(BOOL success, NSError *error) {
    [processInfo enableSuddenTermination];
    [processInfo enableAutomaticTermination:@"Application has finished saving to the persistent store"];
}];

導(dǎo)入對(duì)象
導(dǎo)入之前你應(yīng)當(dāng)充分了解導(dǎo)入數(shù)據(jù)對(duì)象的結(jié)構(gòu)苛秕,同時(shí)構(gòu)建與之對(duì)應(yīng)的實(shí)體。完成這些之后找默,再進(jìn)行數(shù)據(jù)導(dǎo)入艇劫。導(dǎo)入的方式有以下幾種:

依據(jù)數(shù)據(jù)對(duì)象自動(dòng)創(chuàng)建新實(shí)例:

NSDictionary *contactInfo = // 此處解析 JSON 或其他數(shù)據(jù)源
 
Person *importedPerson = [Person MR_importFromObject:contactInfo];

兩步方法:

NSDictionary *contactInfo = // 此處解析 JSON 或其他數(shù)據(jù)源
Person *person = [Person MR_createEntity]; // 此處不一定是新建實(shí)體,也可使用已有實(shí)體
[person MR_importValuesForKeysWithObject:contactInfo];

兩步方法中的實(shí)體可以是新建的惩激,也可以是已存在的店煞,兩步方法可以幫你更新已存在的實(shí)體。

+MR_importFromObject:根據(jù)已配置的查詢值(lookup value)查找相應(yīng)對(duì)象(即上文中的relatedByAttribute 和 attributeNameID)风钻。它遵循Cocoa導(dǎo)入key-value 的范例顷蟀,保證安全導(dǎo)入數(shù)據(jù)。

+MR_importFromObject: 是對(duì)實(shí)例方法-MR_importValuesForKeysWithObject:和創(chuàng)建對(duì)象方法的封裝骡技,返回值為賦值的新對(duì)象鸣个。

以上兩個(gè)方法均為同步執(zhí)行方法羞反。導(dǎo)入時(shí)間長(zhǎng)短不一,很可能影響交互體驗(yàn)囤萤,若將所有數(shù)據(jù)導(dǎo)入工作都放在后臺(tái)執(zhí)行昼窗,就不會(huì)影響軟件交互了。像前面提到的涛舍,MagicalRecord 提供了很多好用的后臺(tái)線程 API :

[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *)localContext {
  Person *importedPerson = [Person MR_importFromObject:personRecord inContext:localContext]; //導(dǎo)入操作
}];

導(dǎo)入數(shù)組
使用 array 對(duì)象來(lái)保存單一類型數(shù)據(jù)很常見(jiàn)澄惊,可以使用 +MR_importFromArray: 方法來(lái)處理:

NSArray *arrayOfPeopleData = /// 解析JSON的結(jié)果
NSArray *people = [Person MR_importFromArray:arrayOfPeopleData];

此方法與+MR_importFromObject:方法類似,也是同步執(zhí)行方法富雅,所以你想要后臺(tái)執(zhí)行掸驱,就得使用前面提到的方法,在后臺(tái)塊里執(zhí)行吹榴。

如果你導(dǎo)入的數(shù)據(jù)與Core Data model 完全匹配亭敢,那么上面的方法已經(jīng)能幫你完成導(dǎo)入工作了。但大多數(shù)情況下并不理想图筹,兩者之間稍有差別, MagicalRecord 的一些方法處理導(dǎo)入數(shù)據(jù)和Core Data model之間的不同帅刀。

實(shí)踐:

錯(cuò)誤數(shù)據(jù)處理

API 方法經(jīng)常會(huì)返回格式錯(cuò)誤或內(nèi)容錯(cuò)誤的數(shù)據(jù)。最好的處理方法是通過(guò)實(shí)體類的分類方法處理远剩,有三個(gè)方法可供選擇:

Method Purpose
- (BOOL) shouldImport; 在導(dǎo)入數(shù)據(jù)前調(diào)用此方法扣溺。如果返回為 NO,則取消向?qū)嶓w導(dǎo)入數(shù)據(jù)瓜晤。
- (void) willImport:(id)data; 緊鄰數(shù)據(jù)導(dǎo)入之前調(diào)用锥余。
- (void) didImport:(id)data; 緊鄰數(shù)據(jù)導(dǎo)入之后調(diào)用。

一般來(lái)說(shuō)痢掠,如果你嘗試導(dǎo)入所有數(shù)據(jù)時(shí)驱犹,發(fā)現(xiàn)一些損壞數(shù)據(jù),你可能想修復(fù)它們足画。

常見(jiàn)的情景是在導(dǎo)入JSON數(shù)據(jù)時(shí)雄驹,常常將數(shù)字字符串被錯(cuò)誤的解析為數(shù)字。如果你想確保它們以字符串的類型導(dǎo)入淹辞,可以這么做:

@interface MyGreatEntity
 
@property(readwrite, nonatomic, copy) NSString *identifier;
 
@end
 
@implementation MyGreatEntity
 
@dynamic identifier;
 
- (void)didImport:(id)data
{
  if (NO == [data isKindOfClass:[NSDictionary class]]) {
    return;
  }
 
  NSDictionary *dataDictionary = (NSDictionary *)data;
 
  id identifierValue = dataDictionary[@"my_identifier"];
 
  if ([identifierValue isKindOfClass:[NSNumber class]]) {
    NSNumber *numberValue = (NSNumber *)identifierValue;
 
    self.identifier = [numberValue stringValue];
  }
}
@end

更新數(shù)據(jù)時(shí)刪除本地記錄

在后續(xù)的導(dǎo)入操作中医舆,有時(shí)不僅需要更新記錄,同時(shí)要?jiǎng)h除本地存在象缀、遠(yuǎn)程數(shù)據(jù)庫(kù)中不存在的數(shù)據(jù)蔬将。此時(shí)需要先根據(jù) relatedByAttribute來(lái)判斷哪些記錄不在遠(yuǎn)程數(shù)據(jù)庫(kù)中,然后將其刪除央星。

NSArray *arrayOfPeopleData = /// JSON 解析結(jié)果
NSArray *people = [Person MR_importFromArray:arrayOfPeopleData];
NSArray *idList = [arrayOfPeopleData valueForKey:@"id"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(id IN %@)", idList];
[Person MR_deleteAllMatchingPredicate:predicate];

如果你想在更新操作中確保相關(guān)記錄已被刪除霞怀,你可以使用與上面代碼類似的邏輯,并在Person 的 willImport:方法中實(shí)現(xiàn):

@implementation Person
- (void)willImport:(id)data {
    NSArray *idList = [data[@"posts"] valueForKey:@"id"];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(id IN %@) AND person.id == %@", idList, self.id];
    [Post MR_deleteAllMatchingPredicate:predicate];
}

[注]:如不想使用MR_前綴等曼,只需要在*-Prefix.pch文件中添加一句#define MR_SHORTHAND即可里烦,注意這句要在#import “MagicalRecord.h”之前凿蒜。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市胁黑,隨后出現(xiàn)的幾起案子废封,更是在濱河造成了極大的恐慌,老刑警劉巖丧蘸,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漂洋,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡力喷,警方通過(guò)查閱死者的電腦和手機(jī)刽漂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)弟孟,“玉大人贝咙,你說(shuō)我怎么就攤上這事》髂迹” “怎么了庭猩?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)陈症。 經(jīng)常有香客問(wèn)我蔼水,道長(zhǎng),這世上最難降的妖魔是什么录肯? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任趴腋,我火速辦了婚禮,結(jié)果婚禮上论咏,老公的妹妹穿的比我還像新娘优炬。我一直安慰自己,他們只是感情好厅贪,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布穿剖。 她就那樣靜靜地躺著,像睡著了一般卦溢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上秀又,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天单寂,我揣著相機(jī)與錄音,去河邊找鬼吐辙。 笑死宣决,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的昏苏。 我是一名探鬼主播尊沸,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼威沫,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了洼专?” 一聲冷哼從身側(cè)響起棒掠,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎屁商,沒(méi)想到半個(gè)月后烟很,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蜡镶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年雾袱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片官还。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡芹橡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出望伦,到底是詐尸還是另有隱情林说,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布屡谐,位于F島的核電站述么,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏愕掏。R本人自食惡果不足惜度秘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望饵撑。 院中可真熱鬧剑梳,春花似錦、人聲如沸滑潘。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)语卤。三九已至追逮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間粹舵,已是汗流浹背钮孵。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留眼滤,地道東北人巴席。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像诅需,于是被迫代替她去往敵國(guó)和親漾唉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子荧库,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • iOS數(shù)據(jù)持久化方式 文件 歸檔(NSKeyedArchiver) 屬性列表(NSUserDefaults) 數(shù)據(jù)...
    向陽(yáng)的向日葵花閱讀 890評(píng)論 0 12
  • 1.CoreData 1.1 CoreData概述 1)Core data 是數(shù)據(jù)持久存儲(chǔ)的最佳方式 2)Core...
    微春風(fēng)閱讀 3,798評(píng)論 0 10
  • 導(dǎo)語(yǔ) 數(shù)據(jù)持久化是一種非易失性存儲(chǔ)技術(shù)料睛,在重啟動(dòng)計(jì)算機(jī)或設(shè)備后也不會(huì)丟失數(shù)據(jù)丐箩,是將內(nèi)存中的數(shù)據(jù)模型轉(zhuǎn)換為存儲(chǔ)模型,...
    樹(shù)袋熊老公閱讀 1,977評(píng)論 0 10
  • 所謂的持久化恤煞,就是將數(shù)據(jù)保存到硬盤中屎勘,使得在應(yīng)用程序或機(jī)器重啟后可以繼續(xù)訪問(wèn)之前保存的數(shù)據(jù)。在iOS開(kāi)發(fā)中居扒,有很多...
    沙漠騎士閱讀 346評(píng)論 0 1
  • iOS開(kāi)發(fā)-數(shù)據(jù)持久化 原文鏈接 Sindri的小巢 在程序開(kāi)發(fā)中概漱,數(shù)據(jù)層永遠(yuǎn)是程序的核心結(jié)構(gòu)之一。我們將現(xiàn)實(shí)事物...
    人生路02閱讀 753評(píng)論 1 4