iOS 數(shù)據(jù)持久化基礎(chǔ)知識小總結(jié)

1. 應(yīng)用的沙盒

注意:如果要為應(yīng)用程序啟用分享功能节芥,需要打開它的info.plist文件并添加鍵為Application supports iTunes file sharing值為YES的Item浴骂;

  • Documents:應(yīng)用程序可以將數(shù)據(jù)存儲在Documents目錄中。如果應(yīng)用程序啟用了iTunes文件分享功能夷恍,用戶就可以在iTunes中看到目錄的內(nèi)容(以及應(yīng)用程序創(chuàng)建的所有子目錄)王财,還可以對其更新文件。

  • Library:應(yīng)用程序也可以在這里存儲數(shù)據(jù)气破。它用來存放不想分享給用戶的文件。需要時你可以創(chuàng)建自己的子目錄餐抢。

  • tmp: tmp目錄供應(yīng)用存儲臨時文件现使。當(dāng)iOS設(shè)備執(zhí)行同步時,iTunes不會備份tmp中的文件弹澎。在不需要這些文件時朴下,應(yīng)用要負責(zé)刪除tmp中的文件,以免占用文件系統(tǒng)的控件苦蒿。

1. 獲取Documents目錄:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

NSString *documentsDirectory = [paths firstObject];

a.常量NSDocumentDirectory表明正在查找Document目錄的路徑殴胧。

b.常量NSUserDomainMask表明希望將所搜限制在應(yīng)用的沙盒中。

NSString *filename = [documentsDirectory stringByAppendingPathComponent:@"theFile.txt"];

完成此調(diào)用后佩迟,filename就包含了指向用用Documents目錄中theFile.txt文件的完整路徑团滥。然后可以根據(jù)filename來創(chuàng)建、讀取和寫入文件报强。

2. 獲取Library目錄:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);

NSString *libraryDirectory = [paths firstObject];

文件操作同上灸姊;

3. 獲取tmp目錄:

NSString *tempPath = NSTemporaryDirectory();

創(chuàng)建該路徑下的文件的路徑:

NSString *tempFile = [tempPath stringByAppendingPathComponent:@"tempFile.txt"];


2. 屬性列表序列化

序列化對象是指可以被轉(zhuǎn)化為字節(jié)流以便于存儲到文件中或者通過網(wǎng)絡(luò)進行傳輸?shù)膶ο蟆km然任何對象都可以被序列化秉溉,但只有某些對象才能被放置到某個集合類中(如NSDictionary或NSArray中)力惯,然后才使用該集合類的writeToFile:atomically:或writeToURL:atomically:方法將它們存儲到屬性列表中≌偎唬可以按照該方法序列化下面的類:

  • NSArray
  • NSMutableArray
  • NSDictionary
  • NSData
  • NSMutableData
  • NSString
  • NSMutableString
  • NSNumber
  • NSDate
  • 如果只使用這些對象構(gòu)建數(shù)據(jù)模型父晶,就可以使用屬性列表來方便的保存和加載數(shù)據(jù)。

  • 如果打算使用屬性列表持久保存應(yīng)用數(shù)據(jù)弄跌,則可以使用字典或數(shù)組甲喝。假設(shè)放到數(shù)組或者字典中的所有對象都是前面列出的可序列化對象,則可以通過對字典或者數(shù)組實例調(diào)用writeToFile:atomically:方法來寫入屬性列表铛只,如下:

[myArray writeToFile:@"some/file/location/output.plist" atomically:YES];

注意:atomically參數(shù)讓該方法將數(shù)據(jù)寫入輔助文件埠胖,而不是寫入指定位置。成功寫入該文件后淳玩,輔助文件將被復(fù)制到第一個參數(shù)指定的位置直撤。這是更安全的寫入文件的方法,因為如果應(yīng)用在保存期間崩潰蜕着,則現(xiàn)有文件不會被破壞谋竖。盡管這增加了一點開銷,但是多數(shù)情況下還是值得的。

  • 屬性列表方法的一個問題是無法將自定義對象序列化到屬性列表中圈盔,另外也不能使用沒有在可以序列化對象類型列表中指定的Cocoa Touch的其他類豹芯。這意味著無法直接使用NSURL、UIImage和UIColor等類驱敲。

  • 且不說序列化問題铁蹈,將這些模型對象保存到屬性列表中還意味著你無法輕松創(chuàng)建派生的或需要計算的屬性(例如,等于兩個屬性之和的屬性)众眨,并且必須將實際上應(yīng)該包含在模型類中的某些代碼移動到控制器類握牧。這些限制也適用于簡單的數(shù)據(jù)模型和簡單應(yīng)用。但在多數(shù)情況下娩梨,如果創(chuàng)建了專用的模型類沿腰,則應(yīng)用更容易維護。

  • 在復(fù)雜的應(yīng)用中狈定,簡單的屬性列表仍然非常有用颂龙。它們是將靜態(tài)數(shù)據(jù)包含在應(yīng)用中的最佳方法。例如纽什,當(dāng)應(yīng)用包含一個選取器時措嵌,創(chuàng)建一個屬性列表文件并將其放在項目的Resources文件夾中,就是將項目列表包含到選取器中的最佳方法芦缰,這樣就能把項目列表編譯到應(yīng)用中企巢。



3.對模型對象進行歸檔

  • 在Cocoa世界中,歸檔是指另一種形式的序列化让蕾,但它是任何對象都可以實現(xiàn)的更常規(guī)的類型浪规。專門編寫用于保存數(shù)據(jù)的任何模型對象都應(yīng)該支持歸檔。使用對模型對象進行歸檔的技術(shù)可以輕松將復(fù)雜的對象寫入文件探孝,然后讀取它們笋婿。
  • 只要在類中實現(xiàn)的每個屬性都是標(biāo)量(如整型和浮點型)或都是遵循NSCoding協(xié)議的某個類的實例,你就可以將整個對象進行完全的歸檔再姑。由于大多數(shù)支持存儲數(shù)據(jù)的Foundation和Cocoa Touch類都遵循NSCoding協(xié)議(不過有一些例外萌抵,如UIImage)找御,對于大多數(shù)類來說元镀,歸檔相對而言比較容易實現(xiàn)。
  • 盡管對歸檔的使用沒有嚴(yán)格要求霎桅,但還有一個協(xié)議應(yīng)該與NSCoding一起實現(xiàn)栖疑,即NSCopying協(xié)議。后者允許賦值對象滔驶,這使在使用數(shù)據(jù)模型對象時具備了較大的靈活性遇革。

1.遵循NSCoding協(xié)議

NSCoding協(xié)議聲明了兩個方法,這兩個方法都是必需的。一個方法將對象編碼到歸檔中萝快,另一個方法對歸檔解碼來創(chuàng)建一個新對象锻霎。這兩個方法都傳遞一個NSCoder實例,使用方式與NSUserDefaults非常相似揪漩。也可以使用KVC對對象和原生數(shù)據(jù)模型(如整型和浮點型)進行編碼和解碼旋恼。

  • 在OC中,必須使用正確的編碼方法將所有實例變量編碼為encoder奄容。如果要子類化某個也遵循NSCoding的類冰更,還需要確保對其父類調(diào)用encodeWithCoder:方法:
- (void)encodeWithCoder:(NSCoder *)encoder {
  [super encodeWithCoder:encoder]; // 讓父類對狀態(tài)進行編碼
  [encoder encodeObject:foo forKey:kFooKey];
  [encoder encodeObject:bar forKey:kBarKey];
  [encoder encodeInt:someInt forKey:kSomeIntKey];
  [encoder encodeFloat:someFloat forKey:kSomeFloatKey];
}
  • 還需要實現(xiàn)一個通過NSCoder初始化對象的初始化方法,恢復(fù)之前歸檔的對象昂勒。
  • OC中實現(xiàn)initWithCoder:方法比實現(xiàn)encodeWithCoder:方法稍微復(fù)雜一些蜀细。如果直接對NSObject進行子類化,或者對某些不遵循NSCoding的其他類進行子類化戈盈,如下:
- (id)initWithCoder:(NSCoder *)decoder {

  if (self = [super init]) {

    foo = [decoder decodeObjectForKey:kFooKey];
    bar = [decoder decodeObjectForKey:kBarKey];
    someInt = [decoder decodeIntForKey:kSomeIntKey];
    someFloat = [decoder decodeFloatForKey:kSomeFloatKey];
  }
  return self;
}
  • 該方法使用[super init]初始化對象實例奠衔。如果初始化成功,則它通過解碼NSCoder的實例中傳遞的值來設(shè)置其屬性塘娶。當(dāng)為某個具有父類遵循NSCoding的類實現(xiàn)NSCoding時涣觉,initWithCoder:方法稍有不同。它不再對super調(diào)用init血柳,而是調(diào)用initWithCoder:官册,如:
- (id)initWithCoder:(NSCoder *)decoder {

  if (self = [super initWithCoder:decoder]) {

    foo = [decoder decodeObjectForKey:kFooKey];
    bar = [decoder decodeObjectForKey:kBarKey];
    someInt = [decoder decodeIntForKey:kSomeIntKey];
    someFloat = [decoder decodeFloatForKey:kSomeFloatKey];
  }
  return self;
}

2. 實現(xiàn)NSCopying協(xié)議

  • NSCopying有一個copyWithZone方法,可用來復(fù)制對象难捌。實現(xiàn)NSCopying與實現(xiàn)initWithCoder非常相似膝宁,只需創(chuàng)建一個同一類的新實例,然后將實例的所有屬性都設(shè)置為與該對象屬性相同的值即可根吁。
- (id) copyWithZone:(NSZone *)zone {

  MyClass *copy = [[[self class] allocWithZone:zone] init];
  copy.foo = [self.foo copyWithZone:zone];
  copy.bar = [self.bar copyWithZone:zone];
  copy.someInt = self.someInt;
  copy.someFloat = self.someFloat;

  return copy;
}
  • 不要過于擔(dān)心NSZone參數(shù)员淫。它指向系統(tǒng)用于管理內(nèi)存的struct。只有在極少數(shù)情況下击敌,才需要關(guān)注zone或者自己創(chuàng)建的zone介返。對某個對象調(diào)用copy的方法與使用默認zone調(diào)用copyWithZone的方法完全相同,幾乎始終能滿足需求沃斤。事實上圣蝎,現(xiàn)在的iOS完全可以忽略zone。NSCopying用到zone在本質(zhì)上是考慮向后兼容性所致衡瓶。

3. 對數(shù)據(jù)對象進行歸檔和取消歸檔

  • 從遵循NSCoding的一個或者多個對象創(chuàng)建歸檔相對比較容易徘公。首先創(chuàng)建一個NSMutableData實例,用于包含編碼的數(shù)據(jù)哮针,然后創(chuàng)建一個NSKeyedArchiver實例关面,用于將對象歸檔到此NSMutableData實例中:
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
  • 創(chuàng)建這兩個實例后坦袍,使用key/value來對希望包含在歸檔中的所有對象進行歸檔:
[archiver encodeObject:myObject forKey:@"keyValueString"];
  • 對所有要包含的對象進行編碼之后,只需告知歸檔程序已經(jīng)完成了這些操作等太,并將NSMutableData實例寫入文件系統(tǒng):
[archiver finishEncoding];
BOOL success = [data writeToFile:@"/path/to/archive" atomically:YES];
  • 寫入文件時出現(xiàn)錯誤會將success設(shè)置為false或NO捂齐。如果success為true或YES,則數(shù)據(jù)已成功寫入指定文件缩抡。從該歸檔創(chuàng)建的任何對象與之前寫入該文件的對象一致辛燥。
  • 有一個快速方法可以獲取同樣的內(nèi)容:使用NSKeyedArchiver的archiveDataWithRootObject方法可以分配一個NSData對象并一次性將對象編碼進去,然后返回NSData對象缝其。
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object];
BOOL success = [data writeToFile:@"path/to/archive" atomically:YES];
  • 也可以使用archiveRootObject:toFile:方法直接從對象歸檔數(shù)據(jù)到文件:
BOOL success = [NSKeyedArchiver archiveRootObject:object toFile:@"/path/to/archive"];
  • 從歸檔重組對象的步驟與上面相似挎塌。從歸檔文件創(chuàng)建一個NSData實例,并創(chuàng)建一個NSKeyedUnarchiver對數(shù)據(jù)進行解碼:
NSData *data = [[NSData alloc] initWithContentsOfFile:@"/path/to/archive"];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
  • 然后内边,使用之前對對象進行歸檔的同一個key從解壓程序中讀取對象:
self.object = [unarchiver decodeObjectForKey:@"keyValueString"];
  • 最后榴都,告知歸檔程序已經(jīng)完成了該操作:
[unarchiver finishDecoding];


4. 使用iOS內(nèi)嵌的SQLite3

1. 創(chuàng)建或打開數(shù)據(jù)庫

使用SQLite3之前,必須打開數(shù)據(jù)庫漠其。用于執(zhí)行此操作的命令是sqlite3_open嘴高,這樣將打開一個現(xiàn)有數(shù)據(jù)庫。如果指定位置上不存在數(shù)據(jù)庫和屎,則函數(shù)會創(chuàng)建一個新的數(shù)據(jù)庫拴驮。下面是打開數(shù)據(jù)庫的代碼:

sqlite3 *database;
int result = sqlite3_open("/path/to/database/file", &database);

如果result等于常量SQLITE_OK,就表示數(shù)據(jù)庫已成功打開。需要注意的是柴信,數(shù)據(jù)庫文件的路徑必須以C字符串的形式進行傳遞套啤。SQLite3是采用可移植的C(而非OC)編寫的,它不知道什么是NSString随常。所幸潜沦,有一個NSString方法能從NSString實例生成C字符串:

const char *stringPath = [pathString UTF8String];

對SQLite3數(shù)據(jù)庫執(zhí)行完所有操作后,調(diào)用以下內(nèi)容來關(guān)閉數(shù)據(jù)庫:

sqlite3_close(database);

數(shù)據(jù)庫將所有數(shù)據(jù)存儲在表中绪氛∷艏Γ可以通過SQL的CREATE語句創(chuàng)建一個新表,并使用函數(shù)sqlite3_exec將其傳遞到打開的數(shù)據(jù)庫枣察,如下:

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);

提示: 在OC中争占,如果兩個字符串之間除了空白(包括換行符)之外沒有其他的分隔字符,那么這兩個字符串會被連接為一個字符串序目。

如之前所做的一樣臂痕,需要檢查result是否等于SQLITE_OK以確保命令成功運行。如果命令未成功運行宛琅,errorMsg或errMsg將對所發(fā)生的問題進行描述刻蟹。

函數(shù)sqlite3_exec針對SQLite3運行任何不返回數(shù)據(jù)的命令逗旁。它用于執(zhí)行更新嘿辟、插入和刪除操作舆瘪。從數(shù)據(jù)庫中檢索數(shù)據(jù)有點復(fù)雜,必須首先向其輸入SQL的SELECT命令來準(zhǔn)備該語句:

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);

注意: OC中所有接受字符串的SQLite3函數(shù)都要求使用舊樣式的C字符串红伦。在實例中英古,可以創(chuàng)建并傳遞一個C字符串,也可以創(chuàng)建一個NSString并通過它的方法(名為UTF8String)派生一個C字符串昙读。這兩個方法都行召调。如果需要操作字符串,則使用NSString或NSMutableString比較容易蛮浑,但將NSString轉(zhuǎn)換為C字符串會導(dǎo)致一些額外開銷唠叛。

如果result等于SQLITE_OK,則語句準(zhǔn)備成功,可以開始遍歷結(jié)果集沮稚。

遍歷結(jié)果集并從數(shù)據(jù)庫中檢索整型和字符串:

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];
  // 這里寫對數(shù)據(jù)進行處理的代碼
}
sqlite3_finalize(statement);

2. 綁定變量

雖然可以通過創(chuàng)建SQL字符串來插入值艺沼,但常用的方法是使用綁定變量來執(zhí)行數(shù)據(jù)庫插入操作。正確處理字符串并確保它們沒有無效字符(以及引號處理過的屬性)是非常繁瑣的事情蕴掏。借助綁定變量障般,這些問題迎刃而解。

要使用綁定變量插入值盛杰,只需按正常方式創(chuàng)建SQL語句即可挽荡,不過要在SQL字符串中添加一個問號。每個問號都表示一個需要在語句執(zhí)行之前進行綁定的變量即供。然后定拟,準(zhǔn)備好SQL語句,將值綁定到各個變量并執(zhí)行命令逗嫡。

下面這個實例使用兩個綁定變量預(yù)處理SQL語句办素。它將整形數(shù)綁定到第一個變量,將字符串綁定到第二個變量祸穷,然后執(zhí)行結(jié)束語句:

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ù)類型性穿,可以選擇不同的綁定語句。大部分綁定函數(shù)都有3個參數(shù)雷滚。

  • 無論針對哪種數(shù)據(jù)類型需曾,任何綁定函數(shù)的第一個參數(shù)都指向之前在sqlite3_prepare_v2()調(diào)用中使用的sqlite3_stmt。

  • 第二個參數(shù)是被綁定變量的索引祈远。它是一個有序索引的值呆万,這表示SQL語句中的第一個問好是索引1,其后的每個問號都依次按序增加1.

  • 第三個參數(shù)始終表示應(yīng)該替換問號的值车份。有些綁定函數(shù)(比如用于綁定文本和二進制數(shù)據(jù)的綁定函數(shù))擁有另外兩個參數(shù)谋减。

  • 一個參數(shù)是在上面第三個參數(shù)中傳遞的數(shù)據(jù)長度。對于C字符串扫沼,可以傳遞-1來代替字符串的長度出爹,這樣函數(shù)將使用整個字符串庄吼。對于所有其他情況,需要指定所傳遞數(shù)據(jù)的長度严就。

  • 另一個參數(shù)是可選的函數(shù)回調(diào)总寻,用于在語句執(zhí)行后完成內(nèi)存清理工作。通常梢为,這種函數(shù)使用malloc()釋放已分配的內(nèi)存渐行。

綁定語句后面的語法看起來可能有點奇怪,因為我們執(zhí)行了一個插入操作铸董。當(dāng)使用綁定變量時祟印,會將相同語法同時用于查詢和更新。如果SQL字符串包含一個SQL查詢(而不是更新)粟害,就需要多次調(diào)用sqlite3_step()旁理,直到它返回SQLITE_DONE。疑問這里是更新我磁,所以僅調(diào)用一次孽文。

3. SQLite3 的應(yīng)用

1. 鏈接到SQLite3庫

通過一個過程API來訪問SQLite3,該API提供對很多C函數(shù)調(diào)用的接口。要使用該API夺艰,需要將應(yīng)用鏈接到一個名為libsqlite3.tbd的動態(tài)庫芋哭。

2. 實現(xiàn)代碼
#import "ViewController.h"
#import <sqlite3.h>

@interface ViewController ()

@property (nonatomic, strong)IBOutletCollection(UITextField) NSArray *lineFields; // 四個UITextField

@end

@implementation ViewController

- (NSString *)dataFilePath {
    
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    return [documentsDirectory stringByAppendingString:@"data.sqlite"];
}

- (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 PRIMARK KEY, FIELD_DATA TEXT);";
    char *errorMsg;
    if (sqlite3_exec(database, [createSQL UTF8String], NULL, NULL, &errorMsg)) {
        sqlite3_close(database);
        NSAssert(0, @"Error creating table: %s", errorMsg);
    }
    
    NSString *query = @"SELECT ROW, FIELD_DATA FROM FIELDS ORDER BY ROW";
    sqlite3_stmt *statement;
    if (sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil) == SQLITE_OK) {
        while (sqlite3_step(statement) == SQLITE_ROW) {
            int row = sqlite3_column_int(statement, 0);
            char *rowData = (char *)sqlite3_column_text(statement, 1);
            NSString *fieldValue = [[NSString alloc] initWithUTF8String:rowData];
            UITextField *field = self.lineFields[row];
            field.text = fieldValue;
        }
        sqlite3_finalize(statement);
    }
    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);
}

@end

首先我們打開數(shù)據(jù)庫,如果打開時遇到了問題郁副,則關(guān)閉它并拋出一個斷言錯誤(或者打印一段錯誤信息并中斷程序):

sqlite3 *database;
    if (sqlite3_open([[self dataFilePath] UTF8String], &database) != SQLITE_OK) {
        
        sqlite3_close(database);
        NSAssert(0, @"Failed to open database");
    }

接下來减牺,需要確保有一個表來保存我們的數(shù)據(jù)〈婊眩可以使用CREATE TABLE完成此任務(wù)拔疚。通過指定IF NOT EXISTS,可以防止數(shù)據(jù)庫覆蓋現(xiàn)有數(shù)據(jù)既荚。如果已有一個具有相同名稱的表稚失,此命令不會執(zhí)行任何操作,所以可以在應(yīng)用每次啟動時安全的調(diào)用它恰聘,不需要顯式的檢查表是否存在:

 NSString *createSQL = @"CREATE TABLE IF NOT EXISTS FIELDS"
                        "(ROW INTEGER PRIMARK KEY, FIELD_DATA TEXT);";
    char *errorMsg;
    if (sqlite3_exec(database, [createSQL UTF8String], NULL, NULL, &errorMsg)) {
        sqlite3_close(database);
        NSAssert(0, @"Error creating table: %s", errorMsg);
    }

數(shù)據(jù)庫中的每一行包含一個整型和一個字符串句各。整型指出圖形界面中得到的是哪一行的數(shù)字(從0開始計數(shù))宰衙。而字符串是這一行中文本框的內(nèi)容所刀。最后,需要加載數(shù)據(jù)纲仍。為此兼蕊,使用SELECT語句初厚。在這個例子中,創(chuàng)建一個SELECT從數(shù)據(jù)庫請求所有行并要求SQLite3準(zhǔn)備的SELECT孙技。要告訴SQLite3按行號順序排序各行产禾,以便總是以相同順序獲取它們排作。否則,SQLite3將按內(nèi)部存儲數(shù)據(jù)返回各行:

    NSString *query = @"SELECT ROW, FIELD_DATA FROM FIELDS ORDER BY ROW";
    sqlite3_stmt *statement;
    if (sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil) == SQLITE_OK) {

然后遍歷返回的每一行:

while (sqlite3_step(statement) == SQLITE_ROW) {

抓取行號并將它存儲在一個int變量中下愈,然后抓取字段數(shù)據(jù)保存到C語言字符串中:

int row = sqlite3_column_int(statement, 0);
char *rowData = (char *)sqlite3_column_text(statement, 1);

接下來纽绍,利用從數(shù)據(jù)庫獲取的值設(shè)置相應(yīng)的字段:

NSString *fieldValue = [[NSString alloc] initWithUTF8String:rowData];
UITextField *field = self.lineFields[row];
field.text = fieldValue;

最后關(guān)閉數(shù)據(jù)庫連接蕾久,所有操作到此結(jié)束:

         }
            sqlite3_finalize(statement);
     }
    sqlite3_close(database);

注意势似,示例在創(chuàng)建表和加載它所包含的所有數(shù)據(jù)后立即關(guān)閉了數(shù)據(jù)庫連接,而不是在應(yīng)用運行的整個過程中保持打開狀態(tài)僧著。這是管理連接最簡單的方式履因,對于這個小示例盹愚,可以在需要連接時再打開它栅迄。在其他需要頻繁使用數(shù)據(jù)庫的應(yīng)用中,可能有必要始終打開連接皆怕。

其他更改是在applicationWillResignActive方法中進行的毅舆,我們需要把應(yīng)用數(shù)據(jù)保存在這里。

applicationWillResignActive方法首次會再次打開數(shù)據(jù)庫愈腾。然后保存數(shù)據(jù)憋活,在4個字段中進行循環(huán),生成4條獨立的命令來更新數(shù)據(jù)庫中的每一行:

for (int i = 0; i < 4; i++) {
        UITextField *field = self.lineFields[i];

示例設(shè)計了一條帶有兩個綁定變量的INSERT OR REPLACE語句虱黄。第一個變量代表所存儲的行悦即,第二個變量代表要存儲的實際字符串值。使用INSERT OR REPLACE橱乱,而不是更標(biāo)準(zhǔn)的INSERT辜梳,就不需要擔(dān)心某個行是否已經(jīng)存在:

        char *update = "INSERT OR REPLACE INTO FIELDS (ROW, FIELD_DATA)"
        "VALUES (?, ?);";

接下來聲明一個指向語句的指針,然后為語句添加綁定變量泳叠,并將值綁定到兩個綁定變量:

       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);
       }

然后調(diào)用sqlite3_step來執(zhí)行更新作瞄,檢查并確定其運行正常,然后完成語句危纫,結(jié)束循環(huán):

          if (sqlite3_step(stmt) != SQLITE_DONE) {
            NSAssert(0, @"Error updating table: %s", errorMsg);
        }
        sqlite3_finalize(stmt);

注意粉洼,OC代碼中使用了一個斷言來檢查錯誤條件。之所以會使用斷言叶摄,而不使用異呈羧停或手動錯誤檢查,是因為這種情況只有在開發(fā)人員出錯的情況下才會出現(xiàn)蛤吓。使用此斷言宏將有助于我們調(diào)試代碼宵喂,并且可以脫離最終的應(yīng)用。

注意: 有一個條件可能導(dǎo)致前面的SQLite代碼出現(xiàn)錯誤会傲,但不是程序員錯誤锅棕。如果設(shè)備的存儲區(qū)已滿拙泽,SQLite無法將其更改保存到數(shù)據(jù)庫,那么這里也會發(fā)生錯誤裸燎。但是顾瞻,這種情況很少見,并可能為用戶帶來更深層次的問題德绿,不過這已經(jīng)超出了應(yīng)用數(shù)據(jù)的范圍荷荤。如果系統(tǒng)處于這一狀態(tài),我們的應(yīng)用甚至可能無法成功啟動移稳。因此可以不用考慮這個問題蕴纳。

完成循環(huán)之后,關(guān)閉數(shù)據(jù)庫:

sqlite3_close(database);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末个粱,一起剝皮案震驚了整個濱河市古毛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌都许,老刑警劉巖稻薇,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異胶征,居然都是意外死亡塞椎,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門弧烤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來忱屑,“玉大人,你說我怎么就攤上這事暇昂≥航洌” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵急波,是天一觀的道長从铲。 經(jīng)常有香客問我,道長澄暮,這世上最難降的妖魔是什么名段? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮泣懊,結(jié)果婚禮上伸辟,老公的妹妹穿的比我還像新娘。我一直安慰自己馍刮,他們只是感情好信夫,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般静稻。 火紅的嫁衣襯著肌膚如雪警没。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天振湾,我揣著相機與錄音杀迹,去河邊找鬼。 笑死押搪,一個胖子當(dāng)著我的面吹牛树酪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嵌言,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼嗅回,長吁一口氣:“原來是場噩夢啊……” “哼及穗!你這毒婦竟也來了摧茴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤埂陆,失蹤者是張志新(化名)和其女友劉穎苛白,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體焚虱,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡购裙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了鹃栽。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片躏率。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖民鼓,靈堂內(nèi)的尸體忽然破棺而出薇芝,到底是詐尸還是另有隱情,我是刑警寧澤丰嘉,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布夯到,位于F島的核電站,受9級特大地震影響饮亏,放射性物質(zhì)發(fā)生泄漏耍贾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一路幸、第九天 我趴在偏房一處隱蔽的房頂上張望荐开。 院中可真熱鬧,春花似錦简肴、人聲如沸晃听。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杂伟。三九已至移层,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赫粥,已是汗流浹背观话。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留越平,地道東北人频蛔。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像秦叛,于是被迫代替她去往敵國和親晦溪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

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