寫在前面:iOS本地持久化存儲的路徑
Documents: 最常用的目錄瓣窄,存放重要的數據靶壮,iTunes同步時會備份該目錄
Library/Caches: 一般存放體積大从诲,不重要的數據,iTunes同步時不會備份該目錄
Library/Preferences: 存放用戶的偏好設置契耿,iTunes同步時會備份該目錄
tmp: 用于存放臨時文件瞒大,在程序未運行時可能會刪除該文件夾中的數據,iTunes同步時不會備份該目錄
存儲方式:NSUserDefaults
搪桂、Plist
透敌、NSKeyedArchiver
、SQLite3
踢械、Core Data
拙泽、Keychain
、FMDB
一裸燎、NSUserDefaults 方式存儲
1.1 寫入
NSUserDefaults *login = [NSUserDefaults standardUserDefaults];
[login setObject:self.passwordField.text forKey:@"token"];
[login synchronize];
1.2 讀取
NSUserDefaults *login = [NSUserDefaults standardUserDefaults];
NSString *str = [login objectForKey:@"token"];
1.只能存儲OC常用數據類型(NSString顾瞻、NSDictionary、NSArray德绿、NSData荷荤、NSNumber等類型)而不能直接存儲自定義數據退渗。
2.鍵值對存儲,直接指定存儲類型蕴纳。
二会油、plist 方式存儲
2.1 寫入
#pragma mark - 保存到本地
- (void)saveToLocale {
[self.view endEditing:YES];
if (!self.nameField.text.length) {
[TipUtils showToast:self.view message:@"應用名稱不能為空"];
return;
}
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0]stringByAppendingPathComponent:@"password.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL Exists = [fileManager fileExistsAtPath:path];
if (!Exists) {
_serectArray = [[NSMutableArray alloc] init];
} else {
_serectArray = [[NSMutableArray alloc] initWithContentsOfFile:path];
}
// 這里是修改更新數據
for (int i=0;i <self.serectArray.count;i++) {
if ([self.myPassword.timeID isEqualToString:self.serectArray[i][@"timeID"]]) {
self.serectArray[i][@"name"] = self.nameField.text;
[self.serectArray writeToFile:path atomically:YES];
if ([self.serectArray writeToFile:path atomically:YES]) {
[TipUtils showToast:self.view message:@"保存成功"];
[self performSelector:@selector(delayMethod) withObject:nil afterDelay:1.0f];
} else {
NSLog(@"保存失敗");
}
return;
}
}
NSDictionary *dict = [NSDictionary dictionary];
dict = @{@"timeID":[self getItemID], @"name":self.nameField.text};
[self.serectArray addObject:dict];
[self.serectArray writeToFile:path atomically:YES];
if ([self.serectArray writeToFile:path atomically:YES]) {
[TipUtils showToast:self.view message:@"保存成功"];
[self performSelector:@selector(delayMethod) withObject:nil afterDelay:1.0f];
NSLog(@"存儲的數據有:%@",self.serectArray);
} else {
NSLog(@"保存失敗");
}
}
#pragma mark - 延時函數
- (void)delayMethod {
[self.navigationController popViewControllerAnimated:YES];
}
2.2 讀取
#pragma mark - 讀取數據
- (void)readDataFromPlist {
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0]stringByAppendingPathComponent:@"password.plist"];
NSLog(@"存儲路徑-%@",path);
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL Exists = [fileManager fileExistsAtPath:path];
if (!Exists) {
_serectArray = [[NSMutableArray alloc] init];
} else {
_serectArray = [[NSMutableArray alloc] initWithContentsOfFile:path];
}
}
1.只能存儲OC常用數據類型(NSString、NSDictionary古毛、NSArray翻翩、NSData、NSNumber等類型)而不能直接存儲自定義模型對象稻薇。
· 如果想用對象嫂冻,只能再用明杰的框架進行字典轉模型了床绪。
· 或者想存儲自定義模型對象 -> 只能將自定義模型對象轉換為字典存儲断凶;
2.系統(tǒng)的plist文件,只能讀取谎痢,不行寫入案狠,自定義的可讀可寫
3.plist文件存儲的位置服傍,但一般存在Documents中
4.如果存儲圖片路徑的話,一定要存儲相對位置骂铁,因為每次啟動APP,plist文件的路徑就會變化吹零,自然圖片的位置也就變化了。
三拉庵、NSKeyedArchiver歸檔(NSCoding)
- 歸檔解檔最大的好處在于可以存儲自定義對象數據
- 歸檔解檔要注意ios版本
- 新建一個數據模型類
Person
#import <Foundation/Foundation.h>
@interface Person : NSObject<NSCoding>
@property(nonatomic, copy) NSString *name;
@end
#import "Person.h"
@implementation Person
-(id)init {
if (self == nil) {
self = [super init];
}
return self;
}
- (void)encodeWithCoder:(nonnull NSCoder *)aCoder {
[aCoder encodeObject:self.name forKey:@"name"];
}
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder {
if (self = [super init]) {
self.name = [aDecoder decodeObjectForKey:@"name"];
}
return self;
}
@end
- 新建一個歸檔解檔通用工具類
ArchiveTools
#import <Foundation/Foundation.h>
@interface ArchiveTools : NSObject
+ (BOOL)archiveObject:(id)object prefix:(NSString *)prefix;
+ (id)unarchiveClass:(Class)class prefix:(NSString *)prefix;
+ (NSString *)getPathWithPrefix: (NSString *)prefix;
@end
#import "ArchiveTools.h"
@implementation ArchiveTools
#pragma mark - 歸檔解檔
+ (BOOL)archiveObject:(id)object prefix:(NSString *)prefix {
if (!object) {
return NO;
}
NSError *error;
if (@available(iOS 11.0, *)) {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object
requiringSecureCoding:YES
error:&error];
if (error)
return NO;
[data writeToFile:[self getPathWithPrefix:prefix] atomically:YES];
} else {
NSData*data = [NSKeyedArchiver archivedDataWithRootObject:object];
[data writeToFile:[self getPathWithPrefix:prefix] atomically:YES];
}
return YES;
}
+ (id)unarchiveClass:(Class)class prefix:(NSString *)prefix {
NSError *error;
NSData *data = [[NSData alloc] initWithContentsOfFile:[self getPathWithPrefix:prefix]];
//會調用對象的initWithCoder方法
if (@available(iOS 11.0, *)) {
id content = [NSKeyedUnarchiver unarchivedObjectOfClass:class fromData:data error:&error];
if (error) {
return nil;
}
return content;
} else {
id content = [NSKeyedUnarchiver unarchiveObjectWithData:data];
return content;
}
}
#pragma mark - 存放文件的路徑
+ (NSString *)getPathWithPrefix: (NSString *)prefix {
// document路徑
NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0];
// 自定義一個文件夾
NSString *filePathFolder = [documentPath stringsByAppendingPaths:@[@"archiveTemp"]].firstObject;
if (![[NSFileManager defaultManager] fileExistsAtPath:filePathFolder]) {
[[NSFileManager defaultManager] createDirectoryAtPath:filePathFolder withIntermediateDirectories:YES attributes:nil error:nil];
}
NSString *path = [NSString stringWithFormat:@"%@/%@.archive",filePathFolder,prefix];
NSLog(@"%@",path);
return path;
}
3.調用使用
#import "ViewController.h"
#import "Person.h"
#import "ArchiveTools.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc] init];
person.name = @"I am Lili";
// 存儲
BOOL isSuccess = [ArchiveTools archiveObject:person prefix:NSStringFromClass(person.class)];
NSLog(@"啦啦-%@",NSStringFromClass(person.class));
if (isSuccess) {
NSLog(@"存儲成功");
} else {
NSLog(@"存儲失敗");
}
// 讀取
Person *content = [ArchiveTools unarchiveClass:Person.class prefix:NSStringFromClass(Person.class)];
NSLog(@"內容是-%@",content);
NSLog(@"具體-%@", content.name);
//刪除歸檔文件
NSFileManager *defaultManager = [NSFileManager defaultManager];
if ([defaultManager isDeletableFileAtPath:[ArchiveTools getPathWithPrefix:NSStringFromClass(Person.class)]]) {
[defaultManager removeItemAtPath:[ArchiveTools getPathWithPrefix:NSStringFromClass(Person.class)] error:nil];
}
}
四灿椅、SQLite3
1. 在項目中導入libsqlite3.0.tbd框架
2.使用數據庫
#import "ViewController.h"
#import <sqlite3.h>
@interface ViewController ()
@property(nonatomic, assign) int res;
@end
@implementation ViewController
{
sqlite3 *db; // 聲明對象
}
- (void)viewDidLoad {
[super viewDidLoad];
[self createDB];
[self createTable];
[self insertData];
[self selectData];
// 關閉數據庫
sqlite3_close(db);
}
#pragma mark - 數據庫相關操作
- (void)createDB {
// 創(chuàng)建數據庫路徑
NSString *dbPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0]stringByAppendingPathComponent:@"test.sqlite"];
NSLog(@"數據庫路徑-%@",dbPath);
// 創(chuàng)建或者打開數據庫
const char *p = [dbPath UTF8String];
int res = sqlite3_open(p, &db);
self.res = res;
}
// 一、創(chuàng)建表格
- (void)createTable {
if(self.res == SQLITE_OK) {
NSLog(@"數據庫成功打開");
NSString *sql = @"create table if not exists temps (t_id integer primary key autoincrement, t_name varchar(20))";
if ([self execNoQueryWithSQL:sql]) {
NSLog(@"創(chuàng)建表格成功");
} else {
NSLog(@"創(chuàng)建表格失敗");
}
} else {
NSLog(@"數據庫未正常打開-%d",self.res);
}
}
// 二名段、插入數據
- (void)insertData {
if (self.res == SQLITE_OK) {
NSString *insert_sql = @"insert into temps (t_name) values('banala')";
if ([self execNoQueryWithSQL:insert_sql]) {
NSLog(@"成功插入數據");
} else {
NSLog(@"插入數據失敗");
}
} else {
NSLog(@"數據庫未正常打開-%d",self.res);
}
}
// 三、刪除數據
- (void)deleteData {
if (self.res == SQLITE_OK) {
NSString *delete_sql = @"delete from temps where t_id=2";
if ([self execNoQueryWithSQL:delete_sql]) {
NSLog(@"成功刪除數據");
} else {
NSLog(@"刪除數據失敗");
}
} else {
NSLog(@"數據庫未正常打開-%d",self.res);
}
}
// 四泣懊、修改數據
- (void)updataData {
if (self.res == SQLITE_OK) {
NSString *update_sql = @"update temps set t_name='ios' where t_id=1";
if ([self execNoQueryWithSQL:update_sql]) {
NSLog(@"成功更新數據");
} else {
NSLog(@"更新數據失敗");
}
} else {
NSLog(@"數據庫未正常打開-%d",self.res);
}
}
// 五伸辟、查詢簡單數據,無參數
- (void)selectData {
if (self.res == SQLITE_OK) {
// NSString *select_sql1 = @"select * from temps where t_id=1";
NSString *select_sql1 = @"select * from temps";
sqlite3_stmt *stmt1 = [self execQueryWithSQL:select_sql1];
while (sqlite3_step(stmt1) == SQLITE_ROW) {
int t_id = sqlite3_column_int(stmt1, 0);
const unsigned char *t_name = sqlite3_column_text(stmt1, 1);
NSString *name = [NSString stringWithUTF8String:(char *)t_name];
NSLog(@"%i %@",t_id,name);
}
// 釋放stmt statement
sqlite3_finalize(stmt1);
} else {
NSLog(@"數據庫未正常打開-%d",self.res);
}
}
// 六馍刮、查詢數據2 參數化的sql語句 查找 id>2 并且名字以p開頭的 用 信夫?占位
- (void)selectDataWithParams {
if (self.res == SQLITE_OK) {
int seachId2 = 1;
NSString *seach_name = @"p%";
NSString *seach_sql = @"select * from temps where t_id>? and t_name like ?";
sqlite3_stmt *stmt6 = [self execQueryWithSQL:seach_sql andWithParams:@[[NSNumber numberWithInt:seachId2],seach_name]];
//準備執(zhí)行(相當于點擊run query),執(zhí)行的時候是一行一行的執(zhí)行
while (sqlite3_step(stmt6) == SQLITE_ROW) {
//按照當前列的類型選數據,列數從0開始
int t_id = sqlite3_column_int(stmt6, 0);
const unsigned char *t_name = sqlite3_column_text(stmt6, 1);
NSString *name = [NSString stringWithUTF8String:(char*)t_name];
NSLog(@"..>>>>>>...%i %@",t_id,name);
}
sqlite3_finalize(stmt6);
} else {
NSLog(@"數據庫未正常打開-%d",self.res);
}
}
#pragma mark - 執(zhí)行除查詢以外的操作
- (BOOL)execNoQueryWithSQL:(NSString *)sql {
/*
執(zhí)行
參數1:sqlite3 對象
參數2:c形式的 sql語句
參數3:回調函數
參數4:回調函數的參數
參數5:錯誤信息(可以char類型指針接受錯誤信息卡啰,用來查錯使用)
*/
char *error;
int result = sqlite3_exec(db, [sql UTF8String], NULL, NULL, &error);
if (result == SQLITE_OK) {
return YES;
} else {
NSLog(@"%s",error);
}
return NO;
}
#pragma mark - 返回查詢結果集静稻,無參數
-(sqlite3_stmt *)execQueryWithSQL:(NSString *)sql {
sqlite3_stmt *stmt;
/*
準備執(zhí)行查詢的sql語句 (相當于把查詢語句寫好)
參數3:sql語句長度,通常用-1表示(系統(tǒng)會自動計算)匈辱,也可以用strlength函數計算
參數4:sql_stmt對象 (執(zhí)行的對象)
參數5:未執(zhí)行的sql語句
*/
int pre_res = sqlite3_prepare_v2(db, [sql UTF8String], -1, &stmt, NULL);
if (pre_res == SQLITE_OK) {
return stmt;
}
return NULL;
}
#pragma mark - 返回查詢結果集m振湾,有參數
-(sqlite3_stmt *)execQueryWithSQL:(NSString *)sql andWithParams:(NSArray *)params{
sqlite3_stmt *stmt;
int pre_res = sqlite3_prepare_v2(db, [sql UTF8String], -1, &stmt, NULL);
if (pre_res == SQLITE_OK) {
if (params!=nil) {
for (int i = 0 ; i<params.count; i++) {
id obj = params[i];
//綁定的數據類型可能為NSString或者NSNumber,或者數據為空亡脸,分別判斷
if (obj == nil) {
// 數據為空
sqlite3_bind_null(stmt, i+1);
} else if ([obj respondsToSelector:@selector(objCType)]) {
//當前的綁定的數據類型位NSNumber
//NSNumber判斷包裝的是int押搪?longInt树酪?shortInt?float?double?
/*
strstr(參數1,參數2) (strstr() c中函數搜索一個字符串在另一個字符串中的第一次出現大州,則該函數返回第一次匹配的字符串的地址续语,找不到返回NULL)
判斷參數1中的字符在參數2的字符串char*中出現的索引
[obj objCType] 如果obj是int返回字符串i
*/
if (strstr("ilsILS", [obj objCType])) {
/*
綁定參數 如果有where
參數1:sqlite_stmt對象 (statement結果集)
參數2:占位符索引 從1開始
參數3:替代占位符的真實參數
*/
sqlite3_bind_int(stmt, i+1, [obj intValue]);
} else if (strstr("fdFD", [obj objCType])){
sqlite3_bind_double(stmt, i+1, [obj doubleValue]);
} else {
stmt = nil;
}
} else if ([obj respondsToSelector:@selector(UTF8String)]) {
//當前的綁定的數據類型為NSString 判斷是否有UTF8String方法
//用bind替換占位符 索引從1開始
sqlite3_bind_text(stmt, i+1, [obj UTF8String], -1, NULL);
} else {
stmt = nil;
}
}
}
return stmt;
}
return NULL;
}
@end
五、Core Data
Core Data是iOS5之后才出現的一個框架厦画,提供了直接使用SQLite數據庫的大部分靈活性疮茄,它提供了對象-關系映射(ORM)的功能,即能夠將OC對象轉化成數據根暑,保存在SQLite數據庫文件中力试,也能夠將保存在數據庫中的數據還原成OC對象,通過CoreData管理應用程序的數據模型购裙,可以極大程度減少需要編寫的代碼數量懂版!
- 廢話不多說,直接上代碼躏率!
//創(chuàng)建數據庫
- (void)createSqlite{
//1躯畴、創(chuàng)建模型對象
//獲取模型路徑
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreData__" withExtension:@"momd"];
//根據模型文件創(chuàng)建模型對象
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
//2、創(chuàng)建持久化存儲助理:數據庫
//利用模型對象創(chuàng)建助理對象
NSPersistentStoreCoordinator *store = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
//數據庫的名稱和路徑
NSString *docStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *sqlPath = [docStr stringByAppendingPathComponent:@"coreData.sqlite"];
NSLog(@"數據庫 path = %@", sqlPath);
NSURL *sqlUrl = [NSURL fileURLWithPath:sqlPath];
NSError *error = nil;
//設置數據庫相關信息 添加一個持久化存儲庫并設置存儲類型和路徑薇芝,NSSQLiteStoreType:SQLite作為存儲庫
[store addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sqlUrl options:nil error:&error];
if (error) {
NSLog(@"添加數據庫失敗:%@",error);
} else {
NSLog(@"添加數據庫成功");
}
//3蓬抄、創(chuàng)建上下文 保存信息 操作數據庫
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
//關聯(lián)持久化助理
context.persistentStoreCoordinator = store;
_context = context;
}
六、Keychain(SQLite API進行封裝的庫)
- Keychain存儲也并不是絕對安全夯到,越獄設備可以拿到
- 程序卸載還存在
- 對于每個應用來說嚷缭,keychain都有兩個訪問區(qū),私有區(qū)和公共區(qū)
私有區(qū)是一個閉合的存儲區(qū)域耍贾,每個應用只能操作自己的私有區(qū)阅爽,本應用存儲的任何數據對其他程序不可見,其他程序也沒有權限訪問這個私有區(qū)荐开。(可以理解為存在鑰匙串的沙盒)付翁。
公共區(qū),apple提供給同一個開發(fā)者賬號開發(fā)的多個app之間的一個數據共享模塊』翁現在只局限于同一個開發(fā)者賬號下的不同app之間數據共享百侧。這個區(qū)域是獨立于私有區(qū)的另外一個數據存儲空間。實現多個應用間共同訪問一些數據能扒。
缺陷:Keychain變化的幾種情況
1.越獄機
2.部分操作系統(tǒng)bug
3.應用卸載后升級iOS系統(tǒng)
七佣渴、第三方FMDB,BGFMDB
- 下載FMDB框架初斑,程序導入sqlite3.0框架
- 廢話不多說辛润,上代碼
#import "ViewController.h"
#import "FMDB.h"
#import "Person.h"
@interface ViewController ()
{
FMDatabase *db;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self createDB];
[self deleteData];
[self queryData];
}
-(void)createDB {
// 1.
NSString *docuPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *dbPath = [docuPath stringByAppendingPathComponent:@"test.db"];
NSLog(@"!!!dbPath = %@",dbPath);
//2.創(chuàng)建對應路徑下數據庫
db = [FMDatabase databaseWithPath:dbPath];
//3.在數據庫中進行增刪改查操作時,需要判斷數據庫是否open
[db open];
if (![db open]) {
NSLog(@"db open fail");
return;
}
//4.數據庫中創(chuàng)建表(可創(chuàng)建多張)
NSString *sql = @"create table if not exists t_student ('ID' INTEGER PRIMARY KEY AUTOINCREMENT,'name' TEXT NOT NULL, 'phone' TEXT NOT NULL,'score' INTEGER NOT NULL)";
//5.執(zhí)行更新操作 此處database直接操作见秤,不考慮多線程問題频蛔,多線程問題灵迫,用FMDatabaseQueue 每次數據庫操作之后都會返回bool數值,YES晦溪,表示success瀑粥,NO,表示fail,可以通過 @see lastError @see lastErrorCode @see lastErrorMessage
BOOL result = [db executeUpdate:sql];
if (result) {
NSLog(@"create table success");
}
[db close];
}
- (void)insertData {
[db open];
// 插入
BOOL result2 = [db executeUpdate:@"insert into 't_student'(ID,name,phone,score) values(?,?,?,?)" withArgumentsInArray:@[@113,@"x3",@"13",@53]];
if (result2) {
NSLog(@"insert into 't_studet' success");
[self showAlertWithTitle:@"insert success" message:nil person:nil];
} else {
[self showAlertWithTitle:[db lastError].description message:nil person:nil];
}
[db close];
}
-(void)deleteData{
[db open];
BOOL result = [db executeUpdate:@"delete from 't_student' where ID = ?" withArgumentsInArray:@[@153]];
if (result) {
NSLog(@"delete from 't_student' success");
[self showAlertWithTitle:@"delete success" message:nil person:nil];
} else {
[self showAlertWithTitle:[db lastError].description message:nil person:nil];
}
[db close];
}
-(void)updateData{
[db open];
BOOL result = [db executeUpdate:@"update 't_student' set ID = ? where name = ?" withArgumentsInArray:@[@153,@"x3"]];
if (result) {
NSLog(@"update 't_student' success");
[self showAlertWithTitle:@"update success" message:nil person:nil];
} else {
[self showAlertWithTitle:[db lastError].description message:nil person:nil];
}
[db close];
}
-(void)queryData {
[db open];
FMResultSet *result = [db executeQuery:@"select * from 't_student' where ID = ?" withArgumentsInArray:@[@153]];
NSMutableArray *arr = [NSMutableArray array];
while ([result next]) {
Person *person = [Person new];
person.ID = [result intForColumn:@"ID"];
person.name = [result stringForColumn:@"name"];
person.phone = [result stringForColumn:@"phone"];
person.score = [result intForColumn:@"score"];
[arr addObject:person];
NSLog(@"從數據庫查詢到的人員 %d-%@-%@-%d",person.ID, person.name,person.phone,person.score);
[self showAlertWithTitle:@"query success" message:nil person:person];
}
}
-(void)showAlertWithTitle:(NSString *)title
message:(NSString *)message
person:(Person *)person
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"sure" style:UIAlertActionStyleDefault handler:nil];
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.text = person.name ? person.name : @"other";
}];
[alert addAction:cancelAction];
[self presentViewController:alert animated:YES completion:^{
}];
}
@end