App開發流程之資料持久化和編譯靜態連結庫,app靜態
先記錄資料持久化。
iOS用戶端提供的常用資料持久化方案:NSUserDefaults代表的使用者佈建,NSKeydArchiver代表的歸檔,plist檔案儲存體,SQLite資料庫(包括上層使用的Core Data,FMDB)。
每種方案都有各自的應用情境和範圍,不能一概而論。不過可以大致以資料儲存量和複雜度來區別。
除了以上提到的方案,再記錄一種方案:LevelDB代表的索引值對資料庫。
NSUserDefaults常用方法:
1.可以使用標準使用者佈建[NSUserDefaults standardUserDefaults],也可以通過init相關方法初始化新的使用者佈建
2.像使用字典一樣擷取、設定、移除索引值對
3.synchronize方法已經不建議使用
Plist檔案儲存體:
1.代碼讀取應用內已經存在的plist檔案,得到一個字典
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"plist"];
NSMutableDictionary *dic = [[NSMutableDictionary alloc] initWithContentsOfFile:filePath];
2.修改資料後,儲存或者建立plist檔案
[dic writeToFile:filePath atomically:YES];
NSKeydArchiver和NSKeyedUnarchiver:
1.歸檔有一個類方法:+ (BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)path;
解檔有一個類方法:+ (nullable id)unarchiveObjectWithFile:(NSString *)path;
可以直接對某一個對象進行歸檔和解檔。
2.但如果需要對多個索引值對進行操作,建議使用如下方法:
+ (void)archiveDataWithDictionary:(NSDictionary *)dic filename:(NSString*)filename archiveSuccessBlock:(archiveSuccessBlock)archiveSuccessBlock{ NSString *fullPath = [self getAppArchivedFileFullPathWithName:filename]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSMutableData *data = [NSMutableData data]; NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; NSArray *keyArray = [NSArray arrayWithArray:[dic allKeys]]; [archiver encodeObject:keyArray forKey:filename]; for (NSString *key in keyArray) { NSObject *object = [dic objectForKey:key]; [archiver encodeObject:object forKey:key]; } [archiver finishEncoding]; [data writeToFile:fullPath atomically:YES]; if (archiveSuccessBlock) { archiveSuccessBlock(); } });}+ (NSDictionary *)unarchiveDataWithFilename:(NSString *)filename{ NSMutableDictionary *dic = [NSMutableDictionary dictionary]; NSData *data = [[NSData alloc] initWithContentsOfFile:[self getAppArchivedFileFullPathWithName:filename]]; NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; NSArray *keyArray = [NSArray arrayWithArray:[unarchiver decodeObjectForKey:filename]]; for (NSString *key in keyArray) { NSObject *object = [unarchiver decodeObjectForKey:key]; [dic setObject:object forKey:key]; } [unarchiver finishDecoding]; return dic;}
歸檔的initForWritingWithMutableData和finishEncoding,解檔的initForReadingWithData和finishDecoding需要成對出現。
SQLite資料庫:
只要使用過SQL Server和MySQL之類的關係型資料庫,就可以便於使用,只是底層的sql語言不太人性化,所以普遍採用了上層的Core Data或者FMDB類庫。
檔案操作:
首先關注方法:FOUNDATION_EXPORT NSArray<NSString *> *NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde);第一個枚舉參數表示檔案目錄,第二個表示範圍域,第三個參數表示是否補充完整的相對路徑
1.得到目前使用者的doc檔案根目錄:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *rootPath = paths[0];
2.補充子檔案路徑
NSString *fullPath = [rootPath stringByAppendingPathComponent:filename];
3.檔案操作,主要使用[NSFileManager defaultManager]單例對象
+ (void)createArchivedRootFile{ NSString *rootPath = [self getAppArchivedFilesRootPath]; if (![[NSFileManager defaultManager] fileExistsAtPath:rootPath]) { [[NSFileManager defaultManager] createDirectoryAtPath:rootPath withIntermediateDirectories:YES attributes:nil error:nil]; }}+ (void)clearArchivedFileWithName:(NSString *)filename{ NSString *rootPath = [self getAppArchivedFilesRootPath]; NSString *fullPath = [rootPath stringByAppendingPathComponent:filename]; if ([[NSFileManager defaultManager] fileExistsAtPath:fullPath]) { [[NSFileManager defaultManager] removeItemAtPath:fullPath error:nil]; }}
除了以上記錄方案,再記錄一下使用索引值對資料庫LevelDB的經曆。
當資料量並不是很大,但是又需要資料庫儲存和操作時候,索引值對資料庫是首選。源自Google的LevelDB是其中的明星。
之前遇到用戶端儲存省市區地址資料的需求,併當使用者選擇地址時候讀取相關資料。如果每次都完整讀取省市區資料,佔用記憶體既大又沒有必要,因為使用者很可能只會選擇一個省下的一個市的一個區。
如果將省市區資料拆分為若干索引值對,並且建立某種鏈式關係,就可以將資料以索引值對分散儲存於某個地方,並且快速讀取需要資料。
1.第一層級只有一個索引值對,key為固定值,value為全部省的名稱數組
2.第二層級索引值對數量為省數量,key為省名稱,value為市名稱數組
3.第三層級索引值對數量為市數量,key為“省名稱.市名稱”,value為區名稱數組
4.。。。。。
如上,資料全部以索引值對分散儲存於LevelDB中,只要知道key規則和名稱,就可以快速取到對應資料,而優秀的IO保證了效能表現。
這是之前記錄的一篇關於LevelDB的文章,可以先參考一下:http://www.cnblogs.com/A-Long-Way-Chris/p/4864573.html
編譯靜態連結庫
正好以LevelDB為案例。先前往下載C++原始碼: https://github.com/google/leveldb
使用Xcode建立靜態連結庫
1.建立項目,選擇類型
2.設定項目Build Phases,點擊地區左上方加號,選擇添加Headers Phase
3.點擊加號,添加需要公開暴露的標頭檔,然後從Project欄拖拽到Public欄
4.切換真機和模擬器,分別編譯成功後,右鍵Products目錄下的libleveldb.a,在Finder中查看
5.在終端程式中cd到該目錄,輸入如下指令,即可匯出同時支援真機和模擬器啟動並執行靜態連結庫
lipo -create Debug-iphoneos/libleveldb.a Debug-iphonesimulator/libleveldb.a -output libleveldb.a
使用命令列,通過Makefile編譯LevelDB的靜態連結庫
1.解壓下載包後,使用Visual Studio Code之類的文字編輯器開啟目錄下Makefile
2.修改Makefile中的CXXFLAGS,添加指令 -fembed-bitcode,儲存
3.在終端中,cd到LevelDB目錄,輸入指令:CXXFLAGS=-miphoneos-version-min=7.0 make PLATFORM=IOS
表示產生iOS版本的靜態連結庫,支援最低版本為7.0(該設定保證在模擬器上可以正常運行)。
如果提示permission denied,則在上述指令前加上sudo,最終為:sudo CXXFLAGS=-miphoneos-version-min=7.0 make PLATFORM=IOS,然後輸入密碼斷行符號即可。
4.說明一下,如果跳過步驟1和2,最後產生的LevelDB靜態連結庫不支援bitcode,可以看到體積相差還是比較大的,按需編譯
將.a檔案和include目錄下的標頭檔加入項目即可正常使用。
1.如果遇到提示某標頭檔找不到,請檢查項目配置中,Header Search Paths是否有配置缺失
2.如果使用不支援bitcode的版本,需要在build setting中將enable bitcode設定為NO。該設定對其他類庫要求一樣
為了便於使用OC編程,還引入了另一個類庫,對LevelDB的代碼進行了OC封裝,地址:https://github.com/matehat/Objective-LevelDB
我在base項目中,增加了LevelDBHelper工具類,進一步對調用代碼進行了封裝,操作更簡單安全。
base項目已更新:git@github.com:ALongWay/base.git