iOS开发高级进阶(17-19)-数据存储
简易数据存储
数据持久化的方法:
NSData
常在网络访问中使用
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
NSString
Key Value 数据
[NSDictionary dictionaryWithContentsOfFile:<#(nonnull NSString *)#>];
[NSDictionary dictionaryWithContentsOfURL:<#(nonnull NSURL *)#>];
//写
NSDictionary *dict =[[NSDictionary alloc]init];
[dict writeToFile:<#(nonnull NSString *)#> atomically:<#(BOOL)#>];
[dict writeToURL:<#(nonnull NSURL *)#> atomically:<#(BOOL)#>];
配置参数(NSUserDefaults)
// 1、设置值的方法:
NSUserDefaults *defaults =[NSUserDefaults standardUserDefaults];//本地
// NSUbiquitousKeyValueStore * defaults2 =[NSUbiquitousKeyValueStore defaultStore];// iCloud
NSDictionary * dict = [[NSDictionary alloc]initWithObjectsAndKeys:@"rememberPwd", @"",nil];
[defaults registerDefaults:dict];
NSString *countName = @"name";
[defaults setValue:countName forKey:@"rememberPwd"];// 写
[defaults synchronize];
// 2、获取指的方法(通过上面的方法设置值之后,我们可以通过下面的方法来获取值)
// NSUserDefaults *defaults =[NSUserDefaults standardUserDefaults];
NSLog(@"%@",[defaults valueForKey:@"rememberPwd"]);
NSLog(@"%@",[defaults valueForKey:@"AppleLanguages"]);
[defaults synchronize];
Settings Bundle
图
沙盒
iOS app 在运行时 只能访问自己的目录
NSHomeDirectory();
- /Documents:文件数据保存在这里,备份
- /Library: 储存程序的默认设置或其他状态信息
- /Library/Caches: 缓存文件,不备份,不会再退出时删除
- /tmp :临时文件,重启删除
//取得tmp目录
NSString *fileName=[NSString stringWithFormat:@"%@_%@", [[NSProcessInfo processInfo] globallyUniqueString], @"file.txt"];
NSURL *fileURL=[NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]];
NSLog(@"%@",fileURL);
//取得NSDocumentDirectory目录
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);//展开目录.
NSLog(@"%@",paths);
用NSFileManager管理系统文件
参考:http://www.cnblogs.com/jy578154186/archive/2013/02/27/2935152.html
用于执行一般的文件系统操作,主要功能包括:从一个文件中读取数据;向一个文件中写入数据;删除文件;复制文件;移动文件;比较两个文件的内容;测试文件的存在性;读取/更改文件的属性... ...
常见的NSFileManager处理文件的方法:
NSFileManager *fileManager = [[NSFileManager alloc]init]; //最好不要用defaultManager。
NSData *myData = [fileManager contentsAtPath:path]; // 从一个文件中读取数据
[fileManager createFileAtPath:path contents:myData attributes:dict];//向一个文件中写入数据,属性字典允许你制定要创建
[fileManager removeItemAtPath:path error:err];
[fileManager moveItemAtPath:path toPath:path2 error:err];
[fileManager copyItemAtPath:path toPath:path2 error:err];
[fileManager contentsEqualAtPath:path andPath:path2];
[fileManager fileExistsAtPath:path]; ... ...
常见的NSFileManager处理目录的方法如下:
[fileManager currentDirectoryPath];
[fileManager changeCurrentDirectoryPath:path];
[fileManager copyItemAtPath:path toPath:path2 error:err];
[fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:err];
[fileManager fileExistsAtPath:path isDirectory:YES];
[fileManager enumeratorAtPath:path]; //获取目录的内容列表。一次可以枚举指定目录中的每个文件。 ... ...
实例
1、文件的创建
-(IBAction) CreateFile
{
//错误信息
NSError *error;
// 创建文件管理器
NSFileManager *fileMgr = [NSFileManager defaultManager];
//指向文件目录
NSString *documentsDirectory= [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
-(IBAction) CreateFile
{
//错误信息
NSError * error;
// 创建文件管理器
NSFileManager *fileMgr = [NSFileManager defaultManager];
//指向文件目录
NSString *documentsDirectory= [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
//创建一个目录(上一个方法弃用)
// [[NSFileManager defaultManager]createDirectoryAtPath: [NSString stringWithFormat:@"%@/myFolder", NSHomeDirectory()] attributes:nil];
[[NSFileManager defaultManager]createDirectoryAtPath:[NSString stringWithFormat:@"%@/myFolder", NSHomeDirectory()] withIntermediateDirectories:NO
attributes:(NSDictionary *)nil error:&error];
// 我们想要创建的文件将会出现在文件目录中
NSString *filePath= [documentsDirectory stringByAppendingPathComponent:@"file2.txt"];
//需要写入的字符串
NSString *str= @"iPhoneDeveloper Tips\nhttp://iPhoneDevelopTips.com";
//写入文件
[str writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
//显示文件目录的内容
NSLog(@"Documentsdirectory: %@",[fileMgr contentsOfDirectoryAtPath:documentsDirectory error:&error]);
}
2、对文件重命名
想要重命名一个文件,我们需要把文件移到一个新的路径下。
//通过移动该文件对文件重命名
NSString *filePath2= [documentsDirectory
stringByAppendingPathComponent:@"file2.txt"];
//判断是否移动
if ([fileMgr moveItemAtPath:filePath toPath:filePath2 error:&error] != YES)
NSLog(@"Unable to move file: %@", [error localizedDescription]);
//显示文件目录的内容
NSLog(@"Documentsdirectory: %@",[fileMgr contentsOfDirectoryAtPath:documentsDirectoryerror:&error]);
3、删除一个文件
//在filePath2中判断是否删除这个文件
//removeItemAtPath:filePath2
if ([fileMgr removeItemAtPath:filePath2 error:&error] != YES)
{
NSLog(@"Unable to delete file: %@",[error localizedDescription]);
}
//显示文件目录的内容
NSLog(@"Documentsdirectory: %@",[fileMgr contentsOfDirectoryAtPath:documentsDirectory error:&error]);
4、删除目录下所有文件
//获取文件路径
- (NSString *)attchmentFolder{
NSString *document = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *path = [document stringByAppendingPathComponent:@"file2"];
NSFileManager *manager = [NSFileManager defaultManager];
if(![manager contentsOfDirectoryAtPath:path error:nil]){
[manager createDirectoryAtPath:path withIntermediateDirectories:NO attributes:nil error:nil];
}
return path;
}
//执行
BOOL result = [fileMgr removeItemAtPath:self.attchmentFolder error:nil];
NSLog(@"result:%@",result?@"YES":@"NO");
5、判断文件是否存在
NSString *filePath = [self dataFilePath];
if ([[NSFileManager defaultManager]fileExistsAtPath:filePath])
{
//do some thing
}
附:
-(NSString *)dataFilePath
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [paths objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:@"data.plist"];
}
判断一个给定路径是否为文件夹
NSLog(@"%@",[fileMgr fileExistsAtPath:filePath2]?@"YES":@"NO");
常用路径工具函数
NSString * NSUserName(); 返回当前用户的登录名
NSString * NSFullUserName(); 返回当前用户的完整用户名
NSString * NSHomeDirectory(); 返回当前用户主目录的路径
NSString * NSHomeDirectoryForUser(); 返回用户user的主目录
NSString * NSTemporaryDirectory(); 返回可用于创建临时文件的路径目录
常用路径工具方法
-(NSString *) pathWithComponents:components //根据components(NSArray对象)中元素构造有效路径
-(NSArray *)pathComponents //析构路径,获取路径的各个部分
-(NSString *)lastPathComponent //提取路径的最后一个组成部分
-(NSString *)pathExtension //路径扩展名
-(NSString *)stringByAppendingPathComponent:path //将path添加到现有路径末尾
-(NSString *)stringByAppendingPathExtension:ext //将拓展名添加的路径最后一个组成部分
-(NSString *)stringByDeletingPathComponent //删除路径的最后一个部分
-(NSString *)stringByDeletingPathExtension //删除路径的最后一个部分 的扩展名
-(NSString *)stringByExpandingTildeInPath //将路径中的代字符扩展成用户主目录(~)或指定用户主目录(~user)
-(NSString *)stringByResolvingSymlinksInPath //尝试解析路径中的符号链接
-(NSString *)stringByStandardizingPath //通过尝试解析~、..、.、和符号链接来标准化路径
使用路径NSPathUtilities.h
tempdir = NSTemporaryDirectory(); 临时文件的目录名
path = [fm currentDirectoryPath];
[path lastPathComponent]; 从路径中提取最后一个文件名
fullpath = [path stringByAppendingPathComponent:fname];将文件名附加到路劲的末尾
extenson = [fullpath pathExtension]; 路径名的文件扩展名
homedir = NSHomeDirectory();用户的主目录
component = [homedir pathComponents]; 路径的每个部分
数据保护API
1.文件保护
//--为filePath文件设置保护等级--
NSDictionary *attributes = [NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSFileProtectionKey];
[[NSFileManager defaultManager]setAttributes:attributes
ofItemAtPath:filePath
error:nil];
文件保护等级属性列表
NSFileProtectionNone //文件未受保护,随时可以访问 (Default)
NSFileProtectionComplete //文件受到保护,而且只有在设备未被锁定时才可访问
NSFileProtectionCompleteUntilFirstUserAuthentication //文件受到保护,直到设备启动且用户第一次输入密码
NSFileProtectionCompleteUnlessOpen //文件受到保护,而且只有在设备未被锁定时才可打开,不过即便在设备被锁定时,已经打开的文件还是可以继续使用和写入
2.keychain项保护
/* 设置keychain项保护等级 */
NSDictionary *query = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrGeneric:@"MyItem",
(__bridge id)kSecAttrAccount:@"username",
(__bridge id)kSecValueData:@"password",
(__bridge id)kSecAttrService:[NSBundle mainBundle].bundleIdentifier,
(__bridge id)kSecAttrLabel:@"",
(__bridge id)kSecAttrDescription:@"",
(__bridge id)kSecAttrAccessible:(__bridge id)kSecAttrAccessibleWhenUnlocked};
SecItemAdd((__bridge CFDictionaryRef)(query), NULL);
//keychain项保护等级列表
kSecAttrAccessibleWhenUnlocked //keychain项受到保护,只有在设备未被锁定时才可以访问
kSecAttrAccessibleAfterFirstUnlock //keychain项受到保护,直到设备启动并且用户第一次输入密码
kSecAttrAccessibleAlways //keychain未受保护,任何时候都可以访问 (Default)
kSecAttrAccessibleWhenUnlockedThisDeviceOnly //keychain项受到保护,只有在设备未被锁定时才可以访问,而且不可以转移到其他设备
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly //keychain项受到保护,直到设备启动并且用户第一次输入密码,而且不可以转移到其他设备
kSecAttrAccessibleAlwaysThisDeviceOnly //keychain未受保护,任何时候都可以访问,但是不能转移到其他设备
实例:
//加密
NSString *documentsPath =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *filePath3 = [documentsPath stringByAppendingPathComponent:@"DataProtect"];
NSString *infoString= @"iPhoneDeveloper Tips\nhttp://iPhoneDevelopTips.com";
[infoString writeToFile:filePath3 atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSDictionary *attributes = [NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSFileProtectionKey];
[[NSFileManager defaultManager] setAttributes:attributes ofItemAtPath:filePath3 error:nil];
NSLog(@"Documentsdirectory: %@",[fileMgr contentsOfDirectoryAtPath:documentsDirectory error:&error]);
文件共享(iTunes)
在Info.plist 中 加入
<key>UIFileSharingEnabled</key>
<true/>
文件共享(in apps)
如果你的app需要打开它不支持的文件,请使用UIDocumentInteractionController。一个documentinteraction controller通过Quick Look框架判断文档是否能被另一个app打开和预览。也就是说,你的app通过documentinteraction controller提供给用户一些可选的操作。可以对同步到app Documents/Shared目录下的文件使用documentinteraction controller。
要使用一个document interaction controller,需要以下步骤:
==1. 针对每个你想打开的文件,创建相应的UIDocumentInteractionController实例。
在app的UI中提供一个代表这个文件的图像化表示(一般,你应该显示文件名或者图标)。
- 如果用户和这个对象发生交互,例如触摸了这个控件,则调用document interaction controller显示一个如下的界面:==
- 预览文件的内容。
- 一个包含预览和打开操作的菜单。你可以通过实现某些委托方法,向菜单中加入其他操作,比如复制、打印。
- 一个菜单,仅包含“以其它方式打开”操作。
1.创建
//--open in--
//创建
- (UIDocumentInteractionController *) setupControllerWithURL: (NSURL *)fileURL usingDelegate: (id <UIDocumentInteractionControllerDelegate>) interactionDelegate {
UIDocumentInteractionController *interactionController =
[UIDocumentInteractionController interactionControllerWithURL: fileURL];
interactionController.delegate = interactionDelegate;
return interactionController;
}
2.呈现
- 模式化显示文件预览窗口,调用
presentPreviewAnimated:
方法。 - 通过弹出菜单提示用户选择相应动作,调用
presentOptionsMenuFromRect:inView:animated:
或者presentOptionsMenuFromBarButtonItem:animated:
方法。 - 提示用户用其他程序打开该文件,调用
presentOpenInMenuFromRect:inView:animated:
方法或者presentOpenInMenuFromBarButtonItem:animated:
方法。
注册应用程序支持的文档类型
如果你的程序可以打开某种特定的文件类型,则你可以通过Info.pllist 文件注册程序所能打开的文档类型。当其他程序向系统询问哪些程序可以识别该类型的文件时,你的程序将会被列到选项菜单中,供用户选择。
Demo
1.设置打开方式
2.在.m文件中
@property(strong,nonatomic) UIDocumentInteractionController * documentInteractionController;
- (void)onOpenWith:(UIButton *)theButton path:(NSString *)path
{
NSURL *URL = [NSURL fileURLWithPath:path];
if (URL) {
self.documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:URL];
self.documentInteractionController.delegate = self;
//self.documentInteractionController.name = @"New";
//self.documentInteractionController.UTI = @"com.adobe.pdf";
[self.documentInteractionController presentOptionsMenuFromRect:CGRectZero
inView:theButton
animated:YES];
}
}
4.定义documentInteractionControllerView
#pragma mark - UIDocumentInteractionControllerDelegate
- (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *)controller
{
return self;
}
- (UIView *)documentInteractionControllerViewForPreview:(UIDocumentInteractionController *)controller
{
return self.view;
}
- (CGRect)documentInteractionControllerRectForPreview:(UIDocumentInteractionController *)controller
{
return self.view.frame;
}
5.在 View 加个 button 并关联事件
- (IBAction)btpr:(id)sender {
NSString *document = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *path = [document stringByAppendingPathComponent:@"file2.txt"];
[self onOpenWith:sender path:path];
}
6.在AppDelegate.m 中加上
-(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options{
return YES;
}
iCloud
UIDocumentPickerViewController
// Initializes the picker instance for selecting a document in a remote location. The valid modes are Import and Open.
- (instancetype)initWithDocumentTypes:(NSArray <NSString *>*)allowedUTIs inMode:(UIDocumentPickerMode)mode NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
// Initializes the picker for exporting a local file to an external location. The valid modes are Export and Move. The new location will be returned using didPickDocumentAtURL:
- (instancetype)initWithURL:(NSURL *)url inMode:(UIDocumentPickerMode)mode NS_DESIGNATED_INITIALIZER;
@property (nullable, nonatomic, weak) id<UIDocumentPickerDelegate> delegate;
@property (nonatomic, assign, readonly) UIDocumentPickerMode documentPickerMode;
iCloud 文件访问
步骤:
1.在 app_id 中加 iCloud 功能
2.在 app_id 中加 iCloud containers 功能
3.在 developerCenter 中加入 iCloud 授权
4.链接 CloudKit.framework (自动)
代码示范:
//--iCloud--
//导入
UIDocumentMenuViewController * mvc1 =[[UIDocumentMenuViewController alloc]initWithDocumentTypes:@[@"public.content"] inMode:UIDocumentPickerModeImport];
UIDocumentPickerViewController * picker =[[UIDocumentPickerViewController alloc]initWithDocumentTypes:@[@"public.content"] inMode:UIDocumentPickerModeImport];
picker.delegate = self;
[self presentViewController:pick animated:YES completion:nil];
//导出
UIDocumentMenuViewController * mvc3 =[[UIDocumentMenuViewController alloc]initWithURL:fileURL inMode:UIDocumentPickerModeExportToService];
UIDocumentPickerViewController * mvc4 =[[UIDocumentPickerViewController alloc]initWithURL:fileURL inMode:UIDocumentPickerModeExportToService];
//获取到文档
-(void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url{
BOOL gotAccess = [url startAccessingSecurityScopedResource];
if(gotAccess){
//direct access
NSData * data = [NSData dataWithContentsOfURL:url];
//coordinated access
NSFileCoordinator *fileControl = [NSFileCoordinator new];
[fileControl coordinateReadingItemAtURL:url options:NSFileCoordinatorReadingWithoutChanges error:nil byAccessor:^(NSURL * _Nonnull newURL) {
NSData * data = [NSData dataWithContentsOfURL:newURL];
}];
}
[url stopAccessingSecurityScopedResource];
}
操作方式
UIDocumentPickerModeImport,//导入
UIDocumentPickerModeOpen,//打开
UIDocumentPickerModeExportToService,//导出
UIDocumentPickerModeMoveToService//直接访问
对象归档(把 app 对象存到文件)
1.简单版本:系统类归档
//准备数据
CGPoint point = CGPointMake(1.0, 2.0);
NSString *info = @"坐标原点";
NSInteger value = 10;
NSString *multiHomePath = [NSHomeDirectory() stringByAppendingPathComponent:@"multi.archiver"];
NSMutableData *data = [[NSMutableData alloc]init];
NSKeyedArchiver *archvier = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data];
NSLog(@"%@",multiHomePath);
//对多个对象进行归档
[archvier encodeCGPoint:point forKey:@"kPoint"];
[archvier encodeObject:info forKey:@"kInfo"];
[archvier encodeInteger:value forKey:@"kValue"];
[archvier finishEncoding];
[data writeToFile:multiHomePath atomically:YES];
//接档
NSMutableData *dataR = [[NSMutableData alloc]initWithContentsOfFile:multiHomePath];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData:dataR];
CGPoint pointR = [unarchiver decodeCGPointForKey:@"kPoint"];
NSString *infoR = [unarchiver decodeObjectForKey:@"kInfo"];
NSInteger valueR = [unarchiver decodeIntegerForKey:@"kValue"];
[unarchiver finishDecoding];
NSLog(@"%f,%f,%@,%ld",pointR.x,pointR.y,infoR,(long)valueR);
2.复杂版本
步骤:
1.Archive.h文件
@interface Archive : NSObject<NSCoding>
@property (copy,nonatomic) NSString *name;
@property NSInteger age;
@property (copy,nonatomic) NSString *address;
@property (copy,nonatomic) UIImage *photo;
@end
2.Archiver.m文件
#import "Archive.h"
#define kNameKey @"NameKey"
#define kAgeKey @"AgeKey"
#define kAddress @"AddressKey"
#define kPhotoKey @"PhotoKey"
@implementation Archive
#pragma mark - NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:_name forKey:kNameKey];
[aCoder encodeInteger:_age forKey:kAgeKey];
[aCoder encodeObject:_address forKey:kAddress];
[aCoder encodeObject:_photo forKey:kPhotoKey];
}
-(id)initWithCoder:(NSCoder *)aDecoder {
_name = [aDecoder decodeObjectForKey:kNameKey];
_age = [aDecoder decodeIntegerForKey:kAgeKey];
_address = [aDecoder decodeObjectForKey:kAddress];
_photo = [aDecoder decodeObjectForKey:kPhotoKey];
return self;
}
#pragma mark - NSCoping
- (id)copyWithZone:(NSZone *)zone {
Archive *copy = [[[self class] allocWithZone:zone] init];
copy.name = [self.name copyWithZone:zone];
copy.age = self.age;
copy.address = [self.address copyWithZone:zone];
copy.photo = self.photo;
return copy;
}
@end
3.xib 中加入2个 button 1个 imageView
4.ViewController
#import "ArchiveViewController.h"
#import "Archive.h"
#define kArchivingDataKey @"ArchivingDataKey"
@interface ArchiveViewController ()
- (IBAction)save:(id)sender;
- (IBAction)loadArchive:(id)sender ;
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (copy,nonatomic) NSString *archivingFilePath;
@end
@implementation ArchiveViewController
//保存图片与归档
- (IBAction)save:(id)sender {
//准备数据
NSString *name = @"静静";
NSInteger age = 16;
NSString *address = @"猜~";
UIImage *photo = [UIImage imageNamed:@"girl"];
//存储数据到类
Archive *archivingData = [[Archive alloc] init];
archivingData.name = name;
archivingData.age = age;
archivingData.address = address;
archivingData.photo = photo;
//归档
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
NSLog(@"%@",self.archivingFilePath);
[archiver encodeObject:archivingData forKey:kArchivingDataKey]; // archivingDate的encodeWithCoder方法被调用
[archiver finishEncoding];
//写入文件
[data writeToFile:self.archivingFilePath atomically:YES];
}
- (IBAction)loadArchive:(id)sender {
NSData *data = [[NSMutableData alloc] initWithContentsOfFile:self.archivingFilePath];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
//获得类
Archive *archivingData = [unarchiver decodeObjectForKey:kArchivingDataKey];// initWithCoder方法被调用
[unarchiver finishDecoding];
//读取的数据
NSString *name = archivingData.name;
NSInteger age = archivingData.age;
NSString *address = archivingData.address;
self.imageView.image = archivingData.photo;
NSLog(@"%@||%ld||%@",name,(long)age,address);
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.archivingFilePath = [NSHomeDirectory() stringByAppendingPathComponent:@"multi.archiver"];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
5.完成撒花!!