數(shù)據(jù)持久化學(xué)習(xí)筆記

數(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保存更改。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末毫缆,一起剝皮案震驚了整個(gè)濱河市唯竹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌苦丁,老刑警劉巖浸颓,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異旺拉,居然都是意外死亡产上,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)蛾狗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)晋涣,“玉大人,你說(shuō)我怎么就攤上這事沉桌∫錾” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵蒲牧,是天一觀的道長(zhǎng)撇贺。 經(jīng)常有香客問(wèn)我,道長(zhǎng)冰抢,這世上最難降的妖魔是什么松嘶? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮挎扰,結(jié)果婚禮上翠订,老公的妹妹穿的比我還像新娘。我一直安慰自己遵倦,他們只是感情好尽超,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著梧躺,像睡著了一般似谁。 火紅的嫁衣襯著肌膚如雪傲绣。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,246評(píng)論 1 308
  • 那天巩踏,我揣著相機(jī)與錄音秃诵,去河邊找鬼。 笑死塞琼,一個(gè)胖子當(dāng)著我的面吹牛菠净,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播彪杉,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼毅往,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了派近?” 一聲冷哼從身側(cè)響起煞抬,我...
    開(kāi)封第一講書(shū)人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎构哺,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體战坤,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡曙强,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了途茫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碟嘴。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖囊卜,靈堂內(nèi)的尸體忽然破棺而出娜扇,到底是詐尸還是另有隱情,我是刑警寧澤栅组,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布雀瓢,位于F島的核電站,受9級(jí)特大地震影響玉掸,放射性物質(zhì)發(fā)生泄漏刃麸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一司浪、第九天 我趴在偏房一處隱蔽的房頂上張望泊业。 院中可真熱鬧,春花似錦啊易、人聲如沸吁伺。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)篮奄。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宦搬,已是汗流浹背牙瓢。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留间校,地道東北人矾克。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像憔足,于是被迫代替她去往敵國(guó)和親胁附。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

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