博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS本地数据保存
阅读量:7095 次
发布时间:2019-06-28

本文共 22157 字,大约阅读时间需要 73 分钟。

hot3.png

沙盒介绍:

获取路径参数介绍:

/**

NSSearchPathForDirectoriesInDomains 是用来查找文件路径的函数,其返回值是一个数组,这个数组中只有一个NSString类型元素,这个元素保存的就是查找的路径。

参数1: NSDocumentDirectory 表示需要查找的是Document文件夹

参数2:NSUserDomainMask 表示在用户的主目录中查找

参数3:YES 表示返回路径展开

*/

   iOS 应用程序在安装时,会创建属于自己的沙盒文件,应用程序不能直接访问其他应用程序的沙盒文件,

   当应用程序需要向外部请求或接收数据时,都需要经过权限认证,否则,无法获取到数据。

   应用程序中所有的非代码文件都保存在沙盒中,比如图片、声音、属性列表,sqlite数据库和文本文件等

   沙盒的的根目录有三个文件夹,分别是 Documents,Library,tmp

    

   Documents:中一般保存应用程序本身产生文件数据,例如游戏进度,绘图软件的绘图等, iTunes备份和

   恢复的时候,会包括此目录

   注意:在此目录下不要保存从网络上下载的文件,否则app无法上架

   Documents:路径获取有3种方法

   1.利用字符串拼接,在home目录后面拼接字符串Documents

     //获取家目录     

       NSString*homeDocumentPath = NSHomeDirectory();

     //拼接

      NSString*documents = [homeDocumentPath stringByAppendingPathComponent:@"Documents"];

   2.NSSearchPathForDirectoriesInDomains方法

   NSString*path=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,  

    YES).lastObject;

   3.NSFileManager 

     [[[NSFileManager defaultManager] URLsForDirectory: NSDocumentDirectory

     inDomains:NSUserDomainMask] lastObject]

 

   Library:目录下有两个子目录:CachesPreferences

   Library/Caches/(这个目录用于存放临时文件,保存应用程序再次启动过程中不需要的信息)

   此目录用来保存应用程序运行时生成的需要持久化的数据,这些数据一般存储体积比较大,又不是十分重

   要,比如网络请求数据等。这些数据需要用户负责删除。iTunes同步设备时不会备份该目录。

    路径同样有三种获取方式:

     NSString*homeDocumentPath = NSHomeDirectory();

     1.

      NSString*cachePath =

      [homeDocumentPath stringByAppendingPathComponent:@"Library/Caches"];

      2.

     NSString*path=

      NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask,  

      YES).lastObject;

       3.

       [[[NSFileManager defaultManager] URLsForDirectory: NSCachesDirectory    

       inDomains:NSUserDomainMask] lastObject]

   Library/Preferences/(用于存放应用程序专用的支持文件,保存应用程序再次启动过程中需要的信息)

  此目录保存应用程序的所有偏好设置,iOS的Settings(设置)应用会在该目录中查找应用的设置信息。

  iTunes同步设备时会备份该目录

  在Preferences/下不能直接创建偏好设置文件,而是应该使用NSUserDefaults类来取得和设置应用程序的

  偏好.

   获取路径同上(改一下标红部分即可)

  tmp iTunes不会备份这个目录,用来保存临时数据,应用退出时会清除该目录下的数据

  NSString *tmp = NSTemporaryDirectory();

  

 

 

 

ios 常用的存储方式:

  • NSUserDefaults(存在沙盒的Library/Preferences 目录文件夹里)
  • 归档(序列化)(存在Documents文件夹下以生成data.archive文件形式存在.缺点:归档的形式来保存数据,只能⼀次性归档保存以及一次性解压。所以只能针对小量数据,而且对数据操作比较 笨拙,即如果想改动数据的某一小部分,还是需要解压整个数据或者归档整个数据)
  • plist文件(沙盒的Documents中就创建了一个plist文件,并且写入数据保存)
  • 数据库(SQLite3的使用还是比较麻烦的,因为都是些c语言的函数,理解起来有些困难。不过在一般开发过程中,使用的都是第三方开源库 FMDB,封装了这些基本的c语言方法,使得我们在使用时更加容易理解,提高开发效率,同样是存储到沙盒的Documents文件夹中,适合存储大量的内容)
  • CoreData
  • KeyChain

NSUserDefaults:NSUserDefaults适合存储轻量级的本地数据,比如要保存一个登陆界面的数据,用户名、

                       密码之类的(支持的数据格式有:NSNumber(Integer、Float、Double),NSString,

                        NSDate,NSArray,NSDictionary,BOOL类型).

/**

NSUserDefaults*defaults = [NSUserDefaultsstandardUserDefaults];

//保存值(key值同名的时候会覆盖的)

 [defaults setObject:@"用户名"forKey:kUsernameKey];

//立即保存

[defaults synchronize];

//取值

NSString*username = [defaults objectForKey:kUsernameKey];

*/

另外的保存方法

//保存NSInteger

[defaults setInteger:(NSInteger) forKey:(nonnullNSString*)];

//保存BOOL

[defaults setBool:(BOOL) forKey:(nonnullNSString*)];

//保存NSURL

[defaults setURL:(nullableNSURL*) forKey:(nonnullNSString*)];

//保存float

[defaults setFloat:(float) forKey:(nonnullNSString*)];

//保存double

[defaults setDouble:(double) forKey:(nonnullNSString*)];

//取值

[defaults integerForKey:(nonnullNSString*)];

[defaults boolForKey:(nonnullNSString*)];

[defaults URLForKey:(nonnullNSString*)];

[defaults floatForKey:(nonnullNSString*)];

[defaults doubleForKey:(nonnullNSString*)];

归档(序列化):一般保存自定义的对象,但是只有遵守NSCoding的类才能使用归档

归档保存自定义对象

定义一个Person类,如果想对person进行归档解档,首先要让Person遵守协议

 

/********Person.h*********/

#import

//遵守NSCoding协议

Person : NSObject

(nonatomic, copy) NSString *name;

@property (nonatomic, assign) NSInteger age;

//自定义的归档保存数据的方法

+(void)savePerson:(Person *)person;

//自定义的读取沙盒中解档出的数据

+(Person *)getPerson;

@end

NSCoding协议有2个方法:

(void)encodeWithCoder:(NSCoder *)aCoder

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

(instancetype)initWithCoder:(NSCoder *)aDecoder

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

/******Person.m*******/

#import "Person.h"

@implementation Person

//归档,Key建议使用宏代替,这里就不使用了

- (void)encodeWithCoder:(NSCoder *)aCoder {

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

[aCoder encodeInteger:self.age forKey:@"age"];

}

//解档

-(instancetype)initWithCoder:(NSCoder *)aDecoder {

if (self=[super init]) {

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

self.age = [aDecoder decodeIntegerForKey:@"age"];

}

return self;

}

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

+(void)savePerson:(Person *)person {

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

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

[NSKeyedArchiver archiveRootObject:person toFile:path];

}

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

+(Person *)getPerson {

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

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

Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

return person;

}

@end

下面就可以在需要的地方归档或解档Person对象。

/*******ViewController.m*******/

//创建Person对象

Person *person = [Person new];

person.name = @"jack";

person.age = 17;

//归档保存数据

[Person savePerson:person];

//解档拿到数据

Person *resultPerson = [Person getPerson];

//打印出结果,证明归档解档成功

NSLog(@"name = %@, age = %ld",resultPerson.name,resultPerson.age);

SQLite

第⼀步:需要添加SQLite相关的库以及头文件:在项目文件的Build Phases下,找到Link Binary Library(ies),添加libsqlite3.0.dylib(libsqlite3.dylib与前者的区别暂时不知,两者应该差不多);在项目文件中头文件或者源文件中添加头文件 #import "/usr/include/sqlite3.h"

第二步:开始使用SQLite:

使用前注意:如果不往数据库里面添加任何的表,这个数据库等于没有建立,不会在硬盘上产生任何文件,如果数据库已经 存在,则会打开这个数据库。

NSArray*documentsPaths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask , YES);

NSString *databaseFilePath=[[documentsPaths objectAtIndex:0] stringByAppendingPathComponent:@"mydb"];

//上面两句已经比较熟悉了吧!

//打开数据库
if (sqlite3_open([databaseFilePath UTF8String], &database)==SQLITE_OK) {

NSLog(@"sqlite dadabase is opened."); }

else{ return;}//打开不成功就返回

在打开了数据库的前提下,如果数据库没有表,那就开始建表了哦!

char *error;

const char *createSql="create table(id integer primary key autoincrement, name text)"; if (sqlite3_exec(database, createSql, NULL, NULL, &error)==SQLITE_OK) {

NSLog(@"create table is ok."); }

else {

sqlite3_free(error);//每次使用完毕清空error字符串,提供给下⼀一次使用 }

建表完成之后,就开始插入记录:

const char *insertSql="insert into a person (name) values(‘gg’)";

if (sqlite3_exec(database, insertSql, NULL, NULL, &error)==SQLITE_OK) {

NSLog(@"insert operation is ok."); }

else {

NSLog(@"error: %s",error);

sqlite3_free(error);//每次使用完毕清空error字符串,提供给下⼀一次使用 }

下⼀一步,查询记录:

const char *selectSql="select id,name from a person";

sqlite3_stmt *statement;
if (sqlite3_prepare_v2(database,selectSql, -1, &statement, nil)==SQLITE_OK) {

NSLog(@"select operation is ok."); }

else {

sqlite3_free(error); }

while(sqlite3_step(statement)==SQLITE_ROW) {

int _id=sqlite3_column_int(statement, 0);
NSString *name=(char*)sqlite3_column_text(statement, 1); NSLog(@"row>>id %i, name %s",_id,name);
}
sqlite3_finalize(statement);
最后,关闭数据库:
sqlite3_close(database);

注意:写入数据库,字符串可以采用char方式,而从数据库中取出char类型,当char类型有表示中文字符 时,会出现乱码。这是因为数据库默认使用ascII(二进制)编码方式。所以要想正确从数据库中取出中文,需要用 NSString来接收从数据库取出的字符串。 

iOS本地数据库FMDB的使用

 FMDB是一种第三方的开源库,FMDB就是对SQLite的API进行了封装,加上了面向对象的思想,让我们不必使用繁琐的C语言API函数,比起直接操作SQLite更加方便

    FMDB优点:

    1.使用起来更加面向对象,省去了很多麻烦、冗余的C语言代码
    2.对比苹果自带的CoreData框架,更加轻量级和灵活
    3.提供多线程安全,有效地防止数据混乱,原来的SQLite不是线程安全

    FMDB缺点:

    因为是OC语言封装的,失去了SQLite原来的跨平台性

 我们要使用FMDB需要的步骤:

    1.项目中添加libsqlite3库的依赖
    2.导入FMDB源码:
    3.下载FMDB的源代码,将代码文件拖入工程
    4.#import导入FMDB的头文件"FMDatabase.h"
    使用FMDB前,需要先了解下3个主要类:
    1.FMDatabase : 一个单一的SQLite数据库,用于执行SQL语句。
    2.FMResultSet :执行查询一个FMDatabase结果集。
    3.FMDatabaseQueue :在多个线程来执行查询和更新时会使用这个类。

    一般的FMDB数据库操作有:

    1.创建数据库
    2.打开数据库、关闭数据库
    3.执行更新(增加、删除、修改)的SQL语句
    4.执行查询的SQL语句

一、打开数据库

通过指定SQLite数据库文件路径来创建FMDatabase对象
FMDatabase *db = [FMDatabase databaseWithPath:path];
if (![db open]) {
NSLog(@"数据库打开失败!");
}
文件路径有三种情况
(1)具体文件路径
  如果不存在会自动创建
(2)空字符串@""
  会在临时目录创建一个空的数据库
  当FMDatabase连接关闭时,数据库文件也被删除
(3)nil
  会创建一个内存中临时数据库,当FMDatabase连接关闭时,数据库会被销毁
二、执行更新
在FMDB中,除查询以外的所有操作,都称为“更新”
create、drop、insert、update、delete等
使用executeUpdate:方法执行更新
- (BOOL)executeUpdate:(NSString*)sql, ...
- (BOOL)executeUpdateWithFormat:(NSString*)format, ...
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments
示例
[db executeUpdate:@"UPDATE t_student SET age = ? WHERE name = ?;", @20, @"Jack"]
三、执行查询
查询方法
- (FMResultSet *)executeQuery:(NSString*)sql, ...
- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments
示例
// 查询数据
FMResultSet *rs = [db executeQuery:@"SELECT * FROM t_student"];
// 遍历结果集
while ([rs next]) {
NSString *name = [rs stringForColumn:@"name"];
int age = [rs intForColumn:@"age"];
double score = [rs doubleForColumn:@"score"];
}
代码示例:

//MARK:创建本地数据库  

  1. - (void)CreateDatabase  
  2. {  
  3.     NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];  
  4.     NSString *dbPath   = [docsPath stringByAppendingPathComponent:@"liantongzhanting.db"];  
  5.     FMDatabase *db     = [FMDatabase databaseWithPath:dbPath];  
  6.       
  7.     if ([db open]) {  
  8.           
  9.         NSString*createTable=@"create table firstcontentlist_tb (id integer primary key autoincrement,contentName text,fileName text,veision text);"  
  10.         "create table contentlist_tb (id integer primary key autoincrement,contentId text,contentName text,contentNo text, fileName text, filePath text,veision text);"  
  11.         "create table userinfo_tb(id integer primary key autoincrement, areaId text,email text,password text,status text,tel text,userId text,username text);"  
  12.         "create table collect_tb (id integer primary key autoincrement, collectImage text,filePath text,contentID text,collectTitle text,collectDesc text,collectText text,tel text,collectTime text,state text,collectType text);"  
  13.         "create table addcollect_tb (id integer primary key autoincrement, imagePath text,url text);"  
  14.         "create table loginfo_tb (id integer primary key autoincrement, tel text,resultDetail text,showroomName text,optDetail text,objectId text,resultCode text,optCode text,optTime text,logdel text);"  
  15.         "create table orderinfo_tb (id integer primary key autoincrement, orderTime text,customerCompany text,customerIndustry text,customerName text,customerDepartment text,customerJob text,customerPhone text,customerMail text,id text,state text);";  
  16.           
  17.         BOOL result=[db executeStatements:createTable];  
  18.           
  19.         if (result)  
  20.         {  
  21.             NSLog(@"创表成功");  
  22.         }else  
  23.         {  
  24.             NSLog(@"创表失败");  
  25.         }  
  26.     }  
  27.       
  28.     [db close];  
  29. }  

 

增加数据:

//MARK:插入下载内容列表  

  1. -(void)insertContentlist:(NSMutableArray*)data  
  2. {  
  3.     [queue inDatabase:^(FMDatabase *db) {  
  4.         //打开数据库  
  5.         if ([db open]) {  
  6.             [db executeUpdate:[NSString stringWithFormat:@"delete from contentlist_tb"]];  
  7.         }  
  8.     }];  
  9.     if(data==nil||data.count<=0)  
  10.     {  
  11.         return;  
  12.     }  
  13.     NSString*insertContentSql=@"insert into contentlist_tb(contentId ,contentName ,contentNo,fileName,filePath ,veision) values (?,?,?,?,?,?)";  
  14.     [queue inDatabase:^(FMDatabase *db) {  
  15.         //打开数据库  
  16.         if ([db open]) {  
  17.             db.beginTransaction;  
  18.             for(int i=0;i<data.count;i++)  
  19.             {  
  20.                 NSDictionary* contentData=[data objectAtIndex:i];  
  21.                 NSString*contentId=[contentData objectForKey:@"contentId"];  
  22.                 NSString*contentName=[contentData objectForKey:@"contentName"];  
  23.                   
  24.                 NSString*contentNo=[contentData objectForKey:@"contentNo"];  
  25.                   
  26.                 NSString*fileName=[contentData objectForKey:@"fileName"];  
  27.                 NSString*filePath=[contentData objectForKey:@"filePath"];  
  28.                 NSString*veision=[contentData objectForKey:@"veision"];  
  29.                   
  30.                 NSArray*contentValues= [[NSArray alloc] initWithObjects:contentId,contentName,contentNo,fileName,filePath,veision,nil];  
  31.                   
  32.                 [db executeUpdate:insertContentSql withArgumentsInArray:contentValues];  
  33.                   
  34.             }  
  35.               
  36.               
  37.         }  
  38.         db.commit;  
  39.     }];  
  40.       
  41. }  

删除数据:

//操作收藏数据库,删除  

  1.     [queue inDatabase:^(FMDatabase *db) {  
  2.         //打开数据库  
  3.         if ([db open]) {  
  4.             [db executeUpdate:[NSString stringWithFormat:@"delete from collect_tb where id='%@'",idStr]];  
  5.               
  6.         }  
  7.     }];  

更新数据:

  1. FMDatabaseQueue* queue=[FMDatabaseQueue getSharedDatabaseQueue];  
  2.                 [queue inDatabase:^(FMDatabase *db) {  
  3.                     //打开数据库  
  4.                     if ([db open]) {  
  5.                         [db executeUpdate:@"UPDATE loginfo_tb SET logdel = ? WHERE optTime = ?",logdel,optTime];  
  6.                         NSLog(@"operationtime=======%@",optTime);  
  7.           
  8.                     }  
  9.                 }];  

 

查询数据:

  1. //根据filename查询version  
  2.             arguments = [NSMutableArray array];  
  3.             [queue inDatabase:^(FMDatabase *db) {  
  4.                 if ([db open]) {  
  5.                       
  6.                     FMResultSet *rs = [db executeQuery:[NSString stringWithFormat:@"SELECT * FROM contentlist_tb where fileName='%@'",filename]];  
  7.                     while ([rs next]) {  
  8.                           
  9.                         [arguments addObject:[rs resultDictionary]];  
  10.                           
  11.                     }  
  12.                     [rs close];  
  13.                       
  14.                 }  
  15.             }];  

[queue inDatabase:^(FMDatabase *db) {  

  1.         if ([db open]) {  
  2.             FMResultSet *rs = [db executeQuery:@"SELECT * FROM contentlist_tb"];  
  3.             arguments = [NSMutableArray array];  
  4.               
  5.             while ([rs next]) {  
  6.                   
  7.                 [arguments addObject: [rs resultDictionary]];  
  8.             }  
  9.             [rs close];  
  10.               
  11.         }  
  12.     }];  

以上就是FMDB的基本使用方法和几种基本的操作数据库的方法,用起来简单易懂,十分方便,大家尽可尝试。

 

CoreData:

CoreData是一门功能强大的数据持久化技术,位于SQLite数据库之上,它避免了SQL的复杂性,能让我们以更自然的方式与数据库进行交互。CoreData提供数据--OC对象映射关系来实现数据与对象管理,这样无需任何SQL语句就能操作他们。

CoreData数据持久化框架是Cocoa API的一部分,⾸次在iOS5 版本的系统中出现,它允许按照实体-属性-值模型组织数据,并以XML⼆进制文件或者SQLite数据⽂件的格式持久化数据

1、可视化,且具有undo/redo能力

2、可以实现多种文件格式:

* NSSQLiteStoreType

* NSBinaryStoreType

* NSInMemoryStoreType'

* NSXMLStoreTyp3、

苹果官方API支持,与iOS结合更紧密

CoreData核心类与结构

NSManagedObjectContext(数据上下文)

  • 对象管理上下文,负责数据的实际操作(重要)
  • 作用:插入数据,查询数据,删除数据,更新数据

NSPersistentStoreCoordinator(持久化存储助理)

  • 相当于数据库的连接器
  • 作用:设置数据存储的名字,位置,存储方式,和存储时机

NSManagedObjectModel(数据模型)

  • 数据库所有表格或数据结构,包含各实体的定义信息
  • 作用:添加实体的属性,建立属性之间的关系
  • 操作方法:视图编辑器,或代码

NSManagedObject(被管理的数据记录)

  • 数据库中的表格记录

NSEntityDescription(实体结构)

  • 相当于表格结构

NSFetchRequest(数据请求)

  • 相当于查询语句

后缀为.xcdatamodeld的包

  • 里面是.xcdatamodel文件,用数据模型编辑器编辑
  • 编译后为.momd或.mom文件
  • 关系图:

手动创建CoreData

创建步骤如下:

1.创建模型文件 [相当于一个数据库]

2.添加实体 [一张表]

3.创建实体类 [相当模型--表结构]

4.生成上下文 关联模型文件生成数据库

 

0、手动创建CoreData数据时,我们创建一个和平常一样的工程,不需要勾选Use Core Data:

1、创建模型文件

1、进入创建新文件,command+N或者如下图

2、选择文件类型, 如下图:

3、设置文件名,如下图:

4、模型文件创建成功,会出现以后

2、创建实体

1、利用可视化的方式创建实体,实体的功能就类似于我们的Model类,具体操作如下如:

3、创建实体类

利用可视化创建了实体,但是我们要想获取对应的数据和名称,就必须关联类,因此要创建实体类,创建步骤如下:

1、进入创建新文件

2、选择文件类型, 实体类文件类型选择:NSManagedObject subclass

3、选择模型文件

4、选择实体

5、创建成功

实体文件创建成功后系统自动帮我们生成对应的类和属性类名对应实体名称属性对应实体中的属性名称;如果我们的属性是基本数据类型,那么默认会帮我们转换成NSNumber类型的属性.

* 老版本:只生成一对文件,即类和属性都在一起

* 新版本:生成一对类文件,再生成一对类目,在类目中生成属性

4、生成上下文 关联数据库

在创建上下文和关联数据库之前我们先来看看想对应的关系依赖图:

从上图我们看到,要想生成上下文,需要有数据库助理模型的支持;就像SQLite中一样,想要操作数据库,你必须要有数据库,并且创建好;我们来看实现代码

- (void)viewDidLoad { [superviewDidLoad];

//1、创建模型对象

//获取模型路径

NSURL*modelURL = [[NSBundlemainBundle] URLForResource:@"School"withExtension:@"momd"];

//根据模型文件创建模型对象

NSManagedObjectModel*model = [[NSManagedObjectModelalloc] initWithContentsOfURL:modelURL];

//2、创建持久化助理

//利用模型对象创建助理对象

NSPersistentStoreCoordinator*store = [[NSPersistentStoreCoordinatoralloc] initWithManagedObjectModel:model];

//数据库的名称和路径

NSString*docStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];

NSString*sqlPath = [docStr stringByAppendingPathComponent:@"mySqlite.sqlite"];

NSLog(@"path = %@", sqlPath);

NSURL*sqlUrl = [NSURLfileURLWithPath:sqlPath];

//设置数据库相关信息

[store addPersistentStoreWithType:NSSQLiteStoreTypeconfiguration:nilURL:sqlUrl options:nilerror:nil];

//3、创建上下文

NSManagedObjectContext*context = [[NSManagedObjectContextalloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

//关联持久化助理

[context setPersistentStoreCoordinator:store];

_context = context;

}

  • 这样之后,我们整个CoreData数据库就算创建完成,整个过程都是我们手动,这样对于原理能更好的理解;
  • 运行之后其实数据库就已经创建完成,进入对应路径下,我们能看到已经创建好的数据库文件;
  • 利用工具打开后,发现里面对应的表也帮我们创建好了;
  • 经过上面的步骤创建后,在后续的工作中我们就不需要进行任何和数据库相关的工作了,所有和数据库打交道的工作就都交给了CoreData来实现。

0.系统创建CoreData

利用系统自带的方式创建数据库和手动创建的方式内部步骤一样的,只是系统将创建模型文件、生成上下文、关联数据库的工作帮我们做了,这些工作我们无需再做;

说这么多,我们一起来看看如何利用系统自带来创建CoreData数据库

对于利用系统自带创建的话,只需要两个步骤:

  • 创建工程
  • 创建实体已经关联实体类

创建工程

1、手动创建CoreData数据库时,我们创建一个和平常一样的工程, 特别注意:一定要勾选Use Core Data:

2、工程创建完后,系统会自动帮我们创建一个与工程同名的模型文件;以及帮我们写好了生成上下文关联数据库的代码

                                                      模型文件

                                                   生成上下文关联数据库

上面这些做好之后,我们只需要创建实体,以及关联实体类就可以

创建实体 关联实体类

创建实体和关联实体类和手动创建数据库的方式是一样的,参照手动创建2、3即可

 

 

 

iOS开发中使用keyChain保存用户密码

keyChain保存用户密码

对于一些轻量的数据,可以使用NSUserDefaults将其保存到应用的沙盒中的plist文件中。

那么对于用户密码是否可以如法炮制呢?答案是否定的。根据查询的资料介绍,如果沙盒被破解,或者手机被越狱,plist文件就会被获取,明文的用户密码就暴露了,导致安全问题。

加密后保存到plist文件中

基于第一个方案,自然可以想到是否可以对明文进行加密后保存呢?答案是肯定的。

iOS提供了多种加密算法,对于用户密码,通常采用的是MD5加密,该加密是不可逆的。使用MD5加密算法需要导入头文件:

#import <CommonCrypto/CommonDigest.h>

那么这个方法是最优方案吗?不是的,因为Apple提供了一个更好的机制:

使用keyChain保存用户密码

根据文档介绍,iOS设备中的keyChain是一个安全的存储容器,可以用来为不同应用保存敏感信息(用户名,密码,网络密码等)。同时,keyChain是一个相对独立的空间,当应用替换或删除时并不会删除keyChain的内容,这对用户来说就十分的便利了。

既然keyChain是Apple提供的专门用于保存敏感信息的容器,同时又具有相对的独立性,那么目前看来,使用keyChain来保存用户名和用户密码是最优的解决方案。

更新:经知友指正与查阅资料,证实iPhone越狱后,有技术手段获取keyChain内容。从用户角度来讲,谨慎越狱。从开发者角度来讲,对于这个安全隐患要有清醒认识。

实现keyChain保存用户密码

通过查询资料,可以知道Apple是提供了官方的一些方法的。但是本着不要重复造轮子,个人采取了学习其他开发者的代码的方法,对于功能实现原理做了解,不去深究细节。

首先,需要导入secutity.framework框架:为了进行实践,个人新建了一个keychain项目。而后:

  1. 在Xcode左侧文件列表中选中项目名称,然后选中TARGETS;
  2. 中间界面选中Build Phases,在Link Binary With Libraries中点击加号;
  3. 搜索找到secutity.framework导入即可,如下图所示:

其次,新建KeyChain类,用于实现向keyChain存储、读取和修改用户名密码。代码如下:

KeyChain.h文件

#import <Foundation/Foundation.h>

#import <Security/Security.h>

 

@interface KeyChain : NSObject

 

+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service;

 

// save username and password to keychain

+ (void)save:(NSString *)service data:(id)data;

 

// load username and password from keychain

+ (id)load:(NSString *)service;

 

// delete username and password from keychain

+ (void)delete:(NSString *)serviece;

@end

KeyChain.m文件

#import "KeyChain.h"

 

@implementation KeyChain

 

+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {

    return [NSMutableDictionary dictionaryWithObjectsAndKeys:

            (id)kSecClassGenericPassword,(id)kSecClass,

            service, (id)kSecAttrService,

            service, (id)kSecAttrAccount,

            (id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,

            nil];

}

 

#pragma mark 写入

+ (void)save:(NSString *)service data:(id)data {

    //Get search dictionary

    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];

    //Delete old item before add new item

    SecItemDelete((CFDictionaryRef)keychainQuery);

    //Add new object to search dictionary(Attention:the data format)

    [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];

    //Add item to keychain with the search dictionary

    SecItemAdd((CFDictionaryRef)keychainQuery, NULL);

}

 

#pragma mark 读取

+ (id)load:(NSString *)service {

    id ret = nil;

    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];

    //Configure the search setting

    //Since in our simple case we are expecting only a single attribute to be returned (the password) we can set the attribute kSecReturnData to kCFBooleanTrue

    [keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];

    [keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];

    CFDataRef keyData = NULL;

    if (SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {

        @try {

            ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];

        } @catch (NSException *e) {

            NSLog(@"Unarchive of %@ failed: %@", service, e);

        } @finally {

        }

    }

    if (keyData)

        CFRelease(keyData);

    return ret;

}

 

#pragma mark 删除

+ (void)delete:(NSString *)service {

    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];

    SecItemDelete((CFDictionaryRef)keychainQuery);

}

@end

为了验证KeyChain类,修改ViewController.m文件代码进行实验。具体如下:

#import "ViewController.h"

#import "KeyChain.h"

 

@interface ViewController ()

 

@end

 

//

NSString * const KEY_USERNAME_PASSWORD = @"com.company.app.usernamepassword";

NSString * const KEY_USERNAME = @"com.company.app.username";

NSString * const KEY_PASSWORD = @"com.company.app.password";

 

@implementation ViewController

 

- (void)viewDidLoad {

    [super viewDidLoad];

    // Do any additional setup after loading the view, typically from a nib.

    

    NSMutableDictionary *userNamePasswordKVPairs = [NSMutableDictionary dictionary];

    [userNamePasswordKVPairs setObject:@"userName" forKey:KEY_USERNAME];

    [userNamePasswordKVPairs setObject:@"password" forKey:KEY_PASSWORD];

    NSLog(@"%@", userNamePasswordKVPairs); //KV

    

    // A、将用户名和密码写入keychain

    [KeyChain save:KEY_USERNAME_PASSWORD data:userNamePasswordKVPairs];

    

    // B、从keychain中读取用户名和密码

    NSMutableDictionary *readUsernamePassword = (NSMutableDictionary *)[KeyChain load:KEY_USERNAME_PASSWORD];

    NSString *userName = [readUsernamePassword objectForKey:KEY_USERNAME];

    NSString *password = [readUsernamePassword objectForKey:KEY_PASSWORD];

    NSLog(@"username = %@", userName);

    NSLog(@"password = %@", password);

    

    // C、将用户名和密码从keychain中删除

    [KeyChain delete:KEY_USERNAME_PASSWORD];

    

}

 

- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}

 

@end

 

实际遇到的问题与解决方法

按理说,运行项目,应该得到的结果应该是得到输出:

2016-10-02 04:10:59.463 keychain[16953:1510836] username = userName

2016-10-02 04:10:59.463 keychain[16953:1510836] password = password

然而我在尝试的时候,却出现了两个问题:

第一个问题

在输出中出现了大量个怪异输出如下:

subsystem: com.apple.UIKit, category: HIDEventFiltered, enable_level: 0, persist_level: 0, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 1, privacy_setting: 2, enable_private_data: 0  

subsystem: com.apple.UIKit, category: HIDEventIncoming, enable_level: 0, persist_level: 0, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 1, privacy_setting: 2, enable_private_data: 0  

subsystem: com.apple.BaseBoard, category: MachPort, enable_level: 1, persist_level: 0, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 0, privacy_setting: 0, enable_private_data: 0  

subsystem: com.apple.UIKit, category: StatusBar, enable_level: 0, persist_level: 0, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 1, privacy_setting: 2, enable_private_data: 0  

subsystem: com.apple.BackBoardServices.fence, category: App, enable_level: 1, persist_level: 0, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 0, privacy_setting: 0, enable_private_data: 0  

通过在Stackoverflow上查询,发现这是Xcode8更新后出现的普遍问题,解决方法是:

  1. 按顺序选择Product–>Scheme–>Edict Scheme;
  2. 选择Run中的Arguments;
  3. 选择Environment Variables;
  4. 添加OS_ACTIVITY_MODE字段并设置Value值为disable;
  5. 点击Close即可。

而后运行项目,问题解决。

第二个问题

程序关于用户名和密码的输出实际为:

2016-10-02 04:10:59.463 keychain[16953:1510836] username = (null)

2016-10-02 04:10:59.463 keychain[16953:1510836] password = (null)

通过分步NSLog可以发现,NSMutableDictionary类的对象userNamePasswordKVPairs是有键值对的,由此可以判断问题还是在KeyChain类的方法本身。受第一个问题的启发,猜测可能和Xcode或iOS的更新有关,经过查询Stackoverflow,证实了这一个猜测。解决方法:

  1. 在Xcode左侧文件列表中选中项目名称,然后选中TARGETS;
  2. 中间界面选中Capabilities;
  3. 将Keychain Sharing的开关由off改为on,如下图所示:

而后运行项目,问题解决,得到输出如下

2016-10-02 04:10:59.463 keychain[16953:1510836] username = userName

2016-10-02 04:10:59.463 keychain[16953:1510836] password = password

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

转载于:https://my.oschina.net/fllKevin/blog/1806375

你可能感兴趣的文章
mac安装weblogic 12
查看>>
CoAP——下一个将被DDos***严重滥用的协议
查看>>
Mui、HTML5+、5+Runtime的关系说明
查看>>
Squid日志分析与访问控制详解
查看>>
Angular 应用的外壳
查看>>
6-4补充1 pxe and cobbler
查看>>
【精益生产】企业成功开展全面质量管理的六大要素
查看>>
shiro-自定义的Realm中使用自己的密码验证规则
查看>>
Python爬虫入门学习线路图最全知识点详解
查看>>
5G时代,两大视频会议系统趋势明显!
查看>>
八年程序猿被裁只用了5分钟,月薪20K的Java程序员就很厉害?
查看>>
小波说雨燕 第三季 构建 swift UI 之 UI组件集-视图集(三)Activity Indicators视图 学习笔记...
查看>>
浅谈物联网所面对的问题和挑衅
查看>>
期刊发表要求都有哪些
查看>>
php导出数据到excel,身份证等数字常用格式
查看>>
我的友情链接
查看>>
Grub 配置解析(1)
查看>>
Oracle数据库管理常用的监控脚本------极大的简化运维工作
查看>>
Oracle10g之SGA与PGA分配建议
查看>>
骆驼命名发
查看>>