iOS高质量博客iOS新手学习iOS 技术点

浅谈iOS本地数据保存

2016-04-25  本文已影响10523人  ParadiseKiss

iOS本地数据保存有多种方式,比如NSUserDefaults、归档、文件保存、数据库、CoreDataKeyChain(钥匙串)等多种方式。其中KeyChain(钥匙串)是保存到沙盒范围以外的地方,也就是与沙盒无关。

沙盒

沙盒目录文件结构

Documents:路径获取有3种方法

  1. 利用字符串拼接,在home目录后面拼接字符串Documents
//获取家目录
NSString *homeDocumentPath = NSHomeDirectory();
//拼接
NSString *documents = [homeDocumentPath stringByAppendingPathComponent:@"Documents"];
  1. NSSearchPathForDirectoriesInDomains方法
// 获取documents目录
//参数1:表示Documents目录。参数2:当前用户目录(user's home directory --- place to install user's personal items)。 参数3:YES 代表展开路径中的波浪字符“~”
NSArray *paths =  NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
// 只有一个documents
NSString *documents = [paths lastObject];
  1. NSFileManager
  //获取documents路径
  [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]

Library/Caches: 也是3种方法

  1. 利用字符串拼接,在home目录后面拼接Library/Caches
NSString *homeDocumentPath = NSHomeDirectory();
//拼接
NSString *cachePath = [homeDocumentPath stringByAppendingPathComponent:@"Library/Caches"];
  1. NSSearchPathForDirectoriesInDomains方法
NSArray *paths =  NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
// 只有一个caches
NSString *cachePath = [paths lastObject];
  1. NSFileManager
[[[NSFileManager defaultManager] URLsForDirectory: NSCachesDirectory inDomains:NSUserDomainMask] lastObject]

tmp路径获取:

NSString *tmp = NSTemporaryDirectory();

iOS本地数据保存常用方式

1. NSUserDefaults

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
//保存值(key值同名的时候会覆盖的)  
 [defaults setObject:@"用户名" forKey:kUsernameKey];
//立即保存
[defaults synchronize];
//取值
NSString *username = [defaults objectForKey:kUsernameKey];
//保存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 *)];

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.

2. 归档(序列化)

@interface Coding : NSObject<NSCoding>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
#import "Coding.h"
#import <objc/runtime.h>

@implementation Coding
 /**
 *  根据类动画获取类的所有属性,不要忘记导入#import <objc/runtime.h>
 *
 *  @param cls <#cls description#>
 *
 *  @return <#return value description#>
 */
- (NSArray *)perperiesWithClass:(Class)cls
{
    
    NSMutableArray *perperies = [NSMutableArray array];
    
    unsigned int outCount;
    //动态获取属性
    objc_property_t *properties = class_copyPropertyList(cls, &outCount);
    
    //遍历person类的所有属性
    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
   Coding *coding1 = [[Coding alloc] init];
   coding1.name = @"小明";
   coding1.age = 12;
   
   Coding *coding2 = [[Coding alloc] init];
   coding1.name = @"小王";
   coding1.age = 20;
   
   NSArray *array = @[coding1, coding2];
   
   //保存对象转化为二进制数据(一定是可变对象)
   NSMutableData *data = [NSMutableData data];
   
   //1.初始化
   NSKeyedArchiver *archivier = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
   //2.归档
   [archivier encodeObject:array forKey:@"key"];
   
   //3.完成归档
   [archivier finishEncoding];
   
   //4.保存
   [[NSUserDefaults standardUserDefaults] setObject:data forKey:@"data"];
//1.获取保存的数据
   NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:@"data"];
   
   //2.初始化解归档对象
   NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
   
   //3.解归档
   NSArray *persons = [unarchiver decodeObjectForKey:@"key"];
   
   //4.完成解归档
   [unarchiver finishDecoding];

3. plist文件保存

//把字典写入到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 *)]];

4. 数据库

 //返回数据库路径,保存到Cache目录下
-(NSString *)databasePath
{
  NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
  
  return [path stringByAppendingPathComponent:@"contacts.db"];
}
  //数据库对象
   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);
> \#define SQLITE_OK           0   /* Successful result */
  //是一个抽象类型,是一个句柄,在使用过程中一般以它的指针进行操作
    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);
  }

5. 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
创建实体 实体结构 创建模型 创建类 选择要生成的CoreData文件 选择要生成的对象 生成好的模型

具体实现代码如下:

- (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];

6. KeyChain

基本使用

为了使用方便,我们使用github上封装好的类KeychainItemWrapperSFHFKeychainUtils

// 初始化一个保存用户帐号的KeychainItemWrapper 
KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"Your Apple ID" accessGroup:@"YOUR_APP_ID.com.yourcompany.AppIdentifier"];
//保存帐号
[wrapper setObject:@"<帐号>" forKey:(id)kSecAttrAccount];  
//保存密码
[wrapper setObject:@"<帐号密码>" forKey:(id)kSecValueData];      
//从keychain里取出帐号密码
NSString *password = [wrapper objectForKey:(id)kSecValueData];
//清空设置
[wrapper resetKeychainItem];
//获取密码密码
+(NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
//存储密码
+(BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error;
//删除密码
+(BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
  //创建一个uuid
  NSString *uuidString = [self uuidString];
  //31C75924-1D2E-4AF0-9C67-96D6929B1BD3
        
 [SFHFKeychainUtils storeUsername:kKeyChainKey andPassword:uuidString forServiceName:kKeyChainGroupKey updateExisting:NO error:nil];
-(NSString *)uuidString
{
    //创建一个uuid
    CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
    CFStringRef stringRef = CFUUIDCreateString(kCFAllocatorDefault, uuidRef);
    
    NSString *uuidString = (__bridge NSString *)(stringRef);
    
    CFRelease(uuidRef);
    
    return uuidString;
}
上一篇 下一篇

猜你喜欢

热点阅读