沙盒详解
1、IOS沙盒机制
IOS应用程序只能在为该改程序创建的文件系统中读取文件,不可以去其它地方访问,此区域被成为沙盒,所以所有的非代码文件都要保存在此,例如图像,图标,声音,映像,属性列表,文本文件等。
1.1、每个应用程序都有自己的存储空间
1.2、应用程序不能翻过自己的围墙去访问别的存储空间的内容
1.3、应用程序请求的数据都要通过权限检测,假如不符合条件的话,不会被放行。
通过这张图只能从表层上理解sandbox是一种安全体系,应用程序的所有操作都要通过这个体系来执行,其中核心内容是:sandbox对应用程序执行各种操作的权限限制。
2、打开模拟器沙盒目录
下面看看模拟器的沙盒文件夹在mac电脑上的什么位置。
文件都在个人用户名文件夹下的一个隐藏文件夹里,中文叫资源库,他的目录其实是Library。
2.1 方法1、可以设置显示隐藏文件,然后在Finder下直接打开。设置查看隐藏文件的方法如下:打开终端,输入命名
显示Mac隐藏文件的命令:defaults write com.apple.finder AppleShowAllFiles -bool true
隐藏Mac隐藏文件的命令:defaults write com.apple.finder AppleShowAllFiles -bool false
输完单击Enter键,退出终端,
重新启动Finder就可以了重启Finder:鼠标单击窗口左上角的苹果标志-->强制退出-->Finder-->
现在能看到资源库文件夹了。
打开资源库后找到/Application Support/iPhone Simulator/文件夹。这里面就是模拟器的各个程序的沙盒目录了。
2.2 方法2、这种方法更方便,在Finder上点->前往->前往文件夹,输入/Users/username/Library/Application Support/iPhone Simulator/ 前往。
username这里写你的用户名。
3、目录结构
默认情况下,每个沙盒含有3个文件夹:Documents, Library 和 tmp。因为应用的沙盒机制,应用只能在几个目录下读写文件
Documents:苹果建议将程序中建立的或在程序中浏览到的文件数据保存在该目录下,iTunes备份和恢复的时候会包括此目录
Library:存储程序的默认设置或其它状态信息;
Library/Caches:存放缓存文件,iTunes不会备份此目录,此目录下文件不会在应用退出删除
tmp:提供一个即时创建临时文件的地方。
iTunes在与iPhone同步时,备份所有的Documents和Library文件。
iPhone在重启时,会丢弃所有的tmp文件。
我们创建一个IosSandbox的项目来展开沙盒和文件读写等操作的练习。
创建后找到模拟器上对应的目录,
这是目录全展开了。
这是上面提到的三个目录 :Documents、Library、 tmp
下篇介绍目录路径获取和文件操作
我们看看如何获取应用程序沙盒目录。包括真机的沙盒的目录。
1、获取程序的Home目录
NSString *homeDirectory = NSHomeDirectory(); NSLog(@"path:%@", homeDirectory); |
打印结果:
2012-06-17 14:00:06.098 IosSandbox[3536:f803] /Users/rongfzh/Library/Application Support/iPhone Simulator/5.1/ Applications/3B8EC78A-5EEE-4C2F-B0CB-4C3F02B996D2 |
那在真机上的目录有是怎么样的呢?我们看看
2012-06-17 14:25:47.059 IosSandbox[4281:f803] /var/mobile/Applications/3B8EC78A-5EEE-4C2F-B0CB-4C3F02B996D2
可见,真机上的目录是/var/mobile/Applications/这个目录下的,和模拟器不一样。这个是Home目录,其他的子目录和模拟器一样。
2、获取document目录
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *path = [paths objectAtIndex:0]; NSLog(@"path:%@", path); |
打印结果
2012-06-17 14:00:06.099 IosSandbox[3536:f803] path:/Users/rongfzh/Library/Application Support/iPhone Simulator/5.1 /Applications/3B8EC78A-5EEE-4C2F-B0CB-4C3F02B996D2/Documents |
3、获取Cache目录
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); NSString *path = [paths objectAtIndex:0]; NSLog(@"%@", path); |
打印结果:
2012-06-17 14:03:50.431 IosSandbox[3628:f803] /Users/rongfzh/Library/Application Support/iPhone Simulator/5.1 /Applications/3B8EC78A-5EEE-4C2F-B0CB-4C3F02B996D2/Library/Caches |
4、获取Library目录
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); NSString *path = [paths objectAtIndex:0]; NSLog(@"%@", path); |
打印结果:
2012-06-17 14:07:17.544 IosSandbox[3733:f803] /Users/rongfzh/Library/Application Support/iPhone Simulator/5.1 /Applications/3B8EC78A-5EEE-4C2F-B0CB-4C3F02B996D2/Library |
5、获取Tmp目录
NSString *tmpDir = NSTemporaryDirectory(); NSLog(@"%@", tmpDir); |
打印结果:
2012-06-17 14:08:07.824 IosSandbox[3782:f803] /var/folders/g7/246bh79130zblw0yjjtc55cw0000gn/T/ |
6、写入文件
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *docDir = [paths objectAtIndex:0]; if (!docDir) { NSLog(@"Documents 目录未找到"); } NSArray *array = [[NSArray alloc] initWithObjects:@"内容",@"content",nil]; NSString *filePath = [docDir stringByAppendingPathComponent:@"testFile.txt"]; [array writeToFile:filePath atomically:YES]; |
注:我们在真机上也运行一下,把文件写入,下一步从真机上把内容读取出来。
写入输入 array ,里面是两个字符串,一会我们读出来打印。
写入我们在程序沙盒目录下看到文件 testFile.txt
打开文件看到的内容是这样的,是个xml格式的plist文件,数据格式保存了内容。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <array> <string>内容</string> <string>content</string> </array> </plist> |
7、读取文件
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *docDir = [paths objectAtIndex:0]; NSString *filePath = [docDir stringByAppendingPathComponent:@"testFile.txt"]; NSArray *array = [[NSArray alloc]initWithContentsOfFile:filePath]; NSLog(@"%@", array); |
打印结果:
把上面的文件解析后,把内容打印出来了。
2012-06-17 14:14:46.249 IosSandbox[3918:f803] ( "\U5185\U5bb9", content ) |
真机上读取并打印文件路径:
2012-06-17 14:25:47.059 IosSandbox[4281:f803] /var/mobile/Applications/3B8EC78A-5EEE-4C2F-B0CB-4C3F02B996D2/Documents/testFile.txt
(
"\U5185\U5bb9",
content
)
真机上也能写入和打印。
我们看看NSFileManager如何使用。包括创建文件,目录,删除,遍历目录等。
1、在Documents里创建目录
创建一个叫test的目录,先找到Documents的目录,
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; NSLog(@"documentsDirectory%@",documentsDirectory); NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *testDirectory = [documentsDirectory stringByAppendingPathComponent:@"test"]; // 创建目录 [fileManager createDirectoryAtPath:testDirectory withIntermediateDirectories:YES attributes:nil error:nil]; |
启动程序,这时候目录就创建了:
2、在test目录下创建文件
创建文件怎么办呢?接着上面的代码 testPath 要用stringByAppendingPathComponent拼接上你要生成的文件名,比如test00.txt。这样才能在test下写入文件。
testDirectory是上面代码生成的路径哦,不要忘了。我往test文件夹里写入三个文件,test00.txt ,test22.txt,text.33.txt。内容都是写入内容,write String。
实现代码如下:
NSString *testPath = [testDirectory stringByAppendingPathComponent:@"test00.txt"]; NSString *testPath2 = [testDirectory stringByAppendingPathComponent:@"test22.txt"]; NSString *testPath3 = [testDirectory stringByAppendingPathComponent:@"test33.txt"]; NSString *string = @"写入内容,write String"; [fileManager createFileAtPath:testPath contents:[string dataUsingEncoding:NSUTF8StringEncoding] attributes:nil]; [fileManager createFileAtPath:testPath2 contents:[string dataUsingEncoding:NSUTF8StringEncoding] attributes:nil]; [fileManager createFileAtPath:testPath3 contents:[string dataUsingEncoding:NSUTF8StringEncoding] attributes:nil]; |
看下面的图,三个文件都出来了,内容也对。
在Documents目录下创建就更简单了,不用加test就ok了
3、获取目录列里所有文件名
两种方法获取:subpathsOfDirectoryAtPath 和 subpathsAtPath
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; NSLog(@"documentsDirectory%@",documentsDirectory); NSFileManager *fileManage = [NSFileManager defaultManager]; NSString *myDirectory = [documentsDirectory stringByAppendingPathComponent:@"test"]; NSArray *file = [fileManage subpathsOfDirectoryAtPath: myDirectory error:nil]; NSLog(@"%@",file); NSArray *files = [fileManage subpathsAtPath: myDirectory ]; NSLog(@"%@",files); |
获取上面刚才test文件夹里的文件名
打印结果
2012-06-17 23:23:19.684 IosSandbox[947:f803] fileList:( ".DS_Store", "test00.txt", "test22.txt", "test33.txt" ) 2012-06-17 23:23:19.686 IosSandbox[947:f803] fileLit( ".DS_Store", "test00.txt", "test22.txt", "test33.txt" ) |
两个方法都可以,隐藏的文件也打印出来了。
4、fileManager使用操作当前目录
//创建文件管理器 NSFileManager *fileManager = [NSFileManager defaultManager]; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; //更改到待操作的目录下 [fileManager changeCurrentDirectoryPath:[documentsDirectory stringByExpandingTildeInPath]]; //创建文件fileName文件名称,contents文件的内容,如果开始没有内容可以设置为nil,attributes文件的属性,初始为nil NSString * fileName = @"testFileNSFileManager.txt"; NSArray *array = [[NSArray alloc] initWithObjects:@"hello world",@"hello world1", @"hello world2",nil]; [fileManager createFileAtPath:fileName contents:array attributes:nil]; |
这样就创建了testFileNSFileManager.txt并把三个hello world写入文件了
changeCurrentDirectoryPath目录更改到当前操作目录时,做文件读写就很方便了,不用加上全路径
5、删除文件
接上面的代码,remove就ok了。
[fileManager removeItemAtPath:fileName error:nil]; |
6、混合数据的读写
用NSMutableData创建混合数据,然后写到文件里。并按数据的类型把数据读出来
6.1写入数据:
NSString * fileName = @"testFileNSFileManager.txt"; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; //获取文件路径 NSString *path = [documentsDirectory stringByAppendingPathComponent:fileName]; //待写入的数据 NSString *temp = @"nihao 世界"; int dataInt = 1234; float dataFloat = 3.14f; //创建数据缓冲 NSMutableData *writer = [[NSMutableData alloc] init]; //将字符串添加到缓冲中 [writer appendData:[temp dataUsingEncoding:NSUTF8StringEncoding]]; //将其他数据添加到缓冲中 [writer appendBytes:&dataInt length:sizeof(dataInt)]; [writer appendBytes:&dataFloat length:sizeof(dataFloat)]; //将缓冲的数据写入到文件中 [writer writeToFile:path atomically:YES]; |
我们看看数据怎么样了:
我们看到后面的是乱码,那是中文被转成了NSData后,还有int float的二进制
6.2读取刚才写入的数据:
//读取数据: int intData; float floatData = 0.0; NSString *stringData; NSData *reader = [NSData dataWithContentsOfFile:path]; stringData = [[NSString alloc] initWithData:[reader subdataWithRange:NSMakeRange(0, [temp length])] encoding:NSUTF8StringEncoding]; [reader getBytes:&intData range:NSMakeRange([temp length], sizeof(intData))]; [reader getBytes:&floatData range:NSMakeRange([temp length] + sizeof(intData), sizeof(floatData))]; NSLog(@"stringData:%@ intData:%d floatData:%f", stringData, intData, floatData); |
打印出来的结果:
2012-06-17 23:51:14.723 IosSandbox[1285:f803] stringData:nihao hello! intData:1234332 floatData:3.140000
这里把写入的汉字改成了 hello。因为[temp length]算长度是,把中文算成一位了,出来的结果有误。
属性列表
属性列表文件是一种XML文件,Foundation框架中的数组和字典等都可以于属性列表文件相互转换。
NSArray类常用读写属性列表文件的方法:
+arrayWithContentsOfFile:类级构造方法,用于从属性列表文件中读取数据,创建NSArray对象。
-initWithContentsOfFile:实例构造方法,用于从属性列表文件中读取数据,创建NSArray对象。
-writeToFile:atomically:该方法把NSArray对象写入到属性列表文件中,第一个参数是文件名,第二个参数为是否使用辅助文件,如果为YES,则先写入到一个辅助文件,然后辅助文件再重新命名为目标文件,如果为NO,则直接写入到目标文件。
NSDictionary类常用读写属性列表文件的方法:
+dictionaryWithContentsOfFile:类级构造方法,用于从属性列表文件中读取数据,创建NSDictionary对象。
-initWithContentsOfFile:实例构造方法,用于从属性列表文件中读取数据,创建NSDictionary对象。
-writeToFile:atomically:该方法将NSDictionary对象写入到属性列表文件中。
属性列表文件数据持久化具体方法,可参考以下实现方式:
假如在项目中手工创建了一个Contacts.plist文件,并在该文件中添加了几条数据,如下图所示。
当然也可以通过代码直接创建plist文件。
接下来需要做的是将项目资源的Contacts.plist文件中数据复制到沙箱Documents目录下。
//对文件进行预处理,判断在Documents目录下是否存在plist文件,如果不存在则从资源目录下复制一个。 -(void)createEditableCopyOfDatabaseIfNeeded { NSFileManager *fileManager=[NSFileManager defaultManager]; NSString *writableDBPath=[self applicationDocumentsDirectoryFile]; BOOL dbexits=[fileManager fileExistsAtPath:writableDBPath]; if (!dbexits) { NSString *defaultDBPath=[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Contacts.plist"]; NSError *error; BOOL success=[fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error]; if (!success) { NSAssert1(0,@"错误写入文件:‘%@’",[error localizedDescription]); } } } //获取放置在沙箱Documents目录下的文件的完整路径 -(NSString *)applicationDocumentsDirectoryFile { NSString *documentDirectory=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; NSString *path=[documentDirectory stringByAppendingPathComponent:@"Contacts.plist"]; return path; }
createEditableCopyOfDatabaseIfNeeded方法中:
NSFileManager的copyItemAtPath:toPath:error:方法实现文件复制。
NSAssert1是Foundation框架提供的宏,它在断言失败的情况下抛出异常,类似的还有NSAssert和NSAssert2等。
applicationDocumentsDirectoryFile方法中:
stringByAppendingPathComponent:能够在目录后面追加文件名,返回完整的文件路径。
沙箱Documents目录下成功生成plist文件之后,就可以进行增、删、改、查操作了。可参考如下代码:
NSString *path=[self applicationDocumentsDirectoryFile]; //将属性列表文件内容读取到array变量中,也就是获取了属性列表文件中全部的数据集合 NSMutableArray *array=[[NSMutableArray alloc]initWithContentsOfFile:path]; //向array中添加一条新记录 NSDictionary *newContact=[NSDictionary dictionaryWithObjects:@[contact.Title,contact.Type] forKeys:@[@"Title",@"Type"]]; [array addObject:newContact]; //删除array中一条记录 [array removeObjectAtIndex:0]; //删除array中全部记录 [array removeAllObjects]; for (NSDictionary* dict in array) { //通过for循环,找到需要修改的数据项,进行修改数据 [dict setValue:@"Test" forKey:@"Title"]; } //将array重新写入属性列表文件中 [array writeToFile:path atomically:YES];
注:完成后,需要选择Product->Clean菜单项清除一些再编译。
iOS开发UI篇—ios应用数据存储方式(归档)
一、简单说明
在使用plist进行数据存储和读取,只适用于系统自带的一些常用类型才能用,且必须先获取路径相对麻烦;
偏好设置(将所有的东西都保存在同一个文件夹下面,且主要用于存储应用的设置信息)
归档:因为前两者都有一个致命的缺陷,只能存储常用的类型。归档可以实现把自定义的对象存放在文件中。
二、代码示例
1.文件结构
2.代码示例
YYViewController.m文件
1 // 2 // YYViewController.m 3 // 02-归档 4 // 5 // Created by apple on 14-6-7. 6 // Copyright (c) 2015年 itcase. All rights reserved. 7 // 8 9 #import "YYViewController.h" 10 #import "YYPerson.h" 11 12 @interface YYViewController () 13 - (IBAction)saveBtnOnclick:(id)sender; 14 - (IBAction)readBtnOnclick:(id)sender; 15 16 @end 17 18 @implementation YYViewController 19 20 - (void)viewDidLoad 21 { 22 [super viewDidLoad]; 23 } 24 25 26 - (IBAction)saveBtnOnclick:(id)sender { 27 //1.创建对象 28 YYPerson *p=[[YYPerson alloc]init]; 29 [email protected]"iCocos"; 30 p.age=23; 31 p.height=1.7; 32 33 //2.获取文件路径 34 NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject]; 35 NSString *path=[docPath stringByAppendingPathComponent:@"person.yangyang"]; 36 NSLog(@"path=%@",path); 37 38 //3.将自定义的对象保存到文件中 39 [NSKeyedArchiver archiveRootObject:p toFile:path]; 40 41 } 42 43 - (IBAction)readBtnOnclick:(id)sender { 44 //1.获取文件路径 45 NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject]; 46 NSString *path=[docPath stringByAppendingPathComponent:@"person.yangyang"]; 47 NSLog(@"path=%@",path); 48 49 //2.从文件中读取对象 50 YYPerson *p=[NSKeyedUnarchiver unarchiveObjectWithFile:path]; 51 NSLog(@"%@,%d,%.1f",p.name,p.age,p.height); 52 } 53 @end
新建一个person类
YYPerson.h文件
1 // 2 // YYPerson.h 3 // 02-归档 4 // 5 // Created by apple on 14-6-7. 6 // Copyright (c) 2014年 itcase. All rights reserved. 7 // 8 9 #import <Foundation/Foundation.h> 10 11 // 如果想将一个自定义对象保存到文件中必须实现NSCoding协议 12 @interface YYPerson : NSObject<NSCoding> 13 14 //姓名 15 @property(nonatomic,copy)NSString *name; 16 //年龄 17 @property(nonatomic,assign)int age; 18 //身高 19 @property(nonatomic,assign)double height; 20 @end
YYPerson.m文件
1 // 2 // YYPerson.m 3 // 02-归档 4 // 5 // Created by apple on 14-6-7. 6 // Copyright (c) 2014年 itcase. All rights reserved. 7 // 8 9 #import "YYPerson.h" 10 11 @implementation YYPerson 12 13 // 当将一个自定义对象保存到文件的时候就会调用该方法 14 // 在该方法中说明如何存储自定义对象的属性 15 // 也就说在该方法中说清楚存储自定义对象的哪些属性 16 -(void)encodeWithCoder:(NSCoder *)aCoder 17 { 18 NSLog(@"调用了encodeWithCoder:方法"); 19 [aCoder encodeObject:self.name forKey:@"name"]; 20 [aCoder encodeInteger:self.age forKey:@"age"]; 21 [aCoder encodeDouble:self.height forKey:@"height"]; 22 } 23 24 // 当从文件中读取一个对象的时候就会调用该方法 25 // 在该方法中说明如何读取保存在文件中的对象 26 // 也就是说在该方法中说清楚怎么读取文件中的对象 27 -(id)initWithCoder:(NSCoder *)aDecoder 28 { 29 NSLog(@"调用了initWithCoder:方法"); 30 //注意:在构造方法中需要先初始化父类的方法 31 if (self=[super init]) { 32 self.name=[aDecoder decodeObjectForKey:@"name"]; 33 self.age=[aDecoder decodeIntegerForKey:@"age"]; 34 self.height=[aDecoder decodeDoubleForKey:@"height"]; 35 } 36 return self; 37 } 38 @end
3.打印效果和两个重要的错误提示
点击保存按钮和读取按钮,成功打印结果如下:
关于不实现两个协议方法的错误提示:
-(void)encodeWithCoder:(NSCoder *)aCoder方法:
-(id)initWithCoder:(NSCoder *)aDecoder方法:
三、继承类中的使用
新建一个学生类,让这个类继承自Preson这个类,增加一个体重的属性。
YYstudent.h文件
1 // 2 // YYstudent.h 3 // 02-归档 4 // 5 // Created by apple on 14-6-7. 6 // Copyright (c) 2014年 itcase. All rights reserved. 7 // 8 9 #import "YYPerson.h" 10 11 @interface YYstudent : YYPerson 12 //增加一个体重属性 13 @property(nonatomic,assign) double weight; 14 @end
YYstudent.m文件
1 // 2 // YYstudent.m 3 // 02-归档 4 // 5 // Created by apple on 14-6-7. 6 // Copyright (c) 2014年 itcase. All rights reserved. 7 // 8 9 #import "YYstudent.h" 10 11 @implementation YYstudent 12 13 //在子类中重写这两个方法 14 - (void)encodeWithCoder:(NSCoder *)aCoder 15 { 16 [super encodeWithCoder:aCoder]; 17 NSLog(@"调用了YYStudent encodeWithCoder"); 18 [aCoder encodeFloat:self.weight forKey:@"weight"]; 19 } 20 21 - (id)initWithCoder:(NSCoder *)aDecoder 22 { 23 if (self = [super initWithCoder:aDecoder]) { 24 NSLog(@"调用了YYstudent initWithCoder"); 25 self.weight = [aDecoder decodeFloatForKey:@"weight"]; 26 } 27 return self; 28 } 29 @end
YYViewController.m文件
1 // 2 // YYViewController.m 3 // 02-归档 4 // 5 // Created by apple on 14-6-7. 6 // Copyright (c) 2014年 itcase. All rights reserved. 7 // 8 9 #import "YYViewController.h" 10 #import "YYPerson.h" 11 #import "YYstudent.h" 12 13 @interface YYViewController () 14 - (IBAction)saveBtnOnclick:(id)sender; 15 - (IBAction)readBtnOnclick:(id)sender; 16 17 @end 18 19 @implementation YYViewController 20 21 - (void)viewDidLoad 22 { 23 [super viewDidLoad]; 24 } 25 26 27 - (IBAction)saveBtnOnclick:(id)sender { 28 //1.创建对象 29 // YYPerson *p=[[YYPerson alloc]init]; 30 // [email protected]"文顶顶"; 31 // p.age=23; 32 // p.height=1.7; 33 34 YYstudent *s=[[YYstudent alloc]init]; 35 [email protected]"wendingding"; 36 s.age=23; 37 s.height=1.7; 38 s.weight=62; 39 //2.获取文件路径 40 NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject]; 41 NSString *path=[docPath stringByAppendingPathComponent:@"person.yangyang"]; 42 NSLog(@"path=%@",path); 43 44 //3.将自定义的对象保存到文件中 45 // [NSKeyedArchiver archiveRootObject:p toFile:path]; 46 [NSKeyedArchiver archiveRootObject:s toFile:path]; 47 48 } 49 50 - (IBAction)readBtnOnclick:(id)sender { 51 //1.获取文件路径 52 NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject]; 53 NSString *path=[docPath stringByAppendingPathComponent:@"person.yangyang"]; 54 NSLog(@"path=%@",path); 55 56 //2.从文件中读取对象 57 // YYPerson *p=[NSKeyedUnarchiver unarchiveObjectWithFile:path]; 58 // NSLog(@"%@,%d,%.1f",p.name,p.age,p.height); 59 YYstudent *s=[NSKeyedUnarchiver unarchiveObjectWithFile:path]; 60 NSLog(@"%@,%d,%.1f,%f",s.name,s.age,s.height,s.weight); 61 } 62 @end
点击保存按钮和读取按钮后的打印输出:
四、重要说明
1.保存数据过程:
//1.创建对象 YYstudent *s=[[YYstudent alloc]init]; [email protected]"wendingding"; s.age=23; s.height=1.7; s.weight=62; //2.获取文件路径 NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject]; NSString *path=[docPath stringByAppendingPathComponent:@"person.yangyang"]; NSLog(@"path=%@",path); //3.将自定义的对象保存到文件中 [NSKeyedArchiver archiveRootObject:s toFile:path];
2.读取数据过程:
//1.获取文件路径 NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject]; NSString *path=[docPath stringByAppendingPathComponent:@"person.yangyang"]; //2.从文件中读取对象 YYstudent *s=[NSKeyedUnarchiver unarchiveObjectWithFile:path];
3.遵守NSCoding协议,并实现该协议中的两个方法。
4.如果是继承,则子类一定要重写那两个方法。因为person的子类在存取的时候,会去子类中去找调用的方法,没找到那么它就去父类中找,所以最后保存和读取的时候新增加的属性会被忽略。需要先调用父类的方法,先初始化父类的,再初始化子类的。
5.保存数据的文件的后缀名可以随意命名。
6.通过plist保存的数据是直接显示的,不安全。通过归档方法保存的数据在文件中打开是乱码的,更安全。
偏好设置
一、简单介绍
很多iOS应用都支持偏好设置,比如保存用户名、密码、字体大小等设置,iOS提供了一套标准的解决方案来为应用加入偏好设置功能
每个应用都有个NSUserDefaults实例,通过它来存取偏好设置。比如,保存用户名、字体大小、是否自动登录
存储位置:
存储形式:
二、代码示例
1.storyboard
2.代码
1 // 2 // YYViewController.m 3 // 01-偏好设置 4 // 5 // Created by apple on 14-6-7. 6 // Copyright (c) 2014年 itcase. All rights reserved. 7 // 8 9 #import "YYViewController.h" 10 //偏好设置 11 @interface YYViewController () 12 /** 13 *保存数据 14 */ 15 - (IBAction)saveData:(id)sender; 16 /** 17 * 读取数据 18 */ 19 - (IBAction)readData:(id)sender; 20 21 @end 22 23 @implementation YYViewController 24 25 - (IBAction)saveData:(id)sender { 26 //1.获取NSUserDefaults对象 27 NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults]; 28 29 //2保存数据(如果设置数据之后没有同步, 会在将来某一时间点自动将数据保存到Preferences文件夹下面) 30 [defaults setObject:@"yangyong" forKey:@"name"]; 31 [defaults setInteger:23 forKey:@"age"]; 32 [defaults setDouble:1.73f forKey:@"height"]; 33 [defaults setObject:@"man" forKey:@"gender"]; 34 35 //3.强制让数据立刻保存 36 [defaults synchronize]; 37 } 38 39 - (IBAction)readData:(id)sender { 40 //1.获取NSUserDefaults对象 41 NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults]; 42 //读取保存的数据 43 NSString *name=[defaults objectForKey:@"name"]; 44 NSString *gender=[defaults objectForKey:@"gender"]; 45 NSInteger age=[defaults integerForKey:@"age"]; 46 double height=[defaults doubleForKey:@"height"]; 47 //打印数据 48 NSLog(@"name=%@,gender=%@,age=%d,height=%.1f",name,gender,age,height); 49 } 50 @end
3.点击保存数据,读取数据按钮打印如下
三、补充说明
1.保存数据
//1.获取NSUserDefaults对象 NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults]; //2保存数据 [defaults setObject:@"yangyong" forKey:@"name"]; [defaults setInteger:23 forKey:@"age"]; [defaults setDouble:1.73f forKey:@"height"]; [defaults setObject:@"man" forKey:@"gender"]; //3.强制让数据立刻保存 [defaults synchronize];
2.读取数据
//1.获取NSUserDefaults对象 NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults]; //2.读取保存的数据 NSString *name=[defaults objectForKey:@"name"]; NSString *gender=[defaults objectForKey:@"gender"]; NSInteger age=[defaults integerForKey:@"age"]; double height=[defaults doubleForKey:@"height"];
3.重要说明
(1)偏好设置是专门用来保存应用程序的配置信息的, 一般情况不要在偏好设置中保存其他数据。如果利用系统的偏好设置来存储数据, 默认就是存储在Preferences文件夹下面的,偏好设置会将所有的数据都保存到同一个文件中。
(2)使用偏好设置对数据进行保存之后, 它保存到系统的时间是不确定的,会在将来某一时间点自动将数据保存到Preferences文件夹下面,如果需要即刻将数据存储,可以使用[defaults synchronize];
(3)注意点:所有的信息都写在一个文件中,对比简单的plist可以保存和读取基本的数据类型。
(4)步骤:获取NSuserDefaults,保存(读取)数据
SQLite3详解
SQLite是嵌入式的和轻量级的SQL数据库。SQLite是由C实现的。广泛用于包括浏览器(支持HTML5的大部分浏览器,IE除外)、iOS、Android以及一些便携需求的小型web应用系统。
1 使用原因:存储、检索信息
2 SQLite是MySQL精简版。但无需服务器就能进行。
3 两个限制:1)必须手动创建数据库 2)没有面向对象的接口。
4 如何手动创建数据库。
使用SQLite前的准备
使用SQLite是很多做iOS开发中第一次面对C的情况,包括我。因为SQLite是C写的,Objective-C可以直接使用C代码。在SQLite前,一般都会使用Cocoa Touch框架,都是基于Objective-C的。
首先,添加framework:libsqlite3.0.dylib
需要在对应文件的头文件中加入:
#import "sqlite3.h" |
并在Frameworks中加入所需的库,否则会报错:
Undefined symbols: "_sqlite3_open", referenced from: |
加入库的方法是:
或者点击 你的应用程序名,(最上面带图标的那个)中间视图会出现frameworks的图表,左下脚有一个加号,就是添加新的frameworks的地方了,在搜索里输入需要的......,添加进去就OK了!选择sqlite库:
选择完的效果:
1 static NoteDAO *sharedManager = nil; 2 3 + (NoteDAO*)sharedManager 4 { 5 static dispatch_once_t once; 6 dispatch_once(&once, ^{ 7 8 sharedManager = [[self alloc] init]; 9 [sharedManager createEditableCopyOfDatabaseIfNeeded]; 10 11 12 }); 13 return sharedManager; 14 } 15 16 //打开数据库 17 - (void)createEditableCopyOfDatabaseIfNeeded { 18 19 NSString *writableDBPath = [self applicationDocumentsDirectoryFile]; 20 21 if (sqlite3_open([writableDBPath UTF8String], &db) != SQLITE_OK) { 22 sqlite3_close(db); 23 NSAssert(NO,@"数据库打开失败。"); 24 } else { 25 char *err; 26 NSString *createSQL = [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS Note (cdate TEXT PRIMARY KEY, content TEXT);"]; 27 if (sqlite3_exec(db,[createSQL UTF8String],NULL,NULL,&err) != SQLITE_OK) { 28 sqlite3_close(db); 29 NSAssert1(NO, @"建表失败, %s", err); 30 } 31 sqlite3_close(db); 32 } 33 } 34 35 - (NSString *)applicationDocumentsDirectoryFile { 36 NSString *documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; 37 NSString *path = [documentDirectory stringByAppendingPathComponent:DBFILE_NAME]; 38 39 return path; 40 } 41 42 43 //插入Note方法 44 -(int) create:(Note*)model 45 { 46 47 NSString *path = [self applicationDocumentsDirectoryFile]; 48 49 if (sqlite3_open([path UTF8String], &db) != SQLITE_OK) { 50 sqlite3_close(db); 51 NSAssert(NO,@"数据库打开失败。"); 52 } else { 53 54 NSString *sqlStr = @"INSERT OR REPLACE INTO note (cdate, content) VALUES (?,?)"; 55 56 sqlite3_stmt *statement; 57 //预处理过程 58 if (sqlite3_prepare_v2(db, [sqlStr UTF8String], -1, &statement, NULL) == SQLITE_OK) { 59 NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 60 [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; 61 NSString *nsdate = [dateFormatter stringFromDate:model.date]; 62 63 //绑定参数开始 64 sqlite3_bind_text(statement, 1, [nsdate UTF8String], -1, NULL); 65 sqlite3_bind_text(statement, 2, [model.content UTF8String], -1, NULL); 66 67 //执行插入 68 if (sqlite3_step(statement) != SQLITE_DONE) { 69 NSAssert(NO, @"插入数据失败。"); 70 } 71 } 72 73 sqlite3_finalize(statement); 74 sqlite3_close(db); 75 } 76 77 return 0; 78 } 79 80 //删除Note方法 81 -(int) remove:(Note*)model 82 { 83 NSString *path = [self applicationDocumentsDirectoryFile]; 84 85 if (sqlite3_open([path UTF8String], &db) != SQLITE_OK) { 86 sqlite3_close(db); 87 NSAssert(NO,@"数据库打开失败。"); 88 } else { 89 90 NSString *sqlStr = @"DELETE from note where cdate =?"; 91 92 sqlite3_stmt *statement; 93 //预处理过程 94 if (sqlite3_prepare_v2(db, [sqlStr UTF8String], -1, &statement, NULL) == SQLITE_OK) { 95 NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 96 [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; 97 NSString *nsdate = [dateFormatter stringFromDate:model.date]; 98 99 //绑定参数开始 100 sqlite3_bind_text(statement, 1, [nsdate UTF8String], -1, NULL); 101 //执行 102 if (sqlite3_step(statement) != SQLITE_DONE) { 103 NSAssert(NO, @"删除数据失败。"); 104 } 105 } 106 107 sqlite3_finalize(statement); 108 sqlite3_close(db); 109 } 110 111 return 0; 112 } 113 114 //修改Note方法 115 -(int) modify:(Note*)model 116 { 117 118 NSString *path = [self applicationDocumentsDirectoryFile]; 119 120 if (sqlite3_open([path UTF8String], &db) != SQLITE_OK) { 121 sqlite3_close(db); 122 NSAssert(NO,@"数据库打开失败。"); 123 } else { 124 125 NSString *sqlStr = @"UPDATE note set content=? where cdate =?"; 126 127 sqlite3_stmt *statement; 128 //预处理过程 129 if (sqlite3_prepare_v2(db, [sqlStr UTF8String], -1, &statement, NULL) == SQLITE_OK) { 130 131 NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 132 [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; 133 NSString *nsdate = [dateFormatter stringFromDate:model.date]; 134 135 //绑定参数开始 136 sqlite3_bind_text(statement, 1, [model.content UTF8String], -1, NULL); 137 sqlite3_bind_text(statement, 2, [nsdate UTF8String], -1, NULL); 138 //执行 139 if (sqlite3_step(statement) != SQLITE_DONE) { 140 NSAssert(NO, @"修改数据失败。"); 141 } 142 } 143 144 sqlite3_finalize(statement); 145 sqlite3_close(db); 146 } 147 return 0; 148 } 149 150 //查询所有数据方法 151 -(NSMutableArray*) findAll 152 { 153 154 NSString *path = [self applicationDocumentsDirectoryFile]; 155 NSMutableArray *listData = [[NSMutableArray alloc] init]; 156 157 if (sqlite3_open([path UTF8String], &db) != SQLITE_OK) { 158 sqlite3_close(db); 159 NSAssert(NO,@"数据库打开失败。"); 160 } else { 161 162 NSString *qsql = @"SELECT cdate,content FROM Note"; 163 164 sqlite3_stmt *statement; 165 //预处理过程 166 if (sqlite3_prepare_v2(db, [qsql UTF8String], -1, &statement, NULL) == SQLITE_OK) { 167 168 NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 169 [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; 170 171 //执行 172 while (sqlite3_step(statement) == SQLITE_ROW) { 173 char *cdate = (char *) sqlite3_column_text(statement, 0); 174 NSString *nscdate = [[NSString alloc] initWithUTF8String: cdate]; 175 176 char *content = (char *) sqlite3_column_text(statement, 1); 177 NSString * nscontent = [[NSString alloc] initWithUTF8String: content]; 178 179 Note* note = [[Note alloc] init]; 180 note.date = [dateFormatter dateFromString:nscdate]; 181 note.content = nscontent; 182 183 [listData addObject:note]; 184 185 } 186 } 187 188 sqlite3_finalize(statement); 189 sqlite3_close(db); 190 191 } 192 return listData; 193 } 194 195 //按照主键查询数据方法 196 -(Note*) findById:(Note*)model 197 { 198 199 NSString *path = [self applicationDocumentsDirectoryFile]; 200 201 if (sqlite3_open([path UTF8String], &db) != SQLITE_OK) { 202 sqlite3_close(db); 203 NSAssert(NO,@"数据库打开失败。"); 204 } else { 205 206 NSString *qsql = @"SELECT cdate,content FROM Note where cdate =?"; 207 208 sqlite3_stmt *statement; 209 //预处理过程 210 if (sqlite3_prepare_v2(db, [qsql UTF8String], -1, &statement, NULL) == SQLITE_OK) { 211 //准备参数 212 NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 213 [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; 214 NSString *nsdate = [dateFormatter stringFromDate:model.date]; 215 //绑定参数开始 216 sqlite3_bind_text(statement, 1, [nsdate UTF8String], -1, NULL); 217 218 //执行 219 if (sqlite3_step(statement) == SQLITE_ROW) { 220 char *cdate = (char *) sqlite3_column_text(statement, 0); 221 NSString *nscdate = [[NSString alloc] initWithUTF8String: cdate]; 222 223 char *content = (char *) sqlite3_column_text(statement, 1); 224 NSString * nscontent = [[NSString alloc] initWithUTF8String: content]; 225 226 Note* note = [[Note alloc] init]; 227 note.date = [dateFormatter dateFromString:nscdate]; 228 note.content = nscontent; 229 230 sqlite3_finalize(statement); 231 sqlite3_close(db); 232 233 return note; 234 } 235 } 236 237 sqlite3_finalize(statement); 238 sqlite3_close(db); 239 240 } 241 return nil; 242 }
注:SQLite中也没有定义日期时间类型,日期时间可以用TEXT,REAL,orINTEGER存储
TEXT:存储为字符串("YYYY-MM-DDHH:MM:SS.SSS").
REAL:asJuliandaynumbers,thenumberofdayssincenooninGreenwichonNovember24,4714B.C.accordingtotheprolepticGregoriancalendar.
INTEGER:asUnixTime,thenumberofsecondssince1970-01-0100:00:00UTC.
SQLiteTypeAffinity(类型检测)
用于自动检测值的类型,以下列举Affinity如何决定类型的规则
(1)如果类型声明中有int,则使用INTEGERaffinity.
(2)如果类型声明中有"CHAR","CLOB",or"TEXT",则使用Textaffinity
(3)如果类型声明中有BLOB或没有指定类型,则使用affinityNONE
http://mobile.51cto.com/iphone-321872.htm
(4)如果类型声明中有"REAL","FLOA",or"DOUB",则使用REALaffinity
(5)否则使用Numericaffinity
类型比较NULL
memcmp函数原型
intmemcmp(constvoid*ptr1,constvoid*ptr2,size_tnum);
比较两个指针指向内存的前num个byte
比较之前的类型转换
l(INTEGER,REALorNUMERIC)和(TEXTorNONE)比较,则TEXT,NONE会被转换成NUMERIC
lTEXT和NONE比较,则NONE会被转换成TEXT
其他情况直接比较。
iOS SQLite3具有的数据类型
NULL:NULLvalue
Integer:值是signedinteger类型,大小可以是1,2,3,4,6,8bytes
REAL:浮点类型
TEXT:以UTF-8,UTF-16BEorUTF-16LE编码存储的字符类型
BLOB:二进制数据
其它数据类型说明
在Cocoa环境下,如果你想使用数据库(如sqlite),你可以使用sql语句的方式通过相关的工具类进行数据库的直接操作。当然你也可以通过别人封装之后的一些简单框架,使得你的操作更加简单(如FMDB BNRPersistence)。
Cocoa框架本身提供了CoreData这个API可方便的让开发者通过操作对象的方式在操作数据库。CoreData是一个对象图(object graph)以及持久化的管理框架。我们可以通过CoreData创对象,设置好象之间的关系,然后将其持久化(我们甚至可以使用内存数据库),或者从硬盘上将持久化后的数据加载到内存中。对象图,我们可以创建一个个的对象,并维持不同对象之间的关系,一对一,一对多等。
CoreData有大量的特性,诸如支持Redo,Undo的功能,这些很多Document based的程序中显得非常的有用。提供数据model结构变化轻量级的迁移方案。CoreData还通过Binding特性和控件的紧密结合,这样使得只需要少量的代码便可以完成强大的功能,下面是一个例子
http://www.timisted.net/blog/archive/multiple-windows-with-core-data/
存储方式
Core Data可以将数据存储为XML,二进制文件或SQLite文件。在Mac OS X 10.5 Leopard及以后的版本中,开发者也可以通过继承NSPersistentStore类以创建自定义的存储格式。每种方法都有其优缺点,例如XML的可读性,SQLite的节约空间等。
Core Data的这一方面类似于原始的Enterprise Objects Framework(EOF)系统,但EOF中开发者可以使用相对简洁的查询方式,而在Core Data中,只能使用一个语法类似SQL子集的查询语言,称为Predicate。Core Data是标准化的,可以自由的读写Xcode数据模型文件(通常是.xcdatamodel文件)。
与EOF不同,Core Data目前没有设计多用户或多线程访问模式。模型迁移通常也需要代码,若其它开发者依赖于某个数据模型,则该数据模型的设计者可能在模型发生改变时需要与新数据模型一起提供版本转换代码。
操作简介
Core Data由相对庞大的类继承体系组成,但开发者需要关注的接口只是其中的一个相对小的子集。
一般需要定义以下Core Data的三个必备
NSPersistentStoreCoordinator *persistentStoreCoordinator;
NSManagedObjectModel *managedObjectModel;
NSManagedObjectContext *managedObjectContext;
以及使用时需要用到的
NSFetchedResultsController *fetchedResultsController;
使用步骤:
还记得我们每次使用CoreData的时候系统都会给我们创建一些代码吗?
1 #pragma mark - Core Data 堆栈 2 //返回 被管理的对象上下文 3 - (NSManagedObjectContext *)managedObjectContext 4 { 5 if (_managedObjectContext) { 6 return _managedObjectContext; 7 } 8 9 NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 10 if (coordinator) { 11 _managedObjectContext = [[NSManagedObjectContext alloc] init]; 12 [_managedObjectContext setPersistentStoreCoordinator:coordinator]; 13 } 14 return _managedObjectContext; 15 } 16 17 // 返回 持久化存储协调者 18 - (NSPersistentStoreCoordinator *)persistentStoreCoordinator 19 { 20 if (_persistentStoreCoordinator) { 21 return _persistentStoreCoordinator; 22 } 23 24 NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataNotes.sqlite"]; 25 26 _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; 27 28 [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType 29 configuration:nil 30 URL:storeURL 31 options:nil 32 error:nil]; 33 34 return _persistentStoreCoordinator; 35 } 36 37 // 返回 被管理的对象模型 38 - (NSManagedObjectModel *)managedObjectModel 39 { 40 if (_managedObjectModel) { 41 return _managedObjectModel; 42 } 43 NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreDataNotes" withExtension:@"momd"]; 44 _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; 45 return _managedObjectModel; 46 } 47 48 #pragma mark - 应用程序沙箱 49 // 返回应用程序Docment目录的NSURL类型 50 - (NSURL *)applicationDocumentsDirectory 51 { 52 return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; 53 }
下面是使用Core的示例代码
1 static NoteDAO *sharedManager = nil; 2 3 + (NoteDAO*)sharedManager 4 { 5 static dispatch_once_t once; 6 dispatch_once(&once, ^{ 7 8 sharedManager = [[self alloc] init]; 9 [sharedManager managedObjectContext]; 10 11 }); 12 return sharedManager; 13 } 14 15 16 //插入Note方法 17 -(int) create:(Note*)model 18 { 19 20 NSManagedObjectContext *cxt = [self managedObjectContext]; 21 22 NoteManagedObject *note = [NSEntityDescription insertNewObjectForEntityForName:@"Note" inManagedObjectContext:cxt]; 23 [note setValue: model.content forKey:@"content"]; 24 [note setValue: model.date forKey:@"date"]; 25 26 note.date = model.date; 27 note.content = model.content; 28 29 NSError *savingError = nil; 30 if ([self.managedObjectContext save:&savingError]){ 31 NSLog(@"插入数据成功"); 32 } else { 33 NSLog(@"插入数据失败"); 34 return -1; 35 } 36 37 return 0; 38 } 39 40 //删除Note方法 41 -(int) remove:(Note*)model 42 { 43 44 NSManagedObjectContext *cxt = [self managedObjectContext]; 45 46 NSEntityDescription *entityDescription = [NSEntityDescription 47 entityForName:@"Note" inManagedObjectContext:cxt]; 48 49 NSFetchRequest *request = [[NSFetchRequest alloc] init]; 50 [request setEntity:entityDescription]; 51 52 NSPredicate *predicate = [NSPredicate predicateWithFormat: 53 @"date = %@", model.date]; 54 [request setPredicate:predicate]; 55 56 NSError *error = nil; 57 NSArray *listData = [cxt executeFetchRequest:request error:&error]; 58 if ([listData count] > 0) { 59 NoteManagedObject *note = [listData lastObject]; 60 [self.managedObjectContext deleteObject:note]; 61 62 NSError *savingError = nil; 63 if ([self.managedObjectContext save:&savingError]){ 64 NSLog(@"删除数据成功"); 65 } else { 66 NSLog(@"删除数据失败"); 67 return -1; 68 } 69 } 70 71 return 0; 72 } 73 74 //修改Note方法 75 -(int) modify:(Note*)model 76 { 77 NSManagedObjectContext *cxt = [self managedObjectContext]; 78 79 NSEntityDescription *entityDescription = [NSEntityDescription 80 entityForName:@"Note" inManagedObjectContext:cxt]; 81 82 NSFetchRequest *request = [[NSFetchRequest alloc] init]; 83 [request setEntity:entityDescription]; 84 85 NSPredicate *predicate = [NSPredicate predicateWithFormat: 86 @"date = %@", model.date]; 87 [request setPredicate:predicate]; 88 89 NSError *error = nil; 90 NSArray *listData = [cxt executeFetchRequest:request error:&error]; 91 if ([listData count] > 0) { 92 NoteManagedObject *note = [listData lastObject]; 93 note.content = model.content; 94 95 NSError *savingError = nil; 96 if ([self.managedObjectContext save:&savingError]){ 97 NSLog(@"修改数据成功"); 98 } else { 99 NSLog(@"修改数据失败"); 100 return -1; 101 } 102 } 103 return 0; 104 } 105 106 //查询所有数据方法 107 -(NSMutableArray*) findAll 108 { 109 NSManagedObjectContext *cxt = [self managedObjectContext]; 110 111 NSEntityDescription *entityDescription = [NSEntityDescription 112 entityForName:@"Note" inManagedObjectContext:cxt]; 113 114 NSFetchRequest *request = [[NSFetchRequest alloc] init]; 115 [request setEntity:entityDescription]; 116 117 NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:YES]; 118 [request setSortDescriptors:@[sortDescriptor]]; 119 120 NSError *error = nil; 121 NSArray *listData = [cxt executeFetchRequest:request error:&error]; 122 123 NSMutableArray *resListData = [[NSMutableArray alloc] init]; 124 125 for (NoteManagedObject *mo in listData) { 126 Note *note = [[Note alloc] init]; 127 note.date = mo.date; 128 note.content = mo.content; 129 [resListData addObject:note]; 130 } 131 132 return resListData; 133 } 134 135 //按照主键查询数据方法 136 -(Note*) findById:(Note*)model 137 { 138 NSManagedObjectContext *cxt = [self managedObjectContext]; 139 140 NSEntityDescription *entityDescription = [NSEntityDescription 141 entityForName:@"Note" inManagedObjectContext:cxt]; 142 143 NSFetchRequest *request = [[NSFetchRequest alloc] init]; 144 [request setEntity:entityDescription]; 145 146 NSPredicate *predicate = [NSPredicate predicateWithFormat: 147 @"date = %@",model.date]; 148 [request setPredicate:predicate]; 149 150 NSError *error = nil; 151 NSArray *listData = [cxt executeFetchRequest:request error:&error]; 152 153 if ([listData count] > 0) { 154 NoteManagedObject *mo = [listData lastObject]; 155 156 Note *note = [[Note alloc] init]; 157 note.date = mo.date; 158 note.content = mo.content; 159 160 return note; 161 } 162 return nil; 163 }
CoreData高级常识
关于CoreData貌似实际开发中很少用到,基本上是个有九个公司不会使用它,因为都说是性能不好,但是作为一个程序员,了解及其使用时必须了,
下面是我从一位大神那里搬过来的一下Core详细介绍,相信以后总有一天会帮我解决不少学习CoreData中的问题!
一、技术概览
1. Core Data 功能初窥
对于处理诸如对象生命周期管理、对象图管理等日常任务,Core Data框架提供了广泛且自动化的解决方案。它有以下特性。
(注:对象图-Object graph的解释:在面向对象编程中,对象之间有各种关系,例如对象直接引用另外的对象,或是通过引用链间接的引用其他对象,这些关系组成了网状的结构。 我们把这些对象(和它们之间的联系)成为对象图。 对象图可大可小,有繁有简。 只包含单个字符串对象的数组就是一个简单的代表;而包含了application对象,引用windows, menus和相关视图对象、其他对象这样的结构就是复杂对象图的例子——这是在说mainwindow.xib。
有时,你可能想要把这样的对象图转化形式,让它们可以被保存到文件中,以使其他的进程或其他的机器可以再次将保存的内容读出,重购对象。 这样的过程常被成之为“归档”(Archiving)。
有些对象图是不完整的——通常称之为局部对象图(partial object graphs)。局部对象图包含了“占位符”(Placeholder)对象,所谓”占位符“,就是一些暂时无内容的对象,它们将再后期被具体化。一个典 型的例子就是nib文件中包含的File‘s Owner对象。
1) 对于key-value coding 和key-value observing完整且自动化的支持
除了为属性整合KVC和KVO的访问方法外, Core Data还整合了适当的集合访问方法来处理多值关系。
2) 自动验证属性(property)值
Core Data中的managed object扩展了标准的KVC 验证方法,以保证单个的数值在可接受的范围之内,从而使组合的值有意义。(需校准翻译)
3) 支持跟踪修改和撤销操作
对于撤销和重做的功能,除过用基本的文本编辑外,Core Data还提供内置的管理方式。
4) 关系的维护
Core Data管理数据的变化传播,包括维护对象间关系的一致性。
5) 在内存中和界面上分组、过滤、组织数据
6) 自动支持对象存储在外部数据仓库的功能
7) 创建复杂请求
你不需要动手去写复杂的SQL语句,就可以创建复杂的数据请求。方法是在“获取请求”(fetch request)中关联NSPredicate(又看到这个东东了,之前用它做过正则)。NSPrdicate支持基本的功能、相关子查询和其他高级的 SQL特性。它还支持正确的Unicode编码(不太懂,请高人指点), 区域感知查询(据说就是根据区域、语言设置调整查询的行为)、排序和正则表达式。
8) 延迟操作(原文为Futures(faulting)直译为期货,这里个人感觉就是延迟操作的形象说法。请高人指教)。
Core Data 使用延迟加载(lazy loading)的方式减少内存负载。 它还支持部分实体化延迟加载,和“写时拷贝”的数据共享机制。(写时拷贝,说的是在复制对象的时候,实际上不生成新的空间,而是让对象共享一块存储区域, 在其内容发生改变的时候再分配)。
9) 合并的策略
Core Data 内置了版本跟踪和乐观锁定(optimistic locking)来支持多用户写入冲突的解决。
注:乐观锁,假定数据一般不出现冲突,所以在数据提交更新的时候,才对数据的冲突进行检测,如果冲突了,就返回冲突信息。
10) 数据迁移
就开发工作和运行时资源来说,处理数据库架构的改变总是很复杂。Core Data的schema migration工具可以简化应对数据库结构变化的任务, 而且在某些情况下,允许你执行高效率的数据库原地迁移工作。
11) 可选择针对程序Controller层的集成,来支持UI的显示同步
Core Data在iPhone OS之上 提供NSFetchedResultsController对象来做相关工作,在Mac OS X上,我们用Cocoa提供的绑定(Binding)机制来完成。
2. 为何要使用Core Data
使用Core Data有很多原因,其中最简单的一条就是:它能让你为Model层写的代码的行数减少为原来的50%到70%。 这归功于之前提到的Core Data的特性。更妙的是,对于上述特性你也既不用去测试,也不用花功夫去优化。
Core Data拥有成熟的代码,这些代码通过单元测试来保证品质。应用Core Data的程序每天被世界上几百万用户使用。通过了几个版本的发布,已经被高度优化。 它能利用Model层的信息和运行时的特性,而不通过程序层的代码实现。 除了提供强大的安全支持和错误处理外,它还提供了最优的内存扩展性,可实现有竞争力的解决方案。不使用Core Data的话,你需要花很长时间来起草自己的方案,解决各种问题,这样做效率不高。
除了Core Data本身的优点之外,使用它还有其他的好处: 它很容易和Mac OS X系统的Tool chain集成;利用Model设计工具可以按图形化方式轻松创建数据库的结构;你可以用Instruments的相关模板来测试Core Data的效率并debug。 在Mac OS X的桌面程序中,Core Data还和Interface Builder集成(打开Inspector可以看到有binding的选项,这个东东iPhone上木有。。。),按照model来创建UI变的更简单 了。 这些功能能更进一步的帮助你缩短设计、开发、测试程序的周期。
3. Core Data不是。。。
看了前面的介绍之后,我们还需要了解一下关于Core Data常见的误解:
1) Core Data不是一个关系型数据库,也不是关系型数据库管理系统(RDBMS)。
Core Data 为数据变更管理、对象存储、对象读取恢复的功能提供了支持。 它可以使用SQLite作为持久化存储的类型。 它本身并不是一个数据库(这点很重要,比如,你可以使用Core Data来记录数据变更,管理数据,但并不能用它向文件内存储数据)。
2) Core Data不是银弹
它并不能取代你写代码的工作。虽然可以纯粹使用XCode的数据建模工具和Interface Builder来编写复杂程序,但在更多的程序中,你都自己动手写代码。
3) Core Data并不依赖于Cocoa Bindings
Core Data + Cocoa Binding = 减少代码数量。但Core Data完全可以在没有bindings的条件下使用。例如,可以编写一个没有UI,但包含Core Data的程序。
二、Core Data基础
1. Core Data基本架构
在大部分程序中,你要能通过某种方式打开一个包含对象归档的文件, 这个文件内至少要有一个根对象的引用。另外,还得能将所有的对象归档到文件中,如果你想要实现撤销的功能,就还要记录对象的更改情况。例如,在 Employee的示例程序中,你要能打开一个包含有employee和department对象归档的文件,而且这个文件至少包含了一个根对象——这 里,是一个包含所有employee的数组——请参考例图Figure 1。 相应的,你还要能将程序中的employee、department对象归档到文件中去。
Figure 1 按照Core Data文档结构管理的对象示意图
使用Core Data的框架,大多数的功能都可以自动实现,因为我们有managed object context(管理对象的上下文,有时直接叫"Context")。managed object context就像是一个关卡,通过它可以访问框架底层的对象——这些对象的集合我们称之为"persistence stack"(数据持久栈)。 managed object context作为程序中对象和外部的数据存储的中转站。栈的底部是persistence object stores(持久化数据存储),请看Figure 2的示意图。
Figure 2 使用Core Data的文档管理示意图
Core Data的使用并不限制在基于文档的程序中(document-based application)。你也能创建一个包含Core Data 的Utility程序(请查看Core Data Utility tutorial文档)。当然其他类型的程序也都可以使用Core Data。
被管理对象和上下文(Managed Objects and Contexts)
你可以把被管理对象上下文想象成一个”聪明“的便笺簿。当你从数据持久层获取对象时,就把这些临时的数据拷贝拿到写在自己的便笺簿上(当然,在便笺上对象 会 “恢复”以前的对象图结构)。然后你就可以随心所欲的修改这些值了(本子是你的,随便画都可以),除非你保存这些数据变化,否则持久层的东西是不会变 的。(跟修改文件后要保存是一个道理)。
附在Core Data框架中模型对象(Model objects)常被称为“被管理对象”(Managed objects)。所有的被管理对象都要通过上下文进行注册。使用上下文,你可以在对象图中添加、删除对象,并记录对象的更改(包括单个对象,或是对象间 的关系)。记录更改后就能支持撤销和重做的功能。同时,上下文还能保证关系更改后对象图的完整性。
如果你想要保存所做的修改, 上下文会保证对象的有效性。在验证有效性后,更改会被写入到persistent store(持久化存储层)中。你在程序中的添加和删除动作都会被作用在存储的数据中。
在你的一个程序中,可能存在多个上下文。 对于数据存储(store)中的每个对象,对应的都有唯一的一个被管理对象(managed object)和上下文相关联(详情请查看"Faulting and Uniquing"文档)。换个角度来想,在persistent store中存储的对象有可能被用在不同的上下文中,每个上下文都有与之对应的被管理对象,被管理对象可以被独立的修改,这样就可能在存储 时导致数据的不一致。Core Data提供了许多解决这个问题的途径(请查看"Using Managed Object"一章)。
获取数据的请求(Fetch Requests)
要使用上下文来获取数据,你需要创建相应的请求(Fetch request)。 Fetch request对象包含你想获取的对象的描述。例如:“所有 Employee”,或“所有的Employee,department是marketing,按薪资降序排列”。Fetch Request包含三个部分。使用最简单的写法,必须指定实体(Entity)的名称,这就暗示了,每次智能获得一种类型的实体。 Fetch Request 还可以包含谓词(predicate)——注:有些地方也把这个叫断言,个人感觉谓词更准确些。谓词将描述对象需要满足的条件(这就和我们在SQL里加的 限定条件差不多,正如前面的"All Employees, in the Marketing department")。另外,Fetch Request还可包含一个用于描述排序方式的对象(熟悉的Order by操作)。如图Figure3所示:
在程序中,你将Fetch Request这个请求发送给上下文,上下文就会从相关的数据源中查找复合条件的对象(也可能找不到),并返回。 所有的被管理对象(managed object)都必须在上下文中注册,因此通过fetch request获得的对象自动被注册。但如前所述,每个在持久存储层(persistence store)中的对象都对应一个和上下文相关的被管理对象(managed object)因此,如果在上下文中已经存在了fetch request要取的对象,那么这个被管理对象将被返回。
Core Data追求高执行效率。 它是“需求驱动”的,因此只会创建你确实需要的对象。对象图不需要保留所有在数据存储层中的对象。单纯指定数据持久层的动作不会将其中所有的数据放到上下 文中去。 当你想从数据存储层中获取某些对象的时候,你只会得到那些你请求的(有点罗嗦,总的意思就是需要时获取,获取的就是需要的)。如果你不在需要这个对象的时 候,默认情况下它会被释放。(当然,只是释放这个对象,而不是从对象图中移除该对象)。——注:个人感觉有点像重新拷了一个文件的某些部分,不用了就在副 本中删除,不会影响原件。
持久化存储助理(Persistent Store Coordinator)
之前提到过,程序中的对 象和外部存储的数据通过Core Data框架中的一系列对象进行协调,这一系列的对象总的被称为持久存储栈(Persistence stack)。在栈顶是被管理对象上下文(Managed object context),而栈底是持久化对象存储层(Persistence object store)。在它们之间就是持久化存储助理。
事实上,持久化存储助理定义了一个栈。从设计方面考虑,它就是可以作为上下 文的”外观“, 这样多个数据存储(Persistence store)看起来就像是一个。 然后上下文就可以根据这些数据存储来创建对象图了。持久化存储助理智能关联一个被管理对象的模型。如果你像要把不同的实体放到不同的存储中去,就需要为你 的模型实体做“分区”,方式是通过定义被管理对象模型的configurations。(请参考"Configurations"一章)。
Figure 4演示了这样的一个结构:employees和departments存储在一个文件中,customers和companies存储在另外一个文件中。当你要获取对象的时候,它们从相关的文件中自动获取;当保存时,又被归档到相应的文件中。
Figure 4存储栈—改
持久化存储(Persistent Stores)
持久化存储是和单独的一个文件或外部的数据关联的,它负责将数据和上下文中的对象进行对应。通常,需要你直接和持久化对象存储打交道的地方,就是指定新 的、 和程序进行关联的外部数据的位置(例如,当用户打开或保存一个文档)。大多数需要访问持久化存储的动作都由上下文来完成。
程序的代码—— 特别是和被管理对象相关的部分——不应该对持久化存储做任何假设(也就是不需要自己考虑存储的方式或过程)。 Core Data对几种文件格式有原生的支持。你可以选择一种自己程序需要的。假设在某个阶段你决定换一种文件的格式,而又不想修改程序的框架,而且,你的程序做 了适当的抽象(注:这个就属于设计方面的东东了),这时,你就能尝到使用Core Data的甜头了。例如,在最初的设计中,程序只从本地文件中获取数据,而你的程序没有去硬指定对应数据的获取位置,而是可以在后期指定从远程位置添加新 的数据类型,这样你就可以使用新的类型,而不需要修改代码。(这段还是感觉翻的不太合适)。
重要提示:
虽然Core Dta支持SQLite作为一种存储类型,但它不能使用任意的SQLite数据库。Core Data在使用的过程种自己创建这个数据库。(详情,请参考"Persistence Store Features")。
持久化文档(Persistent Documents)
你可以通过代码的方式创建和配置持久存储栈,但在多数情况下,你只是想创建一个基于文档 的应用程序(Document-based application,这个是mac上的)来读写文件。这时,用NSDocument的子类NSPersistentDocument可以让你感受到使 用Core Data的便利。默认状况下,NSPersistentDocument就已经创建了它自己的持久存储栈,其中包含了上下文,和单个的持久对象存储,来处 理这样文档和外部数据“一对一”的映射关系。
NSPersistentDocument类提供了访问文档的上下文的方法,也实现了标准的NSDocument方法来通过Core Data读写文件。 一般说来,你不需要编写额外的代码来处理对象的持久化。
持久化文档的撤销(undo)操作也被集成在被管理对象的上下文中。
被管理对象和被管理对象模型(Managed Objects and the Managed Object Model)
为 了管理对象图,也为了提供对象持久化的功能,Core Data需要对对象有很强的描述能力。被管理对象模型就是程序中对象、实体描述的概要图,如图Figure 5所示。创建模型的常用做法是通过Xcode的图形化建模工具Date Model Design tool。但是如果你愿意的话,也可以在运行时通过代码来建模。
Figure 5 有两个实体的对象模型
模型由多个实体描述对象构成,每个描述提供实体的某项元数据,它们包含实体名、实体在程序中的类名(当然,类名和实体名不需要一致)、属性还有关系。属性和关系依次被属性和关系描述对象所代表,如图Figure 6所示。
Figure 6 带有两个属性和一个关系的的实体描述
被管理对象必须是NSManagedObject或其子类的实例。 NSManagedObject可用来表示任何实体。它使用内部私有的存储机制来维护自身的属性,并执行一个被管理对象所必须的基本操作。一个被管理对象 拥有一份实体描述的引用。在使用时,它通过实体描述来找到自身的元数据,包括实体名和属性、关系的信息。你也可以继承NSManagedObject来执 行额外的操作。
被管理对象模型(Managed Object Models)
多数Core Data的功能依赖于你创建的,用来描述程序的实体及其属性、关系的模型图。 模型图由NSManagedObjectModel所表示。一般说来,模型的信息越充实,Core Data能提供的功能就越好。 下文讲解了对象模型的特性,以及如何在程序中创建、使用对象模型。
被管理对象模型的特性
被管理对象模型是 NSManagedObjectModel的实例。它描述了你在程序中使用的实体的概要信息。(如果读者不了解entity、property、 attribute和relationship的含义,请先查看"Core Data Basics"和"Cocoa Design Patterns"文档中的"Object Modeling"一节)
实体(Entities)
模型包含了NSEntityDescription对象,NSEntityDescription对象指代了模型的实体。关于实体由两个重要特征:名称 (name)和类名(name of class)。你应该弄清楚实体、实体的类和作为实体实例的被管理对象之间的区别。
NSEntityDescription 对象可包含NSAttributeDescription对象(指代实体的attribute)和NSRelationshipDescription对 象(指代实体间的relationship)。实体也可能包含fetched属性,该属性由NSFetchedPropertyDescription指 代,模型中有对应的fetch请求的模板,fetch请求由NSFetchRequest所指代。
实体的继承关系
实体的继承和类 的继承很类似,当然,也同样有用。 如果你有若干个相似的实体,就可以抽离出它们的共有特性作为一个“父实体”,就省去了在多个实体中都指定相同的属性。 例如,你可以定义一个包含firstName和lastName的“Person”实体,然后在定义子实体"Employee"和"Customer"。
如果是使用Xcode的可视化建模工具来创建模型,你就可以通过如下图的方式为一个实体指定父级实体。
Figure1 Xcode中为一个实体指定父实体
如果你想在代码中创建继承关系。就需要自顶向下来执行。不能直接指定实体的父实体,而只能给一个实体指定子实体(使 用setSubentities:)。这 就是说,如果你想给A实体指定父实体,就只能把A作为数组中的一个元素,调用目标父实体setSubentities:的方式来设置。
抽象实体
你可以把一个实体指定为“抽象实体”,也就是说,你不打算使用这个实体来创建实例。通常,当你想把这个实体作为父实体,而有子实体来实现详细内容的时候, 就 把它声明“抽象实体”。(和抽象类很像)。例如,在一个绘图程序中,你可能会设计一个Graphic实体,它包含了x和y坐标信息、颜色、绘制区域,而你 不会去创建一个Graphic的实例,而是使用具体的子实体——Circle、TextArea、Line。(这些基本的东西就不给大牛们再罗嗦 了。。。)
Properties(属性,这个和Attributes的意思一样,实在区别不出来,只好上英语了)
实体的 Properties是它的attributes和relationship,包含了fetched属性(如果有的话)。每个property都有名称和 类型。 Attribute也可能有默认值。property的名称不能和NSObject和NSManagedObject类中的无参方法名相同。例如,不能把 property命名为"description"。
临时属性(Transient Property)也是作为模型的一部分,但是不作为实体实例的数据保存在持久存储层。 Core Data也会跟踪临时属性的变化,以备撤销操作时使用。
注意:如果你用模型外的信息对临时属性执行撤销操作,Core Data将不会使用旧值,调用你的set方法——它只会更新快照信息(snapshot information)。(这段怪怪的,用到的话在修改一下翻译吧)
Attributes
Core Data内部支持各种attribute的类型,例如string,date,integer(NSString, NSDate, NSNumber)。如果你使用那些不支持的数据,你需要用到在“Non-Standard Persistent Attributes”介绍到的技术。
你可以将一个attribute声明为“可选”(optional),可选的attribute不 必须有值,但是,不鼓励你将属性置空——尤其是数字值(更好的解决方案是使用强制的值,在这里,我们用默认值,例如0)。 这样做的原因是为了配合SQL中对于空值NULL做比较的操作:NULL不同于Objective-C中的nil。 数据库中的NULL不同于0,搜索0值的操作不会匹配到值为NULL的列。
false == (NULL == 0)
false == (NULL != 0)
而且,在数据库中,NULL也不等于空字符串或是空的数据对象:
false == (NULL == @"")
false == (NULL != @"")
它们之间一点关系都没有。
关系(Relationships)
Core Data支持对一、对多的关系,也支持fetched属性。 Fetched property表示了一种“弱”的、单项的关系。 在employees和departments的例子中, department 的一个fetched property可能是“最近雇佣人”(recent hires),而反过来,employee不会拥有这样的关系。
获取数据请求的模板(Fetch Request Templates)
我们使用NSFetchRequest类来描述数据请求,利用数据请求从持久存储(persistent store)中获取对象。 经常需要多次执行同样的请求,或是执行某种模式的请求,但是其中包含可变的元素(如查找条件)——这些元素经常有用户提供。 例如,在运行的时候,你要根据用户需要获取某个作者在某个指定日期后的出版的所有出版物。
你可以预定义请求,把它们作为模板存储在被管理对象模型中。 预定义的模板在你需要的时候就可以取出使用。通常情况下,我们通过Xcode的data modeling tool工具创建请求模板。模板可以包含变量,如图Figure 2所示。
Figure 2 Xcode predicate builder
关于Fetch request templates的详细信息,请查看"Accessing and Using a Managed Object Model at Runtime"的描述。
用户信息字典(User Info Dictionaries)
模型中的许多元素,诸如entities, attributes, relationships,都有相关的用户信息字典。用熟悉的键-值对,你可以向其中放置任何你需要的数据。这里常用的信息有实体的版本详情,还有针对 fetched property,给谓词(predicate)用的值。
配置(Configurations)
配置包含了一个名称和若干个相关的实体。实体的集合是可以重叠的——这就是说,一个实体可以出现在多个配置中。在代码中,我们使用 setEntities: forConfiguration:的方法来指定配置。也可以用Xcode的建模工具来指定(选中某个实体,就在属性窗口的第三个,就是一个小扳手的符 号)。要获取某项配置的实体,需要用entitiesForConfiguration:的方法。
一般说来,如果你想把不同的实体存放在不同的存储中去,就可能用到配置。一个持久化存储助理(persistent store coordinator)只能有一个被管理对象模型。所以,默认情况下,和助理关联的某个存储必须包含同样的实体。要想绕过这个限制,你可以创建一个包含 实体子集的模型,然后为每一个子集创建配置,这样一来,使用这个模型创建助理,当你需要添加存储时,可使用不同的配置指定对应的存储属性。当你创建配置的 时候,需要记住,不能创建跨存储的关系。
使用被管理对象模型
通常可以使用Xcode的建模工具来创建模型(请参考"Create a managed object with Xcode")。你也可以全部使用代码来创建(请参考"Core Data Utility Tutorial")。
编译数据模型
数据模型是一种部署资源。 在模型中,除了有实体和属性的详细信息外,用Xcode创建的模型还包含了一些额外的视图信息,包括布局、颜色等等。这些信息在运行时不是必须的。模型文 件在编译的过程中会删除这些额外信息以保证尽可能高效的加载。xcdatamodel“源”文件会被momc编译器编译为mom的目标文件。
"mom" 位于 /Library/Application Support/Apple/Developer Tools/Plug-ins/XDCoreDataModel.xdplugin/Contents/Resources/,如果你想把它用在自己的 build脚本中,格式是:mom source destination, source 就是Core Data Model文件,destination就是输出的mom文件。
加载数据模型
在一些情况下,你不需要写任何加载模型的代码。如果你使用基于文档的程序框架(Document-based application),NSPersistentDocument会管理诸如查找模型、加载模型的任务。 如果你创建了非Document-based application,而且里面又用到了Core Data,一般将获取模型的代码放在application delegate里。模型的存储名称——也就是文件名,
和运行时的名称是不相关的,一旦模型被加载,文件名就没有什么意义了。也就是说,对模型文件,你可以随意命名。
如果你想手动加载模型,有两种方式可用,它们各有各的好处:
你可以从指定的bundle集合里创建整合模型,使用如下的类方法:
mergeModelFromBundles:
也可以用指定的URL加载单个的模型,使用如下的实例方法:
initWithContentsOfURL: (这个方法相信大家都用过)
若不需要考虑分开加载模型,第一个类方法很适用。例如:在你的程序中和程序链接的framework里都有你想要加载的模型。这个类方法可以让你很轻松的加载所有的模型,而不需要考虑模型文件的名称,也不用特定的初始化方法来保证所有的模型都被找到。
但是当你有多个模型要加载,特别是这些模型都代表了一个schema的不同版本,这时,知道要加载哪个模型就很重要了(合并包含相同实体的模型可能导致命 名冲突和错误,我们之前“一锅端”的方法不太合适了)。在这种情况下,我们可以用第二个实例方法。 另外,有时我们也需要将模型存储在bundle之外,也需要用这个方法从指定的URL位置加载模型。
还有一点需要说明:我们还有一个类方法 modelByMergingModels:可以用。像mergedModelFromBundles:方法一样,它也能合并给定的若干个模型。这样,我 们就可以通过URL来逐一加载模型,然后在创建助理对象之前将它们整合为一个。
改变模型
由于模型描述了存储层数据的结构,任何改变模型的动作都将使其不在适配于之前创建的存储层。 如果你改变了模型的结构,就需要将当前存储层的数据迁移到新版本。(请参考"Core Data Model Versioning and Data Migration Programming Guide"文档)。例如:如果你添加了新的实体,或新的属性,你将无法打开旧的存储;如果你添加了验证的限制,或者为属性添加了新的缺省值,你就可以打 开旧的存储。
在运行时访问和适用被管理对象模型
在运行时,被管理对象模型就是一个简单的“对象图”(这个概念之前提到过),认识到这点很重要,尤其是当你需要用代码来访问模型的详细信息时。例如:修改 模型(你只能在runtime之前这样做,请参考 NSManagedObjectModel),取回信息(如本地化实体名,属性数据类型,或数据请求模板)。
在运行时访问模型有很多方法,通过持久栈最终从持久化存储助理得到模型,代码如下:
[[aManagedObjectContext persistentStoreCoordinator]managedObjectModel];
你也可以通过实体描述得到模型,因此给定一个被管理对象,你就可以得到它的实体描述,进而获得模型。代码如下:
[[aManagedObject entity] managedObjectModel];
某些情况下,你要维护模型的“直接”引用,也就是说,一个直接返回模型的方法。NSPersistentDocument提供了 managedObjectModel方法,可以返回一个模型,该模型和在文档的上下文中使用的持久化存储助理相关联。如果你使用Core Data Appplication的模板,application delegate将负责模型的引用。
通过代码创建获取数据请求模板(Fetch Request Templates)
你可以通过代码创建数据请求模板并将其和模型关联,方法是:setFetchRequestTemplate: forName:如Listing-1所示。 提醒一下:你只能在模型被助理(coordinator)使用之前修改它。
Listing 1 通过代码创建获取数据请求模板
NSManagedObjectModel *model = …;
NSFetchRequest * requestTemplate = [[NSFetchRequest alloc]init];
NSEntityDescription *publicationEntity =
[[model entitiesByName] objectForKey: @"Publication"];
[requestTemplate setEntity: publicationEntity];
NSPredicate *predicateTemplate = [NSPredicate predicateWithFormat:
@"(mainAuthor.firstName like[cd] $FIRST_NAME) AND \
(mainAuthor.lastName like[cd] $LAST_NAME) AND \
(publicationDate > $DATE)"];
[requestTemplate setPredicate: predicateTemplate];
[model setFetchRequestTemplate: requestTemplate
forName: @"PublicationForAuthorSinceDate"];
[requestTemplate release];
访问请求模板
你可以用"Accessing and Using a Managed Object Model at Runtime"里介绍的代码片段来获取并使用请求模板。替换字典必须包含和模板中定义的变量对应的键。如果你想测试null值,必须使用NSNull对 象——参考"Using Predicates"。(注:这里的替换字典很好理解,之前的模板中用到了诸如$FIRST_NAME, $LAST_NAME, $DATE这些东西,就相当于我们在模板中创建好的“变量”,我们需要把一个模板“具体化”,就用替换字典,将里面的变量对应一个值,这里看代码就明白 了。)
NSManagedObjectModel *model = …;
NSDictionary *substitutionDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
@"Fiona", @"FIRST_NAME", @"Verde", @"LAST_NAME",
[NSDate dateWithTimeIntervalSinceNow: -31356000], @"DATE", nil]; //这里的FIRST_NAME, LAST_NAME, DATE和我们之前模板里的$FIRST_NAME, $LAST_NAME和$DATE对应
NSFetchRequest *fetchRequest =
[model fetchRequestFromTemplateWithName: @"PublicationForAuthorSinceDate"
substitutionVariables: substitutionDictionary]; //从之前的model中拿出请求模板,然后设定替换字典
NSArray *results =
[aManagedObjectContext executeFetchRequest: fetchRequest error: &error];
要是模板里不包含可替换的变量,你要么
1. 使用fetchRequestFromTemplateWithName: substitutionVariables: 方法,传递nil给第二个参数
或者:
2. 使用fetchRequestTemplateForName: 并将结果copy。这个方法不需要传递“替换变量”这个参数,但是如果你要用返回值本身,将会有异常抛出(无法在不可变的模型中修改命名的数据请 求"Can‘t modify named fetch request in an immutable model")。
本地化被管理对象模型
你可以对模型的大部分内容做本地化处理,包括实体和属性名,还有错误信息。要明白,“转成你自己的语言”也是本地化的一部分。 即使你不打算提供外语版本, 显示“自然语言”的出错提示信息也会有更好的用户体验。例如:“First Name is a required property”就比"firstName is a required property"更好。(后面的这个更像是开发者用的log,显示的是变量名,这里不太明显)。
要想对模型进行本地化处理,需要提供一个本地化字典,模式如下:
Table 1 针对被管理对象模型的本地化字典键值对应关系:
- Key Value Note
- "Entity/NonLocalizedEntityName" "LocalizedEntityName"
- "Property/NonLocalizedPropertyName/Entity/EntityName" "LocalizedPropertyName" 1
- "Property/NonLocalizedPropertyName" "LocalizedPropertyName"
- "ErrorString/NonLocalizedErrorString" "LocalizedErrorString"
备注:(1)在不同实体中的属性,拥有相同的原始名称,但需要不同的本地化名称,适用于该格式。
我们可以通过localizationDictionary方法来访问本地化字典。注意:在Mac OS X 10.4上,这个方法可能返回nil,除了Core Data为了某些特定目的(如报告本地化的错误描述)延迟加载本地化字典。
字符串文件
处理模型的本地化最简单的方法就是创建对应的字符串文件——字符串文件名和模型文件名一直,但是后缀名用.strings。(例如,模型文件名为 MyDocument.xcdatamodel,对应的字符串文件名就为MyDocumentModel.strings;如果模型文件已经包含了 Model后缀,你必须再附加一个Model,所以,如果模型文件名为JimsModel.xcdatamodel对应的字符串文件名为 JimsModelModel.strings)。字符串文件格式和标准字符串文件类似(请参考"Localizing String Resources"),但是对应的键值要遵循Table-1中的规则。
一个模型的字符串文件实例:
- "Entity/Emp" = "Employee";
- "Property/firstName" = "First Name";
- "Property/lastName" = "Last Name";
- "Property/salary" = "Salary";
更详细的示例请参考"NSPersistentDocument Core Data Tutorial"。
代码实现设置本地化字典
你可以在运行时设定本地化字典,适用NSManagedObjectModel的setLocalizationDictionary:方法即可。你必须 创建一个符合Table-1格式的字典,并把它和模型关联。必须保证在模型被使用(获取或创建被管理对象)之前做这些工作,因为再使用后模型就不可编辑 了。 Listing 3演示了创建包含本地化字典的被管理对象模型。实体名称叫“Run”,它有两个属性: "date"和"processID",分别是date和integer类型。process ID的值不能为负。
Listing 3 通过代码创建被管理对象模型
- NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] init];
- NSEntityDescription *runEntity = [[NSEntityDescription alloc] init];
- [runEntity setName:@"Run"];
- [runEntity setManagedObjectClassName:@"Run"];
- [mom setEntities:[NSArray arrayWithObject:runEntity]];
- [runEntity release];
- NSMutableArray *runProperties = [NSMutableArray array];
- NSAttributeDescription *dateAttribute = [[NSAttributeDescription alloc] init];
- [runProperties addObject:dateAttribute];
- [dateAttribute release];
- [dateAttribute setName:@"date"];
- [dateAttribute setAttributeType:NSDateAttributeType];
- [dateAttribute setOptional:NO];
- NSAttributeDescription *idAttribute= [[NSAttributeDescription alloc] init];
- [runProperties addObject:idAttribute];
- [idAttribute release];
- [idAttribute setName:@"processID"];
- [idAttribute setAttributeType:NSInteger32AttributeType];
- [idAttribute setOptional:NO];
- [idAttribute setDefaultValue:[NSNumber numberWithInt:0]];
- NSPredicate *validationPredicate = [NSPredicate predicateWithFormat:@"SELF >= 0"];
- NSString *validationWarning = @"Process ID < 0";
- [idAttribute setValidationPredicates:[NSArray arrayWithObject:validationPredicate]
- withValidationWarnings:[NSArray arrayWithObject:validationWarning]];
- [runEntity setProperties:runProperties];
- NSMutableDictionary *localizationDictionary = [NSMutableDictionary dictionary];
- [localizationDictionary setObject:@"Process ID"
- forKey:@"Property/processID/Entity/Run"];
- [localizationDictionary setObject:@"Date"
- forKey:@"Property/date/Entity/Run"];
- [localizationDictionary setObject:@"Process ID must not be less than 0"
- forKey:@"ErrorString/Process ID < 0"];
- [mom setLocalizationDictionary:localizationDictionary];
这段代码写的比较多,这里不再解释了。本地化字典的代码在最后。创建一个符合格式的localizationDictionary,然后用model调用即可。
数据持久化总结
1 //1.沙盒:/Users/nono/Library/Application Support/iPhone Simulator/5.1/Applications/2D135859-1E80-4754-B36D-34A53C521DE3 2 /** 3 // 1、获取程序的Home目录 4 NSString *home = NSHomeDirectory(); 5 NSLog(@"应用程序目录:%@", home); 6 7 // 2、获取Documents目录 8 // NSUserDomainMask 代表从用户文件夹下找 9 // YES 代表展开路径中的波浪字符“~” 10 NSArray *documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 11 // 只有一个匹配目录,所以这个集合里面只有一个元素 12 NSString *doc = documents[0]; 13 NSLog(@"文档目录:%@", doc); 14 15 // 使用字符串拼接的方式获取目录名 16 // 不建议采用,因为新版本的操作系统可能会修改目录名 17 NSString *doc2 = [home stringByAppendingPathComponent:@"Documents"]; 18 NSLog(@"拼接文档目录:%@", doc2); 19 20 // 3、获取Cache目录 21 NSArray *caches = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); 22 NSString *cache = caches[0]; 23 NSLog(@"缓存目录:%@", cache); 24 25 // 4、获取Tmp目录 26 NSString *tmpDir = NSTemporaryDirectory(); 27 NSLog(@"临时目录:%@", tmpDir); 28 29 //5,NSUserDefaults 30 在A类中: 31 NSUserDefaults * userDefault = [NSUserDefaultsstandardUserDefaults]; 32 [userDefault setBool:YES forKey:@"isonline"]; 33 [userDefault setInteger:111 forKey:@"online_user_number"]; 34 ...等等。参见NSUserDefault用法。 35 36 在B中:获取A传递过来的参数 37 NSUserDefault * userDefault = [NSUserDefault standardUserDefault]; 38 BOOL isonline = [userDefault boolForKey:@"isonline"]; 39 NSInteger onlineUserNumber = [userDefault integerForKey:@"online_user_number"]; 40 41 */ 42 43 //2,属性列表 44 /** 45 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 46 NSString *docPath = [paths objectAtIndex:0]; 47 NSString *myFile = [docPath stringByAppendingPathComponent:@"my.list"]; 48 //读取文件 49 NSArray *array = [[NSArray alloc] initWithContentsOfFile:myFile]; 50 //操作完若修改了数据则,写入文件 51 [array writeToFile:myFile atomically:YES]; 52 */ 53 54 55 //3.对象归档 56 /** 57 #pragma NSCoding协议实现实现 58 - (void)encodeWithCoder:(NSCoder *)aCoder 59 { //encoder 60 [aCoder encodeObject:stringAforKey:@"1"]; 61 [aCoder encodeObject:stringBforKey:@"2"]; 62 } 63 - (id)initWithCoder:(NSCoder *)aDecoder 64 { 65 //decoder 66 if (self = [superinit]) { 67 stringA = [[aDecoder decodeObjectForKey:@"1"] retain]; 68 stringB = [[aDecoder decodeObjectForKey:@"2"] retain]; 69 } 70 returnself; 71 } 72 73 #pragma NSCopying协议实现 74 - (id)copyWithZone:(NSZone *)zone 75 { 76 TestObj *copy = [[[selfclass] allocWithZone:zone] init]; 77 copy.stringA = [[self.stringAcopyWithZone:zone] autorelease]; 78 copy.stringB = [[self.stringBcopyWithZone:zone] autorelease]; 79 return copy; 80 } 81 82 //读取归档文件 83 NSData *data = [[NSMutableDataalloc] initWithContentsOfFile:myFile]; 84 NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiveralloc] initForReadingWithData:data]; 85 TestObj * test = [unarchiver decodeObjectForKey:@"data"]; 86 [unarchiver finishDecoding]; 87 [data release]; 88 [unarchiver release]; 89 90 //写入归档文件 91 NSMutableData *data1 = [[NSMutableDataalloc] init]; 92 NSKeyedArchiver *archiver = [[NSKeyedArchiveralloc] initForWritingWithMutableData:data1]; 93 [archiver encodeObject:test forKey:@"data"]; 94 [archiver finishEncoding]; 95 [data writeToFile:myFile atomically:YES]; 96 [data1 release]; 97 [archiver release]; 98 [test release]; 99 */ 100 101 102 //4.数据库存储(SQLite3) 103 /** 104 //数据库操作 105 sqlite3 *database; 106 // const NSString * dbname = @"mydb" 107 int result; 108 //打开一个指定路径的现有的数据库,如果没有则会新建一个db库 109 result = sqlite3_open([myFile UTF8String], &database); 110 if (result != SQLITE_OK) { 111 sqlite3_close(database); 112 } 113 114 //创建一个db表 115 char *errorMsg; 116 NSString *sql_create_table = @"CREATE TABLE IF NOT EXISTS NONOTABLE 省略~~~~~~~~~~~~~"; 117 int result1 ; 118 //sqlite_exec用了针对sqlite3运行任何不要返回数据的命令,它用于执行更新,插入和删除。简单来说,这个方法执行的都是一些无需返回数据(虽然我们可能获取一个状态值。)。 119 result1 = sqlite3_exec(database, [sql_create_table UTF8String], NULL, NULL, &errorMsg); 120 121 //检索查询操作 122 int result2 ; 123 sqlite3_stmt *statment; 124 NSString *sql_selected = @"查询语句"; 125 result2 = sqlite3_prepare_v2(database, [sql_selected UTF8String], -1, &statment, nil); 126 if(result2 == SQLITE_OK){ 127 //单步操作 128 while (sqlite3_step(statment) == SQLITE_ROW) { 129 int row = sqlite3_column_int(statment, 0); 130 char * rpwData = sqlite3_column_text(statment, 1); 131 } 132 sqlite3_finalize(statment); 133 } 134 135 136 //绑定变量,既就是插入操作的一种变种,比如我么那上面提到sqlite_exec可以执行插入操作,插入内容直接是写在sql字窜里,但是考虑到字窜涉及到无效的符号以及会一些严重的注入漏洞(比如以前听过的引号符号)。 137 NSString *sql_bind = @"insert into foo value(?,?)"; 138 result2 = sqlite3_prepare_v2(database, [sql_selected UTF8String], -1, &statment, nil); 139 if(result2 == SQLITE_OK){ 140 sqlite3_bind_int(statment, 1, 235); 141 sqlite3_bind_text(statment, 2, "test", -1, nil); 142 sqlite3_finalize(statment); 143 } 144 if (sqlite3_step(statment) != SQLITE_DONE) 145 NSLog(@"error"); 146 sqlite3_finalize(statment); 147 148 149 sqlite3_close(database); 150 151 */ 152 153 154 //5.苹果公司提供的持久性工具Core Data 155 /* 156 一般需要定义以下Core Data的三个必备 157 NSPersistentStoreCoordinator *persistentStoreCoordinator; 158 NSManagedObjectModel *managedObjectModel; 159 NSManagedObjectContext *managedObjectContext; 160 以及使用时需要用到de 161 NSFetchedResultsController *fetchedResultsController; 162 */ 163 164 165 166 167 168 169 //其实对于ios数据存储,最常用和主要要掌握的就是属性列表和数据库,因为两个是出镜率比较高的。其他可能在数据存明显体现出储优势时,我们会去考虑用另外两种机制。基础的来说,必须掌握属性列表和sqlite的操作存储。
NSUserDefault
1 2 //******************** 5.1 NSUserDefault和对象归档 3 func useNSUserDefault() 4 { 5 //通过单利来创建一个NSUserDefaults对象,全局变量NSUserDefault,可在整个项目传递变量 6 var userDefault:NSUserDefaults = NSUserDefaults.standardUserDefaults() 7 8 //通过init方法创建 9 var userDefault1:NSUserDefaults = NSUserDefaults(suiteName: "SwiftClass")! 10 11 //获取userDefault单利下所有的值 12 println(userDefault.dictionaryRepresentation()) 13 14 //判断NSUserDefaults的“appMessage”key 在dictionaryRepresentation中是否存在,如果不存在就设置“appMessage”值为This is app message。 15 if(userDefault.objectForKey("message") == nil){ 16 userDefault.setObject("This_is_my_default_message", forKey: "message") 17 } 18 19 //如果想单独看某个key的设置,例如: 20 var dic = userDefault.dictionaryRepresentation() 21 var object_one:AnyObject? = (dic as NSDictionary).objectForKey("AppleKeyboards") 22 // //或者 23 // var object_one:AnyObject? = dic["AppleKeyboards"] 24 25 if let oValue: AnyObject! = object_one { 26 println(oValue) 27 } 28 29 30 //Int类型 31 //设置 32 userDefault.setInteger(123456, forKey: "Int") 33 //读取 34 var intValue = userDefault.integerForKey("Int") 35 println(intValue) 36 37 //Float类型 38 //设置 39 userDefault.setFloat(3.2, forKey: "Float") 40 //读取 41 var floatValue = userDefault.floatForKey("Float") 42 println(floatValue) 43 44 //Double类 45 //设置 46 userDefault.setDouble(5.6890, forKey: "Double") 47 //读取 48 var doubleValue = userDefault.doubleForKey("Double") 49 println(doubleValue) 50 51 //Bool类型 52 //设置 53 userDefault.setBool(true, forKey: "Bool") 54 //读取 55 var boolValue = userDefault.boolForKey("Bool") 56 println(boolValue) 57 58 //NSURL类型 59 //设置 60 userDefault.setURL(NSURL(string: "http://www.iphonetrain.com")!, forKey: "NSURL") 61 //读取 62 var urlValue = userDefault.URLForKey("NSURL") 63 println(urlValue) 64 65 66 67 //保存NSDate数据 68 //将对象转换成NSData流 69 var imageData:NSData = NSKeyedArchiver.archivedDataWithRootObject(UIImage(named: "SwiftClassWeiXin.png")!) 70 71 //存储NSData对象 72 userDefault.setObject(imageData, forKey: "imageData") 73 74 //读取数据 75 //获取NSData 76 var objData:AnyObject? = userDefault.objectForKey("imageData") 77 78 //还原对象 79 // var myImage:AnyObject? = NSKeyedUnarchiver.unarchiveObjectWithData(objData as NSData) 80 81 82 //2015年5月2号修改 83 var myImage:AnyObject? = NSKeyedUnarchiver.unarchiveObjectWithData(objData as! NSData) 84 85 86 87 //初始一个UIImage对象 88 println(myImage) 89 90 91 92 93 //自定义的类实现存取需要通过NSData做载体 94 95 //创建AppsModel的实例 96 var model = AppsModel(imageName: "appIcon2.png", app_Name: "租房点评", app_Description: "租房被骗?现在开始,你来改变这一切!《租房点评》为你而备,租房无忧!") 97 98 //实例对象转换成NSData 99 var modelData:NSData = NSKeyedArchiver.archivedDataWithRootObject(model) 100 101 //存储NSData对象 102 userDefault.setObject(modelData, forKey: "myAppModel") 103 104 105 106 107 108 109 110 111 //保存NSString, 112 userDefault.setValue("1_NSString", forKey: "NSString") 113 userDefault.setObject("1_NSString1", forKey: "NSString1") 114 115 //保存NSNumber, 116 var number:NSNumber = NSNumber(int: 32) 117 userDefault.setValue(number, forKey: "number") 118 userDefault.setObject(number, forKey: "number1") 119 120 //保存NSArray 121 var array1:NSArray = NSArray(array:["22222","33333"]) 122 userDefault.setValue(array1, forKey: "array") 123 userDefault.setObject(array1, forKey: "array1") 124 125 //保存NSDictionary 126 var dictionary:NSDictionary = NSDictionary(dictionary: ["1":"1111"]) 127 userDefault.setValue(dictionary, forKey: "dictionary") 128 userDefault.setObject(dictionary, forKey: "dictionary1") 129 130 131 132 133 var value:AnyObject? = userDefault.valueForKey("dictionary") 134 println(value) 135 136 value = userDefault.objectForKey("dictionary1") 137 println(value) 138 139 140 141 //-------- 删除所有的值 142 var ar:NSDictionary = userDefault.dictionaryRepresentation() 143 144 for key in ar.allKeys { 145 146 // userDefault.removeObjectForKey(key as String) 147 148 //2015年5月2号修改 149 userDefault.removeObjectForKey(key as! String) 150 151 userDefault.synchronize() 152 } 153 }