數(shù)據(jù)持久化的相關(guān)知識(shí)
四種將數(shù)據(jù)持久化存儲(chǔ)到iOS文件系統(tǒng)的機(jī)制:
- plist
- 對(duì)象歸檔
- iOS的嵌入式關(guān)系數(shù)據(jù)庫(kù)SQLite3
- Core Data
每個(gè)應(yīng)用都有自己的/Documents文件夾舍杜,且僅能讀寫(xiě)各自的/Documents目錄中的內(nèi)容咳促。
iOS應(yīng)用的3個(gè)支持文件夾:
- Documents:應(yīng)用將數(shù)據(jù)存儲(chǔ)在Documents中,但基于NSUserDefaults的首選項(xiàng)設(shè)置除外盆顾。
- Library: 基于NSUserDefaults的首選項(xiàng)設(shè)置存儲(chǔ)在Library/Preferences文件夾中
- tmp: tmp目錄供應(yīng)用存儲(chǔ)臨時(shí)文件,當(dāng)iOS設(shè)備執(zhí)行和iTunes的同步時(shí)畏梆,不會(huì)備份其中的tmp文件您宪,但在不需要這些文件時(shí)奈懒,應(yīng)用要負(fù)責(zé)刪除tmp中的文件,以免占用文件系統(tǒng)空間宪巨。
獲取Documents目錄
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = paths[0];
常量NSDocumentDirectory表明我們正在尋找Documents目錄的路徑磷杏。第二個(gè)常量 NSUserDomainMask表明我們希望將搜索限制在應(yīng)用的沙盒里。雖然返回的是一個(gè)匹配路徑的數(shù)組捏卓,但我們知道數(shù)組中位于索引0處的一定是Documents目錄极祸,為什么呢?我們知道每個(gè)應(yīng)用只有一個(gè)Documents目錄怠晴,因此只有一個(gè)目錄符合指定的條件遥金。
可以在剛剛檢索到的路徑的結(jié)尾附加另一個(gè)字符來(lái)創(chuàng)建文件名。為此要使用專(zhuān)為該目的設(shè)計(jì)的NSString方法蒜田,即stringByAppendingPathComponent:方法稿械,如下所示:
NSString *fileName = [documentsDirectory stringByAppendingPathComponent:@"theFile.txt"];
完成此調(diào)用之后,fileName就是指向應(yīng)用的Documents目錄中theFile.txt文件的完整路徑冲粤,然后我們就可以使用fileName來(lái)創(chuàng)建美莫、讀取和寫(xiě)入文件了。
獲取tmp目錄
獲取對(duì)應(yīng)用臨時(shí)目錄的引用比獲取對(duì) Documents目錄的引用容易梯捕。名為NSTemporaryDirectory()的Foundation函數(shù)將返回一個(gè)字符串厢呵,該字符串包含到應(yīng)用臨時(shí)目錄的完整路徑。若要?jiǎng)?chuàng)建一個(gè)將會(huì)存儲(chǔ)在臨時(shí)目錄中的文件科阎,首先要找到該臨時(shí)目錄:
NSString *tmpPath = NSTemporaryDirectory();
然后述吸,在路徑的結(jié)尾附上文件名就可以創(chuàng)建指向該目錄下文件的路徑,比如:
NSString *tempFile = [tmpPath stringByAppendingPathComponent:@"theFile.txt"];
文件保存方案
單文件持久化
將數(shù)據(jù)保存在一個(gè)文件中是最簡(jiǎn)單的方法锣笨,且對(duì)于許多應(yīng)用蝌矛,這也是完全可以接受的方法。首先错英,創(chuàng)建一個(gè)根對(duì)象入撒,通常是NSArray或NSDictionary(使用歸檔文件的情況下根對(duì)象可以基于某個(gè)自定義類(lèi))。接下來(lái)椭岩,使用所有需要保存的程序數(shù)據(jù)填充根對(duì)象茅逮。真正保存的時(shí)候,代碼會(huì)將該根對(duì)象的全部?jī)?nèi)容重新寫(xiě)入單個(gè)文件判哥。應(yīng)用在啟動(dòng)時(shí)會(huì)將該文件的全部?jī)?nèi)容讀入內(nèi)存献雅,并在退出時(shí)注銷(xiāo)。
使用單文件的缺點(diǎn)是必須將全部數(shù)據(jù)加載到內(nèi)存中塌计,并且不管更改多少也必須將所有數(shù)據(jù)全部重新寫(xiě)入文件系統(tǒng)挺身。如果應(yīng)用管理的數(shù)據(jù)不超過(guò)幾兆字節(jié),此方法可能非常好锌仅,且簡(jiǎn)單章钾。
多文件持久化
使用多個(gè)文件是另一種實(shí)現(xiàn)持久化的方法墙贱。太復(fù)雜!
plist
使用屬性列表非常方便贱傀,因?yàn)榭梢允褂肵code或Property List Editor應(yīng)用手動(dòng)編輯他們惨撇,并且只要字典或數(shù)組包含特定可序列化對(duì)象,就可以將NSDictionary和NSArray 實(shí)例寫(xiě)入屬性列表或者從屬性列表創(chuàng)建它們府寒。
屬性列表序列化
序列化對(duì)象(serialized object)是指可以被轉(zhuǎn)換為字節(jié)流以便于存儲(chǔ)到文件中或通過(guò)網(wǎng)絡(luò)進(jìn)行傳輸?shù)膶?duì)象魁衙。雖然說(shuō)任何對(duì)象都可以被序列化,但只有某些特定對(duì)象才能被放置到某個(gè)集合類(lèi)中(字典椰棘,數(shù)組)纺棺,然后才使用該集合類(lèi)的writeToFile:atomically:方法或writeToURL:atomically:方法將它們存儲(chǔ)到屬性列表中⌒澳可以按照該方法序列化下面的Objective-C類(lèi):NSArray,dictionary,data,string及它們的可變子類(lèi),NSNumber,NSDate
這里的atomically參數(shù)讓該方法將數(shù)據(jù)寫(xiě)入輔助文件,而不是寫(xiě)入指定位置茅撞。成功寫(xiě)入該文件之后帆卓,輔助文件將被復(fù)制到第一個(gè)參數(shù)指定的位置。這是更安全的做法米丘,因?yàn)槿绻麘?yīng)用在保存期間崩潰剑令,則現(xiàn)有文件不會(huì)被破壞。
練習(xí)使用
創(chuàng)建一個(gè)outlet集合:
@property (strong, nonatomic) IBOutletCollection(UITextField) NSArray *lineFields;
在storyboard中拄查,按住control從頂部的view controller圖標(biāo)拖到要對(duì)應(yīng)的每個(gè)集合成員上吁津,并選擇集合名字。
用于確定文件路徑:
- (NSString *)dataFilePath{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = paths[0];
return [documentsDirectory stringByAppendingPathComponent:@"data.plist"];
}
在viewDidLoad中:
- (void)viewDidLoad {
[super viewDidLoad];
NSString *filePath = [self dataFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSArray *array = [[NSArray alloc] initWithContentsOfFile:filePath];
for (int i = 0; i < 4; i++) {
UITextField *theField = self.lineFields[i];
theField.text = array[i];
}
}
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:app];
// Do any additional setup after loading the view, typically from a nib.
}
檢查數(shù)據(jù)文件是否存在堕扶,如果不存在就不加載它了碍脏,如果存在,就用該文件內(nèi)容實(shí)例化數(shù)組稍算,然后將數(shù)組中的對(duì)象復(fù)制到四個(gè)文本框中典尾。由于數(shù)組是按順序排列的列表,因此只要根據(jù)保存順序(之后會(huì)有)來(lái)復(fù)制數(shù)組糊探,就一定能確保相應(yīng)地字段獲得正確的值钾埂。
-
從屬性列表中加載數(shù)據(jù)后,我們獲得了對(duì)應(yīng)用實(shí)例的引用科平,并使用該引用訂閱UIApplicationWillResignActiveNotification通知褥紫,這是由UIApplication定義的字符串常量,最后一個(gè)參數(shù)app是sender.
- (void)applicationWillResignActive:(NSNotification *)notification{ NSString *filePath = [self dataFilePath]; //對(duì)outlet集合中每個(gè)元素提取相同的屬性(KVC) NSArray *array = [self.lineFields valueForKey:@"text"]; [array writeToFile:filePath atomically:YES]; }
最后一個(gè)方法是applicationWillResignActive:瞪慧。注意它接受一個(gè)指向NSNotification的指針作為參數(shù)髓考。它是一個(gè)通知方法,所有通知都接受一個(gè)NSNotification實(shí)例作為參數(shù)汞贸。應(yīng)用應(yīng)該在終止運(yùn)行或者進(jìn)入后臺(tái)之前保存數(shù)據(jù)绳军,所以我們需要使用名為UIApplicationWillResignActiveNotification通知印机。這樣,只要這個(gè)應(yīng)用不再是當(dāng)前正在與用戶(hù)進(jìn)行交互的應(yīng)用门驾,就會(huì)發(fā)布通知射赛,包括用戶(hù)按下Home鍵,來(lái)了個(gè)電話奶是。
通知方法通過(guò)調(diào)用lineFiedls數(shù)組中每個(gè)文本框的text方法構(gòu)建一個(gè)字符串?dāng)?shù)組楣责。我們利用了一個(gè)便捷的方法,沒(méi)有迭代數(shù)組中的文本框聂沙,而是用了valueForKey:方法秆麸,并傳遞@"text"作為參數(shù)。NSArray類(lèi)的valueForKey:方法為我們實(shí)現(xiàn)了迭代獲取實(shí)例變量的text值及汉,返回包含這些值的數(shù)組沮趣。然后我們將該數(shù)組的內(nèi)容寫(xiě)入一個(gè)屬性列表文件中。
對(duì)模型對(duì)象進(jìn)行歸檔
在Cocoa世界中坷随,歸檔(archiving)是指另一種形式的序列化房铭,但它是任何對(duì)象都可以實(shí)現(xiàn)的更常規(guī)的類(lèi)型。專(zhuān)門(mén)編寫(xiě)用于保存數(shù)據(jù)的任何模型對(duì)象都應(yīng)該支持歸檔温眉。使用對(duì)模型對(duì)象進(jìn)行歸檔的技術(shù)可以輕松將復(fù)雜的對(duì)象寫(xiě)入文件缸匪,然后再?gòu)闹凶x取它們。
只要在類(lèi)中實(shí)現(xiàn)的每個(gè)屬性都是標(biāo)量(int.float)或是遵循NSCoding協(xié)議的某個(gè)類(lèi)的實(shí)例类溢,你就可以對(duì)整個(gè)對(duì)象進(jìn)行完全的歸檔凌蔬。由于大多數(shù)支持存儲(chǔ)數(shù)據(jù)的Foundation和Cocoa Touch類(lèi)都遵循NSCoding協(xié)議(除UIImage)。
還有一個(gè)協(xié)議應(yīng)該和NSCoding協(xié)議一起實(shí)現(xiàn)闯冷,那就是NSCopying協(xié)議砂心。后者允許復(fù)制對(duì)象,這使你在使用數(shù)據(jù)模型對(duì)象時(shí)具備了較大的靈活性窃躲。
遵循NSCoding協(xié)議
NSCoding協(xié)議聲明了兩個(gè)方法计贰,這兩個(gè)方法都是必需的。一個(gè)方法將對(duì)象編碼到歸檔中蒂窒,另一個(gè)方法對(duì)歸檔解碼來(lái)創(chuàng)建一個(gè)新對(duì)象躁倒。這兩個(gè)方法都傳遞一個(gè)NSCoder實(shí)例,使用方法和NSUserDefaults相似洒琢。也可以使用KVC對(duì)對(duì)象和原生數(shù)據(jù)類(lèi)型(int,float)進(jìn)行編碼和解碼秧秉。
對(duì)某個(gè)對(duì)象進(jìn)行編碼的方法可能看起來(lái)如下:
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:foo forKey:kFooKey];
[aCoder encodeObject:bar forKey:kBarKey];
[aCoder encodeInt:someInt forKey:kSomeIntKey];
[aCoder encodeFloat:someFloat forKey:kSomeFloat];
}
若要我們?cè)陧?xiàng)目中支持歸檔,必須使用正確的編碼方法將所有實(shí)例變量編碼成encoder衰抑。如果要子類(lèi)化某個(gè)也遵循NSCoding的類(lèi)象迎,還需要確保對(duì)超類(lèi)調(diào)用encodeWithCoder:方法,你的方法將如下所示:
- (void)encodeWithCoder:(NSCoder *)aCoder{
[super encodeWithCoder:aCoder];
[aCoder encodeObject:foo forKey:kFooKey];
[aCoder encodeObject:bar forKey:kBarKey];
[aCoder encodeInt:someInt forKey:kSomeIntKey];
[aCoder encodeFloat:someFloat forKey:kSomeFloat];
}
我們還需要實(shí)現(xiàn)一個(gè)通過(guò)NSCoder解碼的對(duì)象初始化方法,恢復(fù)我們之前歸檔的對(duì)象砾淌。實(shí)現(xiàn)initWithCoder:方法比實(shí)現(xiàn)encodeWithCoder:方法稍微復(fù)雜一些啦撮。如果直接對(duì)NSObject進(jìn)行子類(lèi)化,或者對(duì)某些不遵循NSCoding的其他類(lèi)進(jìn)行子類(lèi)化汪厨,則你的方法看起來(lái)如下
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
foo = [aDecoder decodeObjectForKey:kFooKey];
bar = [aDecoder decodeObjectForKey:kBarKey];
someInt = [aDecoder decodeObjectForKey:kSomeIntKey];
someFloat = [aDecoder decodeObjectForKey:kAgeKey];
}
return self;
}
該方法使用[super init]初始化對(duì)象實(shí)例赃春,如果初始化成功,則它通過(guò)解碼NSCoder的實(shí)例中傳遞的值來(lái)設(shè)置其屬性劫乱。當(dāng)為某個(gè)具有超類(lèi)且遵循NSCoding的類(lèi)實(shí)現(xiàn)NSCoding時(shí)织中,initWithCoder:方法應(yīng)稍有不同。它不再對(duì)super調(diào)用init衷戈,而是調(diào)用initWithCoder,像這樣:
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super initWithCoder:aDecoder]) {
foo = [aDecoder decodeObjectForKey:kFooKey];
bar = [aDecoder decodeObjectForKey:kBarKey];
someInt = [aDecoder decodeObjectForKey:kSomeIntKey];
someFloat = [aDecoder decodeObjectForKey:kAgeKey];
}
return self;
}
只要實(shí)現(xiàn)這兩個(gè)方法狭吼,就可以對(duì)所有對(duì)象的屬性進(jìn)行編碼和解碼,然后便可以對(duì)對(duì)象進(jìn)行歸檔殖妇,并且可以將其寫(xiě)入歸檔或者從歸檔中讀取它們刁笙。
NSCopying協(xié)議
如前所述,遵循NSCopying對(duì)于任何數(shù)據(jù)模型對(duì)象來(lái)說(shuō)都是非常好的事情谦趣。NSCopying有一個(gè)copyWithZone:方法采盒,可用來(lái)復(fù)制對(duì)象。實(shí)現(xiàn)NSCopying和實(shí)現(xiàn)initWithCoder:非常相似蔚润,只需創(chuàng)建一個(gè)同一類(lèi)的新實(shí)例,然后將該新實(shí)例的所有屬性都設(shè)置為與該對(duì)象屬性相同的值尺栖。此處的copyWithZone:方法的內(nèi)容類(lèi)似下:
- (instancetype)copyWithZone:(NSZone *)zone{
MyClass *copy = [[[self class] allocWithZone:zone] init];
copy.foo = [self.foo copyWithZone:zone];
...
return copy;
}
對(duì)數(shù)據(jù)對(duì)象進(jìn)行歸檔和取消歸檔
從遵循NSCoding的一個(gè)或多個(gè)對(duì)象創(chuàng)建歸檔相對(duì)比較容易嫡纠。首先創(chuàng)建一個(gè)NSMutableData實(shí)例,用于包含編碼的數(shù)據(jù)延赌。然后創(chuàng)建一個(gè)NSKeydArchiver實(shí)例除盏,用于將對(duì)象歸檔到此NSMutableData實(shí)例中:
NSMutableData *data = [NSMutableData new];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
創(chuàng)建這兩個(gè)實(shí)例后,我們使用KVC來(lái)對(duì)希望包含在歸檔中的所有對(duì)象進(jìn)行歸檔挫以,像這樣:
[archiver encodeObject:myObject forKey:@"keyValueString"];
對(duì)所有要包含的對(duì)象進(jìn)行編碼之后者蠕,我們只需告知?dú)w檔程序已經(jīng)完成了這些操作。將NSMutableData實(shí)例寫(xiě)入文件系統(tǒng):
[archiver finishEncoding];
BOOL success = [data writeToFile:@"/../.." atomically:YES];
寫(xiě)入文件時(shí)出現(xiàn)錯(cuò)誤會(huì)將success設(shè)置為NO.如果success為YES掐松,則數(shù)據(jù)已成功寫(xiě)入指定文件踱侣。從該歸檔創(chuàng)建的任何對(duì)象都將是過(guò)去寫(xiě)入該文件的對(duì)象的精確副本。
從歸檔重組對(duì)象的步驟類(lèi)似大磺。從歸檔文件創(chuàng)建一個(gè)NSData實(shí)例抡句,并創(chuàng)建一個(gè)NSKeyedUnarchiver以對(duì)數(shù)據(jù)進(jìn)行解碼:
NSData *data = [[NSData alloc] initWithContentsOfFile:@"/.../.."];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
然后,使用之前對(duì)對(duì)象進(jìn)行歸檔的同一個(gè)鍵從解壓程序中讀取對(duì)象杠愧。
self.object = [unarchiver decodeObjectForKey:@"key"];
最后待榔,告知?dú)w檔程序完成了該操作:
[unarchiver finishDecoding];
在某個(gè)需要?dú)w檔的類(lèi)中:
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
self.lines = [aDecoder decodeObjectForKey:kLinesKey];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:self.lines forKey:kLinesKey];
}
- (instancetype)copyWithZone:(NSZone *)zone{
BIDFourLines *copy = [[[self class] allocWithZone:zone] init];
NSMutableArray *linesCopy = [NSMutableArray new];
for (id line in self.lines) {
linesCopy addObject:[line copyWithZone:zone];
}
copy.lines = linesCopy;
return copy;
}
我們剛才實(shí)現(xiàn)了遵循NSCoding和NSCopying所需的所有方法。在encodeWithCoder:中對(duì)四個(gè)屬性進(jìn)行了編碼流济,并在initWithCoder:中使用相同的4個(gè)鍵值對(duì)對(duì)這些屬性進(jìn)行解碼锐锣。在copyWithZone:中腌闯,我們創(chuàng)建了一個(gè)新的BIDFourLines對(duì)象,并將四個(gè)字符串復(fù)制到其中雕憔。
然后更改ViewController姿骏。
- (void)viewDidLoad {
[super viewDidLoad];
NSString *filePath = [self dataFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSData *data = [[NSMutableData alloc] initWithContentsOfFile:filePath];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
BIDFourLines *fourlines = [unarchiver decodeObjectForKey:kRootKey];
[unarchiver finishDecoding];
for (int i = 0; i < 4; i++) {
UITextField *theField = self.lineFields[i];
theField.text = fourlines.lines[i];
}
}
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:app];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)applicationWillResignActive:(NSNotification *)notification{
NSString *filePath = [self dataFilePath];
//對(duì)outlet集合中每個(gè)元素提取相同的屬性(KVC)
BIDFourLines *fourLines = [[BIDFourLines alloc] init];
fourLines.lines = [self.lineFields valueForKey:@"text"];
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:fourLines forKey:kRootKey];
[archiver finishEncoding];
[data writeToFile:filePath atomically:YES];
}
使用內(nèi)嵌的SQLite3
首先在Targets的build phase中引入sqlite3動(dòng)態(tài)庫(kù)
SQLite3在存儲(chǔ)和檢索大量數(shù)據(jù)方面非常有效。他還能夠?qū)?shù)據(jù)進(jìn)行復(fù)雜的聚合橘茉,與使用對(duì)象執(zhí)行這些操作比工腋,獲得結(jié)果的速度更快。
SQLite3可以不需要將所有對(duì)象加載到內(nèi)存中畅卓。
SQLite3使用SQL(Structured Query Language擅腰, 結(jié)構(gòu)化查詢(xún)語(yǔ)言)。SQL是與關(guān)系數(shù)據(jù)庫(kù)交互的標(biāo)準(zhǔn)語(yǔ)言翁潘。
關(guān)系數(shù)據(jù)庫(kù)和面向?qū)ο蟮木幊陶Z(yǔ)言使用完全不同的方法來(lái)存儲(chǔ)和組織數(shù)據(jù)趁冈。這些方法差異很大,因而出現(xiàn)了在兩者之間進(jìn)行轉(zhuǎn)換的各種技術(shù)以及很多庫(kù)和工具拜马。這些技術(shù)統(tǒng)稱(chēng)為ORM(Object-Relational Mapping渗勘,對(duì)象關(guān)系映射)。目前有很多ORM工具可用于Cocoa touch.實(shí)際上俩莽,Apple提供的Core Data就是一種旺坠。
創(chuàng)建和打開(kāi)數(shù)據(jù)庫(kù)
使用SQLite3前,必須打開(kāi)數(shù)據(jù)庫(kù)扮超。用于執(zhí)行此操作的命令是sqlite3_open().這樣將打開(kāi)一個(gè)現(xiàn)有數(shù)據(jù)庫(kù)取刃,如果指定位置上不存在數(shù)據(jù)庫(kù),則函數(shù)便會(huì)創(chuàng)建一個(gè)新的數(shù)據(jù)庫(kù)出刷,下面是打開(kāi)新數(shù)據(jù)庫(kù)的代碼
sqlite3 *database;
int result = sqlite3_open("/path/to/database/file", &database);
如果result等于常量 SQLITE_OK,就表示數(shù)據(jù)庫(kù)已經(jīng)打開(kāi)璧疗。此處你應(yīng)該記住,數(shù)據(jù)庫(kù)文件必須以C字符串而非NSString的形式進(jìn)行傳遞馁龟。SQLite3是采用可移植的C而非OC編寫(xiě)的崩侠,它不知道什么是NSString.所幸,NSString有個(gè)方法坷檩,該方法能將NSString實(shí)例轉(zhuǎn)化為C字符串却音。
const char *stringPath = [@"string" UTF8String];
對(duì)SQLite3數(shù)據(jù)庫(kù)執(zhí)行完所有操作后,調(diào)用一下內(nèi)容來(lái)關(guān)閉數(shù)據(jù)庫(kù)淌喻。
sqlite3_close(database);
數(shù)據(jù)庫(kù)將所有數(shù)據(jù)存在表中僧家。可以通過(guò)SQL的CREATE語(yǔ)句創(chuàng)建一個(gè)新表裸删,并使用sqlite3_exec將其傳遞到打開(kāi)的數(shù)據(jù)庫(kù),代碼如下:
char *errorMsg;
const char *createSQL = "CREATE TABLE IF NOT EXISTS PEOPLE""(ID INTEGER PRIMARY KEY AUTOINCREMENT, FIELD_DATA TEXT)";
int result = sqlite3_exec(database, createSQL, NULL, NULL, &errorMsg);
如果里那個(gè)字符之間除了空白(包括換行符)之外沒(méi)有其他分隔字符八拱,那么這兩個(gè)字符串會(huì)被連接成一個(gè)字符串.
如之前所做的一樣,需要檢查result是否等于SQLITE_OK以確保命令成功運(yùn)行。如果命令未成功運(yùn)行肌稻,errorMsg將對(duì)所發(fā)生的問(wèn)題進(jìn)行描述
函數(shù)sqlite3_exec()針對(duì)SQLite3運(yùn)行任何不返回?cái)?shù)據(jù)的命令清蚀。它用于執(zhí)行:
- 更新
- 插入
- 刪除
從數(shù)據(jù)庫(kù)中檢索數(shù)據(jù)有點(diǎn)復(fù)雜,必須首先向其輸入SQL的SELECT命令來(lái)準(zhǔn)備該語(yǔ)句
NSString *query = @"SELECT ID, FIELD_DATA FROM FIELDS ORDER BY ROW";
sqlite3_stmt *statement;
int result = sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil);
所有接受字符串的SQLite3函數(shù)都要求使用舊式的C字符串爹谭。在實(shí)例中枷邪,我們可以創(chuàng)建并傳遞一個(gè)C字符串,也可以創(chuàng)建一個(gè)NSString并通過(guò)它的方法UTF8String派生一個(gè)C字符串诺凡。這兩個(gè)方法都行东揣。如果需要操縱字符串,則使用NSString或NSMutableString較為容易腹泌,但將NSString轉(zhuǎn)換為C字符串會(huì)導(dǎo)致一些額外的開(kāi)銷(xiāo)嘶卧。
如果result等于SQLITE_OK,則語(yǔ)句準(zhǔn)備成功凉袱,可以開(kāi)始遍歷結(jié)果集芥吟。下面的例子將遍歷結(jié)果集從數(shù)據(jù)庫(kù)中搜索int 和 NSString。
while (sqlite3_step(statement) == SQLITE_ROW) {
int rowNum = sqlite3_column_int(statement, 0);
char *rowData = (char *)sqlite3_column_text(statement, 1);
NSString *fieldValue = [[NSString alloc] initWithUTF8String:rowData];
//do something with the data here
}
sqlite3_finalize(statement);
綁定變量
雖然可以通過(guò)創(chuàng)建SQL字符串來(lái)插入值专甩,但常用的方法是使用綁定變量(bind variable)來(lái)執(zhí)行數(shù)據(jù)庫(kù)插入操作钟鸵。正確處理字符串并確保他們沒(méi)有無(wú)效字符(以及引號(hào)處理過(guò)的屬性)是非常繁瑣的事情。借助綁定變量涤躲,這些問(wèn)題都迎刃而解棺耍。
需使用綁定變量插入值,只需按正常方式創(chuàng)建SQL語(yǔ)句种樱,但要在SQL字符串中添加一個(gè)問(wèn)號(hào).每個(gè)問(wèn)號(hào)都表示一個(gè)需要在語(yǔ)句執(zhí)行之前進(jìn)行綁定的變量烈掠。然后準(zhǔn)備好SQL語(yǔ)句,將值綁定到各個(gè)變量并執(zhí)行命令缸托。
下面這個(gè)示例使用兩個(gè)綁定變量預(yù)處理SQL語(yǔ)句,它將int綁定到第一個(gè)變量瘾蛋,將字符串綁定到第二個(gè)變量俐镐,然后執(zhí)行查詢(xún)語(yǔ)句:
char *sql = "insert into foo values (?, ?);";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(database, sql, -1, &stmt, nil) == SQLITE_OK) {
sqlite3_bind_int(stmt, 1, 235);
sqlite3_bind_text(stmt, 2, "Bar", -1, NULL);
}
if (sqlite3_step(stmt) != SQLITE_DONE) {
NSLog(@"This should be real error checking");
}
sqlite3_finalize(stmt);
根據(jù)希望使用的數(shù)據(jù)類(lèi)型,可以選擇不同的綁定語(yǔ)句哺哼。大部分綁定函數(shù)都只有3個(gè)參數(shù)佩抹。
- 無(wú)論針對(duì)哪種數(shù)據(jù)類(lèi)型,任何綁定函數(shù)的第一個(gè)參數(shù)都指向之前在sqlite3_prepare_v2()調(diào)用中使用的sqlite3_stmt.
- 第二個(gè)參數(shù)是所綁定的變量的索引取董。它是一個(gè)有序索引的值棍苹,這表示SQL語(yǔ)句中的第一個(gè)問(wèn)號(hào)索引是1,而其后每個(gè)問(wèn)號(hào)的索引值都依次加1
- 第三個(gè)參數(shù)始終表示應(yīng)該替換問(wèn)號(hào)的值茵汰。有些綁定函數(shù)(比如說(shuō)用于綁定文本和二進(jìn)制數(shù)據(jù)的綁定函數(shù))擁有另外兩個(gè)參數(shù)枢里。
- 一個(gè)參數(shù)是在上面的第三個(gè)參數(shù)中傳遞的數(shù)據(jù)的長(zhǎng)度。對(duì)于C字符串,可以傳遞-1來(lái)代替字符串的長(zhǎng)度栏豺,則函數(shù)將使用整個(gè)字符串彬碱,對(duì)于所有其他情況,需要指定所傳遞數(shù)據(jù)的長(zhǎng)度奥洼。
- 另一個(gè)參數(shù)是可選的函數(shù)callback巷疼,用于在語(yǔ)句執(zhí)行后完成內(nèi)存清理工作。通過(guò)整個(gè)函數(shù)使用malloc()釋放已分配的內(nèi)存灵奖。
- 綁定函數(shù)后面的語(yǔ)法似乎看起來(lái)有點(diǎn)奇怪嚼沿,因?yàn)槲覀儓?zhí)行了一個(gè)插入操作。當(dāng)使用綁定常量時(shí)瓷患,會(huì)將相同語(yǔ)法同時(shí)用于查詢(xún)和更新骡尽。如果SQL字符串包含了一個(gè)SQL查詢(xún)(而不是更新),我們需要多次調(diào)用sqlite3_step()尉尾,直到它返回SQLITE_DONE爆阶。因?yàn)檫@里是更新,所以?xún)H調(diào)用一次沙咏。
SQLite3的應(yīng)用
#import "ViewController.h"
#import <sqlite3.h>
@interface ViewController ()
@property (copy, nonatomic) IBOutletCollection(UITextField) NSArray *lineFields;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
sqlite3 *database;
if (sqlite3_open([[self dataFilePath] UTF8String], &database) != SQLITE_OK) {
sqlite3_close(database);
NSAssert(0, @"Failed to open database");
}
NSString *createSQL = @"CREATE TABLE IF NOT EXISTS FIELDS""(ROW INTEGER PRIMARY KEY, FIELD_DATA TEXT)";
char *errorMsg;
if (sqlite3_exec(database, [createSQL UTF8String], NULL, NULL, &errorMsg) != SQLITE_OK) {
sqlite3_close(database);
NSAssert(0, @"Error creating table: %s", errorMsg);
}
NSString *query = @"SELECT ROW, FIELD_DATA FROM FIELDS ORDER BY ROW";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(database, [query UTF8String], -1, &stmt, nil) == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
int row = sqlite3_column_int(stmt, 0);
char *rowData = (char *)sqlite3_column_text(stmt, 1);
NSString *fieldValue = [[NSString alloc] initWithUTF8String:rowData];
UITextField *field = self.lineFields[row];
field.text = fieldValue;
}
sqlite3_finalize(stmt);
}
sqlite3_close(database);
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:app];
}
- (void)applicationWillResignActive:(NSNotification *)notification{
sqlite3 *database;
if (sqlite3_open([[self dataFilePath] UTF8String], &database) != SQLITE_OK) {
sqlite3_close(database);
NSAssert(0, @"Failed to open database");
}
for (int i = 0; i < 4; i++) {
UITextField *field = self.lineFields[i];
char *update = "INSERT OR REPLACE INTO FIELDS (ROW, FIELD_DATA""VALUES (?, ?);";
char *errorMsg = NULL;
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(database, update, -1, &stmt, nil) == SQLITE_OK) {
sqlite3_bind_int(stmt, 1, i);
sqlite3_bind_text(stmt, 2, [field.text UTF8String], -1, NULL);
}
if (sqlite3_step(stmt) != SQLITE_DONE) {
NSAssert(0, @"Error updating table: %s", errorMsg);
}
sqlite3_finalize(stmt);
}
sqlite3_close(database);
}
- (NSString *)dataFilePath{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = paths[0];
return [path stringByAppendingPathComponent:@"data.sqlite"];
}
@end
首先打開(kāi)數(shù)據(jù)庫(kù)辨图,如果在打開(kāi)時(shí)遇到了問(wèn)題,則關(guān)閉它并拋出一個(gè)斷言錯(cuò)誤肢藐。
接下來(lái)故河,需要確保有一個(gè)表來(lái)保存我們的數(shù)據(jù)∵罕可以使用CREATE TABLE來(lái)完成此任務(wù)鱼的。通過(guò)指定IF NOT EXISTS,可以防止數(shù)據(jù)庫(kù)覆蓋現(xiàn)有數(shù)據(jù)痘煤。如果已有一個(gè)具有相同名稱(chēng)的表凑阶,此命令會(huì)直接退出,不執(zhí)行任何操作衷快,所以可以在應(yīng)用每次啟動(dòng)時(shí)安全地調(diào)用它宙橱,無(wú)需顯式檢查表是否存在。
最后需要加載數(shù)據(jù)蘸拔。為此师郑,使用SELECT語(yǔ)句。在這個(gè)簡(jiǎn)單例子中调窍,我們創(chuàng)建了一個(gè)SELECT來(lái)從數(shù)據(jù)庫(kù)請(qǐng)求所有行宝冕,并要求SQLite3準(zhǔn)備我們的SELECT。要告訴SQLite3按行號(hào)排序各行邓萨,以便我們總是以相同順序獲取它們地梨。否則菊卷,SQLite3將按內(nèi)部存儲(chǔ)順序返回各行。
最后關(guān)閉數(shù)據(jù)庫(kù)連接湿刽,所有操作到此結(jié)束的烁。
請(qǐng)注意,我們?cè)趧?chuàng)建表和加載它所包含的所有數(shù)據(jù)后立即關(guān)閉了數(shù)據(jù)庫(kù)連接诈闺,而不是在應(yīng)用運(yùn)行的整個(gè)過(guò)程中保持打開(kāi)狀態(tài)渴庆。這是管理連接最簡(jiǎn)單的方式,對(duì)于整個(gè)小應(yīng)用雅镊,我們可以在需要連接時(shí)再打開(kāi)它襟雷。在其他需要頻繁使用數(shù)據(jù)庫(kù)的應(yīng)用中,可能有必要始終打開(kāi)連接仁烹。
其他更改是在applicationWillResignActive:方法中進(jìn)行的耸弄,我們需要把應(yīng)用數(shù)據(jù)保存在這里。由于數(shù)據(jù)庫(kù)中的數(shù)據(jù)存儲(chǔ)在一個(gè)表中卓缰,存儲(chǔ)后應(yīng)用的數(shù)據(jù)看起來(lái)跟下面相似:
行 | FIELD_DATA
------------- | -------------
0 | text1
1 | text2
2 | text3
3 | text4
applicationWillResignActive:方法會(huì)首先再次打開(kāi)數(shù)據(jù)庫(kù)计呈。然后保存數(shù)據(jù),在4個(gè)字段中進(jìn)行循環(huán)征唬,生成4條獨(dú)立命令來(lái)更新數(shù)據(jù)庫(kù)中的每一行捌显。
我們?cè)谘h(huán)中要做的第一件事就是創(chuàng)建一個(gè)字段名稱(chēng),以便可以檢索到正確的文本框輸出口总寒。記住扶歪,使用valueForKey:可以根據(jù)名稱(chēng)檢索屬性。同時(shí)為錯(cuò)誤消息聲明一個(gè)指針摄闸,在出現(xiàn)錯(cuò)誤時(shí)使用善镰。
我們?cè)O(shè)計(jì)了一條帶兩個(gè)綁定變量的 INSERT OR REPLACE的SQL語(yǔ)句。第一個(gè)變量代表所存儲(chǔ)的行年枕,第二個(gè)變量代表要存儲(chǔ)的實(shí)際內(nèi)容字符串值炫欺。使用INSERT OR REPLACE而不是更標(biāo)準(zhǔn)的INSERT,就不必?fù)?dān)心某個(gè)行是否存在熏兄。
接下來(lái)聲明一個(gè)指向語(yǔ)句的指針竣稽,然后為語(yǔ)句添加綁定變量,并將值綁定到兩個(gè)綁定變量
然后調(diào)用sqlite3_step()來(lái)執(zhí)行更新霍弹,檢查并確定其運(yùn)行正常,然后完成語(yǔ)句娃弓,結(jié)束循環(huán)典格。
注意,此處使用了一個(gè)斷言來(lái)檢查錯(cuò)誤條件台丛。之所以會(huì)使用斷言耍缴,而不使用異忱危或手動(dòng)錯(cuò)誤檢查,是因?yàn)檫@種情況只有在開(kāi)發(fā)人員出錯(cuò)的情況下才會(huì)出現(xiàn)防嗡。使用此斷言宏有助于我們調(diào)試代碼变汪,并且可以脫離最終應(yīng)用。如果某個(gè)錯(cuò)誤條件是用戶(hù)正常情況下可能遇到的條件蚁趁,則應(yīng)該使用其他形式的錯(cuò)誤檢查裙盾。
有一個(gè)條件可能導(dǎo)致前面的SQLite代碼出現(xiàn)錯(cuò)誤,而不是程序員錯(cuò)誤他嫡。如果設(shè)備的存儲(chǔ)區(qū)已滿番官,SQLite無(wú)法將其更改保存到數(shù)據(jù)庫(kù),那么這里也會(huì)發(fā)生錯(cuò)誤钢属。但是這種情況很少見(jiàn)徘熔,并可能為用戶(hù)帶來(lái)更深層次的問(wèn)題,不過(guò)這已超出了應(yīng)用數(shù)據(jù)的范圍淆党。
完成循環(huán)后酷师,關(guān)閉數(shù)據(jù)庫(kù)。
Core Data
Core Data是一款穩(wěn)定染乌,功能全面的持久化工具山孔。
你不需要?jiǎng)?chuàng)建類(lèi),而是先在數(shù)據(jù)模型編輯器中創(chuàng)建一些實(shí)體(entity)慕匠,然后在代碼中為這些實(shí)體創(chuàng)建托管對(duì)象.(managed object)
實(shí)體表示對(duì)對(duì)象的描述饱须,而托管對(duì)象表示在運(yùn)行時(shí)創(chuàng)建的該實(shí)體的具體實(shí)例,
因此台谊,在數(shù)據(jù)模型編輯器中蓉媳,你將創(chuàng)建實(shí)體,而在代碼中锅铅,你將創(chuàng)建并檢索托管對(duì)象酪呻。實(shí)體和托管對(duì)象之間的差異類(lèi)似于類(lèi)和類(lèi)的實(shí)例。
實(shí)體由屬性組成盐须,屬性分為3種類(lèi)型玩荠。
- 特性(attribute) 特性在Core Data實(shí)體中的作用與實(shí)例變量在Objective-C類(lèi)中的作用完全相同,它們都用于保存數(shù)據(jù)贼邓。
- 關(guān)系--顧名思義阶冈,關(guān)系用于定義實(shí)體之間的關(guān)系。舉例來(lái)說(shuō)塑径,假設(shè)要定義一個(gè)Person實(shí)體女坑,你可能首先會(huì)定義一些特性,比如hairColor,eyeColor,height和weight.你還可以定義地址特性统舀,比如說(shuō)state和zipCode.或者匆骗,可以將他們嵌入到單獨(dú)的HomeAddress實(shí)體中劳景。時(shí)候后面這種方法娘汞,你可能還希望在Person和HomeAddress之間創(chuàng)建一個(gè)關(guān)系伟墙。關(guān)系可以1對(duì)1或1對(duì)多。從Person到HomeAddress的關(guān)系可以是1對(duì)1祖搓,因?yàn)榇蠖鄶?shù)人都只有一個(gè)家庭地址瓮钥。從HomeAddress到Person的關(guān)系可以是1對(duì)多筋量,因?yàn)榭赡芏鄠€(gè)person住在同一個(gè)HomeAddress
- 提取屬性(fetched property):提取屬性是關(guān)系的備選方法。用提取屬性可以創(chuàng)建一個(gè)可在提取時(shí)被評(píng)估的查詢(xún)骏庸,從而確定哪些對(duì)象屬于這個(gè)關(guān)系毛甲。沿用剛才的例子,一個(gè)Person對(duì)象可以擁有一個(gè)名為Neighbors的提取屬性具被,該屬性查找數(shù)據(jù)存儲(chǔ)中與這個(gè)Person的HomeAddress擁有相同郵政編碼的所有HomeAddress對(duì)象玻募。由于提取屬性的結(jié)構(gòu)和使用方式,它們通常是1對(duì)1的關(guān)系一姿。提取屬性也是唯一一種能讓你跨越多個(gè)數(shù)據(jù)存儲(chǔ)的關(guān)系七咧。
通常,特性叮叹,關(guān)系和提取屬性都是使用Xcode的數(shù)據(jù)模型編輯器定義的.
鍵值編碼
我們的代碼中不再使用存取方法和修改方法艾栋,而是使用鍵值編碼來(lái)設(shè)置屬性或檢索它們的已有值。
在操作托管對(duì)象時(shí)蛉顽,用于設(shè)置和檢索屬性值得鍵就是希望設(shè)置的特性的名稱(chēng)蝗砾。因此要從托管對(duì)象中檢索在name特性中的值,需要調(diào)用以下方法:
NSString *name = [myManagedObject valueForKey:@"name"];
同樣携冤,要為托管對(duì)象的屬性設(shè)置新值悼粮,可以執(zhí)行一下操作:
[myManagedObject setValue:@"daf" forKey:@"dajif"];
在上下文中結(jié)合它們
那么,托管對(duì)象的活動(dòng)區(qū)域在哪里呢曾棕?它們位于所謂的持久存儲(chǔ)中扣猫,有時(shí)也成為支持存儲(chǔ)(backing store)。持久存儲(chǔ)可以采用多種不同的形式翘地。默認(rèn)情況下申尤,Core Data應(yīng)用將支持存儲(chǔ)實(shí)現(xiàn)為在應(yīng)用Documents目錄中的SQLite數(shù)據(jù)庫(kù)。雖然數(shù)據(jù)是通過(guò)SQLite存儲(chǔ)的衙耕,但Core Data框架中的類(lèi)將完成與加載和保存數(shù)據(jù)相關(guān)的所有工作昧穿。如果使用Core Data,則不需要編寫(xiě)任何SQL語(yǔ)句。你只需要操作對(duì)象橙喘,而內(nèi)部的工作就由Core Data完成时鸵。
除了SQLite之外,支持存儲(chǔ)還可以作為二進(jìn)制文件實(shí)現(xiàn)渴杆,甚至以XML格式存儲(chǔ)寥枝。還有一種選擇是創(chuàng)建一個(gè)內(nèi)存庫(kù),編寫(xiě)緩存機(jī)制時(shí)可以采取這種方法磁奖,但它在當(dāng)前回話結(jié)束后無(wú)法保存數(shù)據(jù)囊拜。在幾乎所有情況下,你都應(yīng)該采用默認(rèn)的設(shè)置比搭,并使用SQLite作為持久存儲(chǔ)冠跷。
雖然大多數(shù)應(yīng)用都只有一個(gè)持久存儲(chǔ),但也可以在同一應(yīng)用中使用多個(gè)持久存儲(chǔ)身诺。
除了創(chuàng)建它之外(通常在應(yīng)用委托中實(shí)現(xiàn))蜜托,我們通常不會(huì)直接操作持久存儲(chǔ),而是使用所謂的托管對(duì)象上下文(context)霉赡。上下文協(xié)調(diào)對(duì)持久存儲(chǔ)的訪問(wèn)橄务,同時(shí)保存自上次保存對(duì)象以來(lái)修改過(guò)的屬性的信息。上下文還能通過(guò)撤銷(xiāo)管理器來(lái)取消所有更改穴亏,這意味著你可以撤銷(xiāo)單個(gè)操作或是回滾到上次保存的數(shù)據(jù)蜂挪。(有點(diǎn)類(lèi)似git哦)
可以將多個(gè)上下文指向相同的持久存儲(chǔ),但大多數(shù)ios應(yīng)用只使用一個(gè)嗓化。
許多核心數(shù)據(jù)調(diào)用都需要NSManagedObjectContext作為參數(shù)棠涮,或者需要在上下文中執(zhí)行。除了一些更加復(fù)雜刺覆、多線程的iOS應(yīng)用外严肪,應(yīng)用委托都可以只使用managedObjectContext屬性--它是Xcode項(xiàng)目魔板自動(dòng)為應(yīng)用創(chuàng)建的默認(rèn)上下文。
你可能會(huì)發(fā)現(xiàn)谦屑,除了委托對(duì)象上下文和持久存儲(chǔ)協(xié)調(diào)者之外驳糯,所提供的應(yīng)用委托還包含一個(gè)NSManagedObjectModel實(shí)例。該類(lèi)負(fù)責(zé)在運(yùn)行時(shí)加載和表示使用Xcode中的數(shù)據(jù)模型編輯器創(chuàng)建的數(shù)據(jù)模型伦仍。通常结窘,你不需要直接與該類(lèi)交互。該類(lèi)由其他Core Data類(lèi)在后臺(tái)使用充蓝,因此它們可以確定數(shù)據(jù)模型中定義了哪些實(shí)體和屬性隧枫。只要使用所提供的文件創(chuàng)建數(shù)據(jù)模型,就完全不需要擔(dān)心這個(gè)類(lèi)谓苟」倥В‘
創(chuàng)建托管對(duì)象
創(chuàng)建托管對(duì)象的新實(shí)例非常簡(jiǎn)單,但沒(méi)有alloc和init使用起來(lái)簡(jiǎn)單涝焙。這里使用NSEntityDescription類(lèi)中的insertNewObjectForEntityForName:inManagedObjectContext:工廠方法卑笨。NSEntityDescription的工作是跟蹤在應(yīng)用的數(shù)據(jù)模型中定義的所有實(shí)體并能夠讓你創(chuàng)建這些實(shí)體的實(shí)例。此方法創(chuàng)建并返回一個(gè)實(shí)例仑撞,表示內(nèi)存中的單個(gè)實(shí)體赤兴。它返回使用該特定實(shí)體的正常屬性設(shè)置的NSManagedObject實(shí)例,或者如果將實(shí)體配置為使用NSManagedObject的特定子類(lèi)實(shí)現(xiàn)妖滔,則返回該類(lèi)的實(shí)例。請(qǐng)記住桶良,實(shí)體類(lèi)似于類(lèi)座舍。實(shí)體是對(duì)象的描述,用于定義特定的實(shí)體具有哪些屬性陨帆。
創(chuàng)建新對(duì)象的方法如下:
NSManagedObject *thing = [NSEntityDescription insertNewObjectForEntityForName:@"Thing" inManagedObjectContext:context];
這個(gè)方法的名稱(chēng)為insertNewObjectForEntityForName:inManagedObjectContext:曲秉,因?yàn)槌藙?chuàng)建新對(duì)象外,它還將此新對(duì)象插入到上下文疲牵,并返回這個(gè)對(duì)象承二。調(diào)用結(jié)束后,對(duì)象存在于上下文中纲爸,但還不是持久存儲(chǔ)的一部分亥鸠。下一次托管對(duì)象上下文的save:方法被調(diào)用時(shí),這個(gè)對(duì)象將被添加到持久存儲(chǔ)缩焦。
獲取托管對(duì)象
要從持久存儲(chǔ)中獲取托管對(duì)象读虏,可以使用獲取請(qǐng)求(fetch request),這是Core Data處理預(yù)定義的查詢(xún)的方式袁滥。例如盖桥,可以要求“返回所有eyeColor為藍(lán)色的Person"
首次創(chuàng)建獲取請(qǐng)求后,為它提供一個(gè)NSEntityDescription题翻,指定希望檢索的一個(gè)或多個(gè)對(duì)象實(shí)體揩徊。下面是一個(gè)創(chuàng)建獲取請(qǐng)求的例子:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entityDescr = [NSEntityDescription entityForName:@"Thing" inManagedObjectContext:context];
[request setEntity:entityDescr];
也可以使用NSPredicate類(lèi)為獲取請(qǐng)求指定條件。Predicate類(lèi)似于SQL的WHERE子句嵌赠,可定義條件讓獲取請(qǐng)求得出結(jié)果塑荒。下面是一個(gè)簡(jiǎn)單的示例:
NSPredicate *pre = [NSPredicate predicateWithFormat:@"(name = %@)", nameThing];
[request setPredicate:pre];
第一行代碼創(chuàng)建的predicate告訴獲取請(qǐng)求,無(wú)需獲取指定實(shí)體的所有托管對(duì)象姜挺,它應(yīng)僅獲取那些name屬性被設(shè)置為當(dāng)前存儲(chǔ)在nameThinng變量中的值的托管對(duì)象齿税。所以,如果nameThing是一個(gè)包含值@"Bob"的NSString,則會(huì)告訴獲取請(qǐng)求僅返回其name屬性為"Bob"的托管對(duì)象炊豪。這是一個(gè)簡(jiǎn)單的例子凌箕,predicate復(fù)雜很多。
創(chuàng)建了獲取請(qǐng)求并為它提供實(shí)體描述后(可以選擇為它指定一個(gè)predicate)词渤,使用NSManagedObjectContext中的實(shí)例方法來(lái)執(zhí)行獲取請(qǐng)求:
NSError *error;
NSArray *objects = [context executeFetchRequest:request error:&error];
if (objects == nil) {
//handle error
}
executeFetchRequest:error:將從持久存儲(chǔ)中加載特定對(duì)象牵舱,并在一個(gè)數(shù)組中返回它們。如果遇到錯(cuò)誤缺虐,則會(huì)獲得一個(gè)nil數(shù)組芜壁,并且你提供的錯(cuò)誤指針將指向描述特定問(wèn)題的NSError對(duì)象。如果沒(méi)有遇到錯(cuò)誤,則會(huì)獲得一個(gè)有效的數(shù)組慧妄,但其中可能沒(méi)有任何對(duì)象顷牌,因?yàn)榭赡軟](méi)有任何對(duì)象滿足指定標(biāo)準(zhǔn)。此后塞淹,context(對(duì)它執(zhí)行了請(qǐng)求)將跟蹤對(duì)該數(shù)組中返回的托管對(duì)象的所有更改韧掩。向該上下文發(fā)送一條save:信息可保存更改。
Core Data的應(yīng)用
#import "ViewController.h"
#import "AppDelegate.h"
static NSString * const kLineEntityName = @"Line";
static NSString * const kLineNumberKey = @"lineNumber";
static NSString * const kLineTextKey = @"lineText";
@interface ViewController ()
@property (strong, nonatomic) IBOutletCollection(UITextField) NSArray *lineFields;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:kLineEntityName];
NSError *error;
NSArray *objects = [context executeFetchRequest:request error:&error];
if (objects == nil) {
NSLog(@"There was a error!");
//handle error
}
for (NSManagedObject *oneObject in objects) {
int lineNum = [[oneObject valueForKey:kLineNumberKey] intValue];
NSString *lineText = [oneObject valueForKey:kLineTextKey];
UITextField *theField = self.lineFields[lineNum];
theField.text = lineText;
}
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:app];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)applicationWillResignActive:(NSNotification *)notification{
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
NSManagedObjectContext *context = appDelegate.managedObjectContext;
NSError *error;
for (int i = 0; i < 4; i++) {
UITextField *textFiled = self.lineFields[i];
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:kLineEntityName];
NSPredicate *pred = [NSPredicate predicateWithFormat:@"(%s = %d)", kLineNumberKey, i];
request.predicate = pred;
NSArray *objects = [context executeFetchRequest:request error:&error];
if (objects == nil) {
NSLog(@"There is an error!");
//handle error
}
NSManagedObject *theLine = nil;
if (objects.count > 0) {
theLine = objects[i];
}else {
theLine = [NSEntityDescription insertNewObjectForEntityForName:kLineEntityName inManagedObjectContext:context];
}
[theLine setValue:[NSNumber numberWithInt:i] forKey:kLineNumberKey];
[theLine setValue:textFiled.text forKey:kLineTextKey];
}
[appDelegate saveContext];
}
來(lái)看一下viewDidLoad方法窖铡。我們需要確定持久存儲(chǔ)中是否已經(jīng)存在數(shù)據(jù),如果有則加載數(shù)據(jù)并使用它填充字段坊谁。該方法首先獲取對(duì)應(yīng)委托的引用费彼,我們將使用這個(gè)引用獲得為我們創(chuàng)建的托管對(duì)象上下文。
下一個(gè)步驟是創(chuàng)建一個(gè)獲取請(qǐng)求并將實(shí)體描述傳遞給它口芍,以便請(qǐng)求知道要檢索的對(duì)象類(lèi)型
由于我們希望檢索持久存儲(chǔ)中的所有l(wèi)ine對(duì)象箍铲,因此沒(méi)有創(chuàng)建predicate.通過(guò)執(zhí)行沒(méi)有predicate的請(qǐng)求,上下文將返回庫(kù)中的每一個(gè)line對(duì)象鬓椭。確保返回的是有效的數(shù)組颠猴,如果不是則記錄對(duì)應(yīng)的日志。
接下來(lái)小染,我們使用快速枚舉遍歷已獲取托管對(duì)象的數(shù)組翘瓮,從中提取每個(gè)托管對(duì)象的lineNumber和lineText值,并使用該信息更新用戶(hù)界面上的一個(gè)文本框裤翩。
然后资盅,我們需要在應(yīng)用即將終止(無(wú)論是轉(zhuǎn)到后臺(tái)還是完全退出)的時(shí)候獲取通知,以便能夠保存用戶(hù)對(duì)數(shù)據(jù)作出的任何更改踊赠。
我們接下來(lái)討論applicationWillResignActive:呵扛。這里使用的方法和前面一樣,先獲取對(duì)應(yīng)用委托的引用筐带,然后使用此引用獲取應(yīng)用的默認(rèn)上下文指針今穿。
然后使用循環(huán)語(yǔ)句為每個(gè)標(biāo)簽執(zhí)行一次,獲得每個(gè)字段對(duì)應(yīng)的索引伦籍。
接下來(lái)蓝晒,為line實(shí)體創(chuàng)建獲取請(qǐng)求。需要確認(rèn)持久存儲(chǔ)中是否已經(jīng)有一個(gè)與這個(gè)字段對(duì)應(yīng)的托管對(duì)象鸽斟,因此創(chuàng)建一個(gè)predicate拔创,用于為字段標(biāo)識(shí)正確的對(duì)象。
此時(shí)在上下文中執(zhí)行獲取請(qǐng)求并且檢查objects是否為nil.如果為nil,則表示遇到錯(cuò)誤富蓄,我們應(yīng)該為應(yīng)用執(zhí)行合適的錯(cuò)誤處理剩燥。
現(xiàn)在聲明一個(gè)指向NSManagedObject的指針并將它設(shè)為nil.執(zhí)行此操作的原因是,我們還不知道是要從持久存儲(chǔ)里加載對(duì)象還是創(chuàng)建新的托管對(duì)象。因此灭红,可以檢查與條件匹配的返回對(duì)象侣滩。如果返回了有效對(duì)象,就加載变擒,否則就創(chuàng)建一個(gè)新的托管對(duì)象來(lái)保存這個(gè)字段的文本君珠。
接著,使用鍵值編碼來(lái)設(shè)置行號(hào)和此托管對(duì)象的文本娇斑。
完成循環(huán)后策添,通知context保存更改。