iOS经验总结

iOS-保存数据

2017-08-21  本文已影响0人  树下敲代码的超人

技 术 文 章 / 超 人


沙盒中持久化保存数据方案

从简单到复杂

沙盒说明

NSSearchPathForDirectoriesInDomains方法说明,用来查找文件路径
参数1:指定收索路径名称
typedef NS_ENUM(NSUInteger, NSSearchPathDirectory) {
NSApplicationDirectory = 1, // supported applications (Applications)
NSDemoApplicationDirectory, // unsupported applications, demonstration versions (Demos)
NSDeveloperApplicationDirectory, // developer applications (Developer/Applications). DEPRECATED - there is no one single Developer directory.
NSAdminApplicationDirectory, // system and network administration applications (Administration)
NSLibraryDirectory, // various documentation, support, and configuration files, resources (Library)
NSDeveloperDirectory, // developer resources (Developer) DEPRECATED - there is no one single Developer directory.
NSUserDirectory, // user home directories (Users)
NSDocumentationDirectory, // documentation (Documentation)
NSDocumentDirectory, // documents (Documents)
NSCoreServiceDirectory, // location of CoreServices directory (System/Library/CoreServices)
NSAutosavedInformationDirectory API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) = 11, // location of autosaved documents (Documents/Autosaved)
NSDesktopDirectory = 12, // location of user's desktop
NSCachesDirectory = 13, // location of discardable cache files (Library/Caches)
NSApplicationSupportDirectory = 14, // location of application support files (plug-ins, etc) (Library/Application Support)
NSDownloadsDirectory API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) = 15, // location of the user's "Downloads" directory
NSInputMethodsDirectory API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) = 16, // input methods (Library/Input Methods)
NSMoviesDirectory API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) = 17, // location of user's Movies directory (~/Movies)
NSMusicDirectory API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) = 18, // location of user's Music directory (~/Music)
NSPicturesDirectory API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) = 19, // location of user's Pictures directory (~/Pictures)
NSPrinterDescriptionDirectory API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) = 20, // location of system's PPDs directory (Library/Printers/PPDs)
NSSharedPublicDirectory API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) = 21, // location of user's Public sharing directory (~/Public)
NSPreferencePanesDirectory API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) = 22, // location of the PreferencePanes directory for use with System Preferences (Library/PreferencePanes)
NSApplicationScriptsDirectory NS_ENUM_AVAILABLE(10_8, NA) = 23, // location of the user scripts folder for the calling application (~/Library/Application Scripts/code-signing-id)
NSItemReplacementDirectory API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) = 99, // For use with NSFileManager's URLForDirectory:inDomain:appropriateForURL:create:error:
NSAllApplicationsDirectory = 100, // all directories where applications can occur
NSAllLibrariesDirectory = 101, // all directories where resources can occur
NSTrashDirectory API_AVAILABLE(macos(10.8), ios(11.0)) API_UNAVAILABLE(watchos, tvos) = 102 // location of Trash directory
};
参数2:查找的目录类型
enum {
NSUserDomainMask = 1, ///< 用户主目录
NSLocalDomainMask = 2, ///< 当前机器
NSNetworkDomainMask = 4, ///< 网络中可见的主机
NSSystemDomainMask = 8, ///< 系统目录,不可修改(/System)
NSAllDomainsMask = 0x0ffff, ///< 所有
};
参数3:是否显示完整路径,YES为展开后完整路径

//获取Documents路径
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

NSString *documentsPath = [paths objectAtIndex:0];

*  Library :

//获取Library路径
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *libraryPath = [paths objectAtIndex:0];

**  Caches:保存应用运行时生成的需要持久化的数据,比如SDWebImage把缓存的图片就存放到该目录下。当程序退出后,该目录保存的文件一直存在。一般存储体积大、不需要备份的非重要数据。iTunes同步设备时不会备份该目录。

//获取Caches路径
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cachesPath = [paths objectAtIndex:0];

**  Preferences:保存应用的所有偏好设置,iOSSettings(设置)应用会在该目录中查找应用的设置信息。iTunes同步设备时会备份该目录

*  tmp :保存应用运行时所需的临时数据,使用完毕后再将相应的文件从该目录删除。当手机内存爆满时,就算应用没有运行时,系统会清除所有应用该目录下的文件。iTunes同步设备时不会备份该目录。获取tmp路径方法NSTemporaryDirectory();


1. NSUserDefaults(最简单)

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
//保存值(key值同名的时候会覆盖)  
[defaults setObject:@"用户名" forKey:@"Key名称"];
//立即保存
[defaults synchronize];
//取值
NSString *username = [defaults objectForKey:@"Key名称];
同样,保存还有一些方法,比如:
//保存NSInteger
[defaults setInteger:(NSInteger) forKey:(nonnull NSString *)];
//保存BOOL
[defaults setBool:(BOOL) forKey:(nonnull NSString *)];
//保存NSURL
[defaults setURL:(nullable NSURL *) forKey:(nonnull NSString *)];
//保存float
[defaults setFloat:(float) forKey:(nonnull NSString *)];
//保存double
[defaults setDouble:(double) forKey:(nonnull NSString *)];
取值另外方法:
//取值
[defaults integerForKey:(nonnull NSString *)];
[defaults boolForKey:(nonnull NSString *)];
[defaults URLForKey:(nonnull NSString *)];
[defaults floatForKey:(nonnull NSString *)];
[defaults doubleForKey:(nonnull NSString *)];
删除方法:
//删除指定key的数据
[defaults removeObjectForKey:(nonnull NSString *)];

synchronize。立即保存,苹果文档这么说:

Writes any modifications to the persistent domains to disk and updates all unmodified persistent domains to what is on disk.
Return Value YES if the data was saved successfully to disk, otherwise NO.(大概意思是,使用synchronize后会立即把所有执行了存值方法但未在内存中修改值的值修改。该方法返回YES表示修改成功,NO表示修改失败)


2. plist文件保存(简单实用)

其实NSUserDefaults本身也是保存的一份plist文件在Library下的Preferences文件夹中

/* 例如保存在Documents路径下 */
//Documents文件路径
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
//拼接Documents+文件名,plistName就是文件名字。比如@"data.plist"
NSString *filePath = [self.path stringByAppendingPathComponent:plistName];
//把字典写入到plist文件,比如文件path为:~/Documents/data.plist
[dictionary writeToFile:path atomically:YES];
//把数组写入到plist文件中
[array writeToFile:path atomically:YES];
NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfURL:[NSURL fileURLWithPath:(nonnull NSString *)]];
NSDictionary *dictionary =  [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:(nullable NSString *) ofType:(nullable NSString *)]];
NSArray *array = [NSArray arrayWithContentsOfURL:[NSURL fileURLWithPath:(nonnull NSString *)]];
NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:(nullable NSString *) ofType:(nullable NSString *)]];

3. 归档(序列化)

//归档会触发
(void)encodeWithCoder:(NSCoder *)aCoder 
//解归
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder

档会触发

@interface Coding : NSObject<NSCoding>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
#import "ModelClass.h"
//必须导入
#import <objc/runtime.h>
@implementation Coding
 /**
 *  根据类动画获取类的所有属性
 *
 *  @param cls <#cls description#>
 *
 *  @return <#return value description#>
 */
- (NSArray *)perperiesWithClass:(Class)cls
{
    NSMutableArray *perperies = [NSMutableArray array];
    //定义属性数量
    unsigned int outCount;
    //动态获取cls类的所有属性,并把值叠加到outCount,每有一个属性累加1
    objc_property_t *properties = class_copyPropertyList(cls, &outCount);
    //遍历cls类的所有属性
    for (int i = 0; i < outCount; i++)
    {
        objc_property_t property = properties[i];
        const char *name = property_getName(property);
        NSString *s = [[NSString alloc] initWithUTF8String:name];
        [perperies addObject:s];
    }
    return perperies;
}


/**
 *  归档会触发
 *
 *  @param aCoder <#aCoder description#>
 */
- (void)encodeWithCoder:(NSCoder *)aCoder
{
    for (NSString *perperty in [self perperiesWithClass:[self class]])
    {
        [aCoder encodeObject:perperty forKey:perperty];
    }
}


/**
 *  解归档会触发
 *
 *  @param aDecoder <#aDecoder description#>
 *
 *  @return <#return value description#>
 */
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super init])
    {
        for (NSString *perperty in [self perperiesWithClass:[self class]])
        {
            [self setValue:[aDecoder decodeObjectForKey:perperty] forKey:perperty];;
        }
    }
    return self;
}
@end
//创建需要归档的数据
 ModelClass *model1 = [ModelClass new];
 model1.name = @"用户名1";
 model.password = @"****";
 model1.age = 18;
//创建需要归档的数据
 ModelClass *model2 = [ModelClass new];
 model2.name = @"用户名2";
 mode2.password = @"****";
 model2.age = 20;

 NSArray *array = @[model1, model2];

 /* 保存对象转化为二进制数据(一定是可变对象)归档的data和解档的data必须是同一个对象。*/
 NSMutableData *data = [NSMutableData data];

/* 归档 */
 //1.初始化
 NSKeyedArchiver *archivier = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
 //2.归档
 [archivier encodeObject:array forKey:@"key"];
 //3.完成归档
 [archivier finishEncoding];
/* 归档 */


 //1.初始化解归档对象,解档的data跟归档data是同一个对象
 NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
 //2.解归档
 NSArray *persons = [unarchiver decodeObjectForKey:@"key"];
 //3.完成解归档
 [unarchiver finishDecoding];

4. CoreData

下面是CoreData堆栈图

image.png

适用于保存用户数据,聊天数据等比较复杂数量较多属性较多的数据

构成

创建工程的时候,勾上Use Core Data
。如图所示:

勾选Use Core Data会自动生成CoreData文件
#import <CoreData/CoreData.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>@property (strong, nonatomic) UIWindow *window;
/* 上下文 */
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
/* 管理数据模型*/
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
/*持久化的数据的对象*/
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
/** * 保存上下文 */
- (void)saveContext;
/** * 获取documents路径 
*
* @return <#return value description#> 
*/
- (NSURL *)applicationDocumentsDirectory;
@end

创建模型。选择项目->右击->New File...->iOS 下面的Core Data->NSManagedObject subclass->选择CoreData文件点击Next->选择要生成的对象模型,点击Next->Create。具体操作如下截图:

- (NSManagedObjectContext *)context
{ 
AppDelegate *app = [UIApplication sharedApplication].delegate; 
return app.managedObjectContext;
}
//创建Person对象
/*insertNewObjectForEntityForName:就是创建的实体名字。inManagedObjectContext:上下文,appDelegate里面已经创建完成。*/ 
Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:[self context]];
 //赋值 
[person setValue:@"小王" forKey:@"name"];
 [person setValue:@(35) forKey:@"age"]; 
//保存 
if (![[self context] save:nil]) 
{ 
    //TODO:保存失败
 }

* 查询

//创建查询对象
 NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Person"];
#if 0 
//条件查询 //
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age<=35"]; 
//查询名字带有王的 
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name like[cd]'*王*'"];
//设置查询条件 request.predicate = predicate;
#endif 
//排序 NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:NO]; 
//设置排序条件 
request.sortDescriptors = @[sort]; 
//执行查询 
NSArray *objectArray = [[self context] executeFetchRequest:request error:nil];
 //遍历查询结果 
for (Person *p in objectArray)
 {
 NSLog(@"%@ - %@",[p valueForKey:@"name"],[p valueForKey:@"age"]);
 }

* 修改

//先查询要修改的对象 
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Person"];
 //设置查询条件 NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name='小王' and age = 35"]; 
request.predicate = predicate;
 //执行查询
 NSArray *objectArray = [[self context] executeFetchRequest:request error:nil]; 
//遍历要修改的对象
 for (Person *p in objectArray) 
{
 //修改(修改内存数据,没有同步数据库)
 [p setValue:@(45) forKey:@"age"]; 
} 
//同步数据库 [[self context] save:nil];

* 删除

//查询要删除的数据
 NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Person"];
 //设置查询条件 NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name='小王'"];
 request.predicate = predicate;
 //执行查询
 NSArray *objectArray = [[self context] executeFetchRequest:request error:nil]; 
//遍历删除 for (Person *p in objectArray) 
{
 //删除内存中的数据 [[self context] deleteObject:p];
 }
 //同步数据库 [[self context] save:nil];

CoreData最重要的是关系,由于时间关系。当app更新版本,并且表结构有修改,需要版本升级和数据迁移操作,否则app就是崩掉。


4. 数据库

首先需要对常用的sql语句了解。这里就不在介绍了,可以看下这个教程:http://www.w3school.com.cn/sql/

//返回数据库路径,保存到Cache目录下
-(NSString *)databasePath{ 
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]; 
return [path stringByAppendingPathComponent:@"contacts.db"];
}

*创建sqlite3
对象并且打开数据库,如果数据库打开成功,就创建表。

//数据库对象 sqlite3 *contactDB;
const char *path = [[self databasePath] UTF8String];
 if (sqlite3_open(path, &contactDB) == SQLITE_OK) 
{ 
char *errMsg; const char *sql_stmt = "CREATE TABLE IF NOT EXISTS CONTACTS(ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT, ADDRESS TEXT,PHONE TEXT)"; 
//执行语句 
    if (sqlite3_exec(contactDB, sql_stmt, NULL, NULL, &errMsg) !=   
  SQLITE_OK) {
 //创建表失败 
    }
} 
else
 {
 //打开数据库失败
 } sqlite3_close(contactDB);

*代码解释:
sqlite3_open
打开指定路径的数据库,如果数据库不存在,就会创建一个新的数据库。
SQLITE_OK
是一个常量,表示打开数据库成功。下面是苹果的定义:

define SQLITE_OK 0 /* Successful result */

& contactDB:就是数据库对象。
& sqlite3_exec:就是执行sql语句方法。
& sqlite3_close:关闭数据库,一般暂时不用数据库的时候手动关闭,防止资源浪费。

*保存数据到数据库

//是一个抽象类型,是一个句柄,在使用过程中一般以它的指针进行操作sqlite3_stmt *statement;
//数据库路径
const char *path = [[self databasePath] UTF8String];
//使用的时候打开数据库
if (sqlite3_open(path, &contactDB) == SQLITE_OK)
{
 NSString *insertSQL = [NSString stringWithFormat:@"INSERT INTO CONTACTS (name,address,phone) VALUES(\"%@\",\"%@\",\"%@\")",name.text,address.text,phone.text]; 
const char *insert_stmt = [insertSQL UTF8String]; 
// 这个函数将sql文本转换成一个准备语句(prepared statement)对象,同时返回这个对象的指针。这个接口需要一个数据库连接指针以及一个要准备的包含SQL语句的文本。它实际上并不执行这个SQL语句,它仅仅为执行准备这个sql语句 sqlite3_prepare_v2(contactDB, insert_stmt, -1, &statement, NULL); 
//执行这个sql 
if (sqlite3_step(statement) == SQLITE_DONE) 
{ 
//TODO:已存储到数据库; 
} else
 { 
//TODO:保存失败
 } 
//销毁statement对象 sqlite3_finalize(statement); //关闭数据库 
sqlite3_close(contactDB);
}

*查询操作

//数据库路径
const char *path = [[self databasePath] UTF8String];
//查询结果集对象句柄sqlite3_stmt *statement;
//打开数据库
if (sqlite3_open(path, &contactDB) == SQLITE_OK)
{ 
//查询的sql语句 
NSString *querySQL = [NSString stringWithFormat:@"SELECT address,phone from contacts where name=\"%@\"",name.text]; 
const char *query_stmt = [querySQL UTF8String]; 
//执行查询sql语句 
if (sqlite3_prepare_v2(contactDB, query_stmt, -1, &statement, NULL) == SQLITE_OK) 
{
 //遍历每条数据 
if (sqlite3_step(statement) == SQLITE_ROW) 
{ 
//获取每条数据的字段。
 NSString *addressField = [[NSString alloc] initWithUTF8String:(const char *)sqlite3_column_text(statement, 0)]; 
address.text = addressField; NSString *phoneField = [[NSString alloc] initWithUTF8String:(const char *)sqlite3_column_text(statement, 1 )]; 
phone.text = phoneField; 
//TODO:已查到结果 
} else 
{ 
//TODO:未查到结果 
} 
sqlite3_finalize(statement);
 } sqlite3_close(contactDB);
}

沙盒外持久化保存数据方案

1. SSKeyChain

上一篇下一篇

猜你喜欢

热点阅读