汽车公司可能用到的知识点需要马上研究的iOS接下来要研究的知识点

iOS数据存储

2021-11-22  本文已影响0人  林希品

iOS数据存储

https://www.jianshu.com/p/19fdbf81a086

如何存储数据

文件

NSUserDefaults

数据库

文件

沙盒

iOS本地化存储的数据保存在沙盒中。

获取沙盒文件:

// 获取Documents目录的路径

NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;

// 得到Document目录下的fileName文件的路径

NSString *filePath = [documentPath stringByAppendingPathComponent:fileName];

//获取Library/Caches目录路径

NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;

//获取Library/Caches目录下的fileName文件路径

NSString *filePath = [path stringByAppendingPathComponent:fileName];

//获取temp路径

NSString *tmp = NSTemporaryDirectory();

//获取temp下fileName文件的路径

NSString *filePath = [tmp stringByAppendingPathComponent:fileName];

其中:

plist

可以把字典或数组直接写入到文件中。另外,NSString、NSData、NSNumber等类型,也可以使用writeToFile:atomically:方法直接将对象写入文件中,只是Type为空。

存储

// 要保存的数据

NSDictionary *dict = [NSDictionary dictionaryWithObject:@"iOS cookbook" forKey:@"bookName"];

// 获取路径.

NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;

NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];

// 写入数据

[dict writeToFile:filePath atomically:YES];

读取

// 文件路径

NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;

NSString *filePath = [documentPath stringByAppendingPathComponent:@"test.plist"];

// 解析数据

NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];

NSString *result = dict[@"bookName"];

NSLog(@"%@", result);

NSKeyedArchiver归档 / NSKeyedUnarchiver解档

NSKeyedArchiver的三个使用场景:

// 获取归档文件路径

NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;

NSString *filePath = [documentPath stringByAppendingPathComponent:@"knight"];

// 对字符串@”test”进行归档,写入到filePath中

[NSKeyedArchiver archiveRootObject:@"test" toFile:filePath];

// 根据保存数据的路径filePath解档数据

NSString *result = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];

// 打印归档的数据

NSLog(@"%@",result);

// 获取归档路径

NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;

NSString *filePath = [documentPath stringByAppendingPathComponent:@"test"];

// 用来承载数据的NSMutableData

NSMutableData *data = [[NSMutableData alloc] init];

// 归档对象

NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];

// 将要被保存的三个数据

NSString *name = @"jack";

int age = 18;

double height = 1.80;

// 运用encodeObject:方法归档数据

[archiver encodeObject:name forKey:@"name"];

[archiver encodeInt:age forKey:@"age"];

[archiver encodeDouble:height forKey:@"height"];

// 结束归档

[archiver finishEncoding];

// 写入数据(存储数据)

[data writeToFile:filePath atomically:YES];

// NSMutableData用来承载解档出来的数据

NSMutableData *resultData = [[NSMutableData alloc] initWithContentsOfFile:filePath];

// 解档对象

NSKeyedUnarchiver *unArchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:resultData];

// 分别解档出三个数据

NSString *resultName = [unArchiver decodeObjectForKey:@"name"];

int resultAge = [unArchiver decodeIntForKey:@"age"];

double resultHeight = [unArchiver decodeDoubleForKey:@"height"];

//结束解档

[unArchiver finishDecoding];

// 打印

NSLog(@"name = %@, age = %d, height = %.2f", resultName, resultAge, resultHeight);

@interface DDAppConfigModel : NSObject <NSCoding>

@property(nonatomic, copy) NSString *paramName;

@property(nonatomic, copy) NSString *paramValue;

@property(nonatomic, copy) NSString *version;

@property(nonatomic, copy) NSString *delFlag;

@property(nonatomic, copy) NSString *type;

@end

其中:NSCoding协议有2个方法:

归档时调用这个方法,在方法中使用encodeObject:forKey:归档变量。

解档时调用这个方法,在方法中使用decodeObject:forKey读出变量。

@implementation DDAppConfigModel

{

if (self = [super init])

{

self.paramName = [aDecoder decodeObjectForKey:@"paramName"];

self.paramValue = [aDecoder decodeObjectForKey:@"paramValue"];

self.version = [aDecoder decodeObjectForKey:@"version"];

self.delFlag = [aDecoder decodeObjectForKey:@"delFlag"];

self.type = [aDecoder decodeObjectForKey:@"type"];

}

return self;

}

{

[aCoder encodeObject:self.paramName forKey:@"paramName"];

[aCoder encodeObject:self.paramValue forKey:@"paramValue"];

[aCoder encodeObject:self.version forKey:@"version"];

[aCoder encodeObject:self.delFlag forKey:@"delFlag"];

[aCoder encodeObject:self.type forKey:@"type"];

}

//类方法,运用NSKeyedArchiver归档数据

{

NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;

NSString *path = [docPath stringByAppendingPathComponent:@"DDAppConfigModel.plist"];

[NSKeyedArchiver archiveRootObject:config toFile:path];

}

//类方法,使用NSKeyedUnarchiver解档数据

{

NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;

NSString *path=[docPath stringByAppendingPathComponent:@"DDAppConfigModel.plist"];

DDAppConfigModel *config = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

return config;

}

@end

NSUserDefaults

一般使用它来进行一些设置的记录,比如用户ID,开关是否打开等设置。通过键值对的方式记录设置。

NSUserDefaults可以存储的数据类型包括:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。如果要存储其他类型,则需要转换为前面的类型,才能用NSUserDefaults存储。

// 通用型设置数据

define kSetUserDefaults(key, value) ([USER_DEFAULT setObject:value forKey:key], [USER_DEFAULT synchronize])

// 通用型获取数据

define kUserDefaults(key) [USER_DEFAULT objectForKey:key]

数据库

SQLite3

推荐参考:https://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=2649286361&idx=1&sn=78bbcda7f41a14291ad71289e4821f71&scene=0#rd

FMDB

FMDB封装了SQLite的C语言API,更加面向对象。

FMDB中的三个类:

创建数据库。

FMDatabase创建的时候需要提供一个SQLite数据库文件。我们一般提供一个.db的文件路径即可。

NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];

FMDatabase *db = [FMDatabase databaseWithPath:path];

打开数据库

在和数据库进行交互之前,我们需要先打开数据库。使用上一步拿到的数据库文件操作句柄db

if (![db open])

{

db = nil;

return;

}

创建表

使用集合语句来进行表的创建。

NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);"

             "create table bulktest2 (id integer primary key autoincrement, y text);"

             "create table bulktest3 (id integer primary key autoincrement, z text);"

             "insert into bulktest1 (x) values ('XXX');"

             "insert into bulktest2 (y) values ('YYY');"

             "insert into bulktest3 (z) values ('ZZZ');";

success = [db executeStatements:sql];

查询

使用executeQuery 来执行查询语句,FMResultSet来进行存储查询的结果。

FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"];

while ([s next]) {

//retrieve values for each record

}

可通过如下的方式将结果集里面的数据取出来。

while ([rs next])

{

if (oldIDs == nil) oldIDs = [[NSMutableArray alloc] init];

[oldIDs addObject:[rs stringForColumnIndex:0]];

}

另外,FMResultSet可以通过如下的方法将数据通过恰当的格式检索出来。

intForColumn:

longForColumn:

longLongIntForColumn:

boolForColumn:

doubleForColumn:

stringForColumn:

dateForColumn:

dataForColumn:

dataNoCopyForColumn:

UTF8StringForColumn:

objectForColumn:

更新

通过调用- (BOOL)executeUpdate:(NSString*)sql withParameterDictionary:(NSDictionary *)arguments执行插入、删除或者更新数据。

插入数据或者更新数据。

NSString *sql = ![rs next] ? INSERT_STATEMENT : UPDATE_STATEMENT;

BOOL success = [db executeUpdate:sql withParameterDictionary:[self pr_toDBDictionary]];

if (!success)

{

STLogDBLastError(db);

result = NO;

return;

}

删除数据

BOOL success = [db executeUpdate:@"DELETE FROM STAppLog WHERE userID = ? AND appLogID = ?;", self.userID, self.appLogID];

if (!success)

{

STLogDBLastError(db);

result = NO;

return;

}

多线程操作数据库

FMDatabaseQueue内部实现了FMDatabase相关的创建及管理。

FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];

[queue inDatabase:^(FMDatabase *db) {

[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];

[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];

[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];

FMResultSet *rs = [db executeQuery:@"select * from foo"];

while ([rs next]) {

    …

}

}];

事务

参考:https://blog.csdn.net/x32sky/article/details/45531229

[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {

[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];

[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];

[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];

if (whoopsSomethingWrongHappened) {

    *rollback = YES;

    return;

}

// etc ...

}];

关闭数据库

[db close];

FMDatabase源码解析

管理数据库。需要指定一个文件用来存储数据。例如:xxx/xxx/evian.db

然后,调用如下方法打开数据库:

if SQLITE_VERSION_NUMBER >= 3005000

if (_db) {

    return YES;

}

int err = sqlite3_open_v2([self sqlitePath], (sqlite3**)&_db, flags, [vfsName UTF8String]);

if(err != SQLITE_OK) {

    NSLog(@"error opening!: %d", err);

    return NO;

}

if (_maxBusyRetryTimeInterval > 0.0) {

    // set the handler

    [self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];

}

return YES;

else

NSLog(@"openWithFlags requires SQLite 3.5");

return NO;

endif

}

vfs是虚拟文件系统的简称,主要是用来统一不同平台操作系统文件的访问,屏蔽底层硬件介质,提供统一的访问接口。

我们可以看到通过 sqlite3_open_v2这个函数,在指定的path上面打开了数据库的句柄:_db.

后面,我们就可以拿这个句柄去访问数据库了,比如创建库、创建表、插入数据、更新数据、查询数据等。

FMResultSet源码解析

下面我们来看看FMDatabase最主要的一个接口:

}

FMDatabaseQueue源码解析

ifndef NDEBUG

/* Get the currently executing queue (which should probably be nil, but in theory could be another DB queue

 * and then check it against self to make sure we're not about to deadlock. */

FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);

assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");

endif

FMDBRetain(self);

dispatch_sync(_queue, ^() {

    FMDatabase *db = [self database];

    block(db);

    if ([db hasOpenResultSets]) {

        NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]");

if defined(DEBUG) && DEBUG

        NSSet *openSetCopy = FMDBReturnAutoreleased([[db valueForKey:@"_openResultSets"] copy]);

        for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) {

            FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue];

            NSLog(@"query: '%@'", [rs query]);

        }

endif

    }

});

FMDBRelease(self);

}

如何使用呢,请看如下调用:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

// 1、查询所有的日志。

NSArray *arrLog = [self dd_fetchAppLogs];

});

{

__block NSMutableArray *appLogs = nil;

WSELF;

[[FMDatabaseQueue sharedInstance] inDatabase:^(FMDatabase *db) {

    SSELF;

    FMResultSet *rs = [db executeQuery:@"SELECT * FROM STAppLog;"];

    @onExit {

        [rs close];

    };

    if (rs == nil)

    {

        STLogDBLastError(db);

        return;

    }

    while ([rs next])

    {

        @autoreleasepool {

            if (appLogs == nil) appLogs = [[NSMutableArray alloc] init];

            STAppLog *appLog = [self mj_objectWithKeyValues:rs.resultDictionary];

            [appLogs addObject:appLog];

        }

    }

}];

return appLogs;

}

通过源码可以知道,该查询操作将在FMDB内部创建的队列中执行子线程的查询操作。

CoreData

参考:https://www.jianshu.com/p/d9ee92cd3483

创建数据库表

在MesaSQLite设计器中创建表结构,然后将生成的sql复制出来使用。这样可以避免手敲代码产生的错误。

将sql保存成文件,然后放到xcode工程中。

coreData.png

数据模型管理类NSManagedObjectModel

通过NSManagedObjectModel,可以将创建的数据模型文件读取为模型管理类对象。实体类似于数据库中的表结构。

持久化存储协调者类NSPersistentStoreCoordinator

NSPersistentStoreCoordinator建立数据模型与本地文件或数据库之间的联系,通过它将本地数据读入内存或者将修改过的临时数据进行持久化的保存。

数据对象管理上下文NSManagedObjectContext

NSManagedObjectContext是进行数据管理的核心类,我们通过这个类来进行数据的增删改查等操作。

如何使用

一般我们需要在工程里创建一个xxx.xcdatamodeld文件。然后在这个文件里面创建表

coreData1.png

创建NSManagedObjectModel对象

首先、我们需要知道数据模型文件在哪,通过数据模型文件加载NSManagedObjectModel。

{

if (_managedObjectModel != nil) {

    return _managedObjectModel;

}

NSURL *modelURL = [DDBITrackKitBUNDLE URLForResource:@"Model" withExtension:@"momd"];

_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

return _managedObjectModel;

}

创建NSPersistentStoreCoordinator对象

PersistentStoreCoordinator对象的创建需要用到ManagedObjectModel对象,根据objectModel的模型结构创建持久化的本地存储。同时PersistentStoreCoordinator对象需要知道数据库文件在哪里,以便打开对一个的数据库。

{

if (_persistentStoreCoordinator != nil) {

    return _persistentStoreCoordinator;

}

// 指定一个数据库文件路径

NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"DDBITrackKit.sqlite"];

NSError *error = nil;

// 通过ManagedObjectModel对象创建NSPersistentStoreCoordinator对象。

_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];

// 指定底层的存储方式为SQLite数据库。

if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {

    //

    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);

    abort();

}

return _persistentStoreCoordinator;

}

创建NSManagedObjectContext对象。

操作数据需要用到Object的句柄,每个句柄管理了对应的ObjectModel对象。一般我们会创建一个主句柄。然后使用子句柄执行多线程操作。

需要注意的点是:各个线程创建的子句柄相互之间是不能直接进行通信的,后果是有可能会崩溃,以及查询不出数据、或者查询出来错误的数据。如果需要通信,那么需要把实体(Entity)的ObjectID传递给另一个线程,然后这个线程里的context执行对应的查询,然后再改变查询出来的object的数据。比较繁琐。

保存数据的时候。可以调用子句柄的save:函数,然后会在我们的通知中心里拿到这个数据改变的通知。

创建主句柄

{

if (_mainManagedObjectContext != nil) {

    return _mainManagedObjectContext;

}

_mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

_mainManagedObjectContext.persistentStoreCoordinator = [self persistentStoreCoordinator];

return _mainManagedObjectContext;

}

创建多线程的子句柄

{

// 设置一个 persistent store coordinator 和两个独立的 contexts 被证明了是在后台处理 Core Data 的好方法。

NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];

context.persistentStoreCoordinator = [self persistentStoreCoordinator];

return context;

}

创建数据更改的通知。

[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification *note) {

    NSManagedObjectContext *moc = self.mainManagedObjectContext;

    if (note.object != moc) {

        [moc performBlock:^{

            [moc mergeChangesFromContextDidSaveNotification:note];

        }];

    }

}];

查询数据

通过NSEntityDescription来查询所需要的实体对象。

{

id object = nil;

NSString *entityName = NSStringFromClass(self);

if (identifier)

{

    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:entityName];

    fetchRequest.predicate = [NSPredicate predicateWithFormat:@"appLogID = %@", identifier];

    fetchRequest.fetchLimit = 1;

    fetchRequest.returnsObjectsAsFaults = NO;

    NSError *error = nil;

    id object = [[context executeFetchRequest:fetchRequest error:&error] lastObject];

    if (object == nil) {

        object = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];

    }

}

else

{

    object = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];

}

return object;

}

或:

{

NSManagedObjectContext *context = [DatabaseHelper shareInstance].store.privateContext;

NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"DDBILog"];

[request setReturnsObjectsAsFaults:NO];

NSError *error;

NSArray *arrResult = [context executeFetchRequest:request error:&error];

return arrResult;

}

更新数据

先查询出来对象,然后更新对象响应的字段,最后调用save函数进行保存

{

DDBILog *tAppLog = [DDBILog findOrCreateWithIdentifier:self.appLogID inContext:localContext];

tAppLog.accuracy         = self.accuracy;

[localContext save:NULL];

if (completion) {

    completion(YES, nil);

}

}

删除对象

{

NSManagedObjectContext *localContext = [DatabaseHelper shareInstance].store.privateContext;

[self dd_deleteWithContext:localContext completion:completion];

}

{

[self MR_deleteEntityInContext:context];

[context save:NULL];

}

mr 的内部实现

{

NSError *error = nil;

NSManagedObject *entityInContext = [context existingObjectWithID:[self objectID] error:&error];

[MagicalRecord handleErrors:error];

if (entityInContext) {

    [context deleteObject:entityInContext];

}

return YES;

}

通过这里可以看到,线程之间传递数据是通过objectID查询出来对应的实体的。然后调用context的deleteObject函数将这个实体删除。

作者:来鸿去燕

链接:https://www.jianshu.com/p/19fdbf81a086

来源:简书

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

上一篇 下一篇

猜你喜欢

热点阅读