本文目录结构如下:
一、iOS中的沙盒机制
二、文件系统
三、属性列表 (Property List)
四、偏好设置 (NSUserDefaults)
五、对象归档
六、SQLite3
七、Core Data
为了避免篇幅太长,本文重点介绍前6部分内容,Core Data会在后续博文中详细讲解。
一、iOS中的沙盒机制 什么是沙盒 不管是Mac OS X 还是iOS的文件系统都是建立在UNIX文件系统基础之上的。iOS应用程序只能对自己创建的文件系统读取文件,这个独立、封闭、安全的空间,叫做沙盒。它一般存放着程序包文件(可执行文件)、图片、音频、视频、plist文件、sqlite数据库以及其他文件。每个应用程序都有自己的独立的存储空间(沙盒)。一般来说应用程序之间是不可以互相访问。(提示:在IOS8中已经开放访问)。
沙盒模型到底有哪些好处呢?
安全:别的App无法修改你的程序或数据
保护隐私:别的App无法读取你的程序和数据
方便删除:因为一个App所有产生的内容都在自己的沙盒中,所以删除App只需要将沙盒删除就可以彻底删除程序了
iOS App沙盒中的目录
App Bundle :如xxx.app,其实是一个目录,里面有app本身的二进制数据以及资源文件。由于应用程序必须经过签名, 所以您在运行时不能对这个目录中的内容进行修改,否则可能会使应用程序无法启动
Documents :最常用的目录,一般需要持久的数据都放在此目录中,可以在当中添加子文件夹,iTunes备份和恢复的时候,会包括此目录。
Library :设置程序的默认设置和其他状态信息。下面默认包含下面两个目录 Caches和Preferences。
Caches 目录用于存放应用程序专用的支持文件,保存应用程序再次启动过程中需要的信息。 iTunes不会同步此文件夹,适合存储体积大,不需要备份的非重要数据。
Preferences 目录包含应用程序的偏好设置文件。您不应该直接创建偏好设置文件,而是应该使用NSUserDefaults类来取得和设置应用程序的偏好。 iTunes同步该应用时会同步此文件夹中的内容。
tmp :iTunes不会同步此文件夹,系统可能在应用没运行时就删除该目录下的文件,所以此目录适合保存应用中的一些临时文件,用完就删除。
模拟器沙盒的位置: 1 2 NSLog (@"%@" ,NSHomeDirectory ());
二、文件系统 1、获取沙盒目录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #import "ViewController.h" @interface ViewController ()@end @implementation ViewController - (void )viewDidLoad { [super viewDidLoad]; [self fileOperation_Directory]; } * 获取沙盒目录 */ - (void )fileOperation_Directory { NSString *homePath = NSHomeDirectory (); NSLog (@"---根目录 : %@" ,homePath); NSArray *docPaths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory , NSUserDomainMask , YES ); NSString *docPath = [docPaths lastObject]; NSLog (@"---Document目录 : %@" ,docPath); NSArray *libPaths = NSSearchPathForDirectoriesInDomains (NSLibraryDirectory , NSUserDomainMask , YES ); NSString *libPath = [libPaths lastObject]; NSLog (@"---Library目录 : %@" ,libPath); NSArray *cachePaths = NSSearchPathForDirectoriesInDomains (NSCachesDirectory , NSUserDomainMask , YES ); NSString *cachePath = [cachePaths lastObject]; NSLog (@"---Cache目录 : %@" ,cachePath); NSString *tempPath = NSTemporaryDirectory (); NSLog (@"---temp目录 : %@" ,tempPath); }
这些代码本身没什么好说的,但大家可能和我一样,都会有个疑问:为什么获取Document、Library目录时,需要用NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
方法?
我们每次重新运行应用程序,会发现打印的根目录会有所不同。例如:
1 2 3 根目录 : /Users/ richfitbi/Library/ Developer/CoreSimulator/ Devices/B04AE26C-5A22-4FB1-A593-69678F42BF96/ data/Containers/ Data/Application/ 015 BCA36-A207-400 C-B511-3 E5405A54BFC 根目录 : /Users/ richfitbi/Library/ Developer/CoreSimulator/ Devices/B04AE26C-5A22-4FB1-A593-69678F42BF96/ data/Containers/ Data/Application/ 9074 D659-52 F1-4450 -B36B-4711 C0650140
这是因为iPhone会为每一个应用程序生成一个私有目录, 并随即生成一个数字字母串作为目录名,在每一次应用程序启动时,这个字母数字串都是不同于上一次。
为了能够方便得到Document等目录的绝对路径,便可使用NSSearchPathForDirectoriesInDomains方法。
1 NSArray *NSSearchPathForDirectoriesInDomains (NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde);
参数说明:
directory 目录类型 比如Documents目录 就是NSDocumentDirectory
domainMask 在iOS的程序中这个取NSUserDomainMask
expandTilde YES,表示将~展开成完整路径
该方法可描述为: NSSearchPathForDirectoriesInDomains(“想要查找的目录”,“想要从哪个路径区域保护区查找”,,“是否将~展开成完整路径”)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 typedef NS_OPTIONS (NSUInteger , NSSearchPathDomainMask ) { NSUserDomainMask = 1 , NSLocalDomainMask = 2 , NSNetworkDomainMask = 4 , NSSystemDomainMask = 8 , NSAllDomainsMask = 0x0ffff }; NSApplicationDirectory = 1 , NSDemoApplicationDirectory , NSDeveloperApplicationDirectory , NSAdminApplicationDirectory , NSLibraryDirectory , NSDeveloperDirectory , NSUserDirectory , NSDocumentationDirectory , NSDocumentDirectory , NSCoreServiceDirectory , NSAutosavedInformationDirectory NS_ENUM_AVAILABLE (10 _6, 4 _0) = 11 , NSDesktopDirectory = 12 , NSCachesDirectory = 13 , NSApplicationSupportDirectory = 14 , NSDownloadsDirectory NS_ENUM_AVAILABLE (10 _5, 2 _0) = 15 , NSInputMethodsDirectory NS_ENUM_AVAILABLE (10 _6, 4 _0) = 16 , NSMoviesDirectory NS_ENUM_AVAILABLE (10 _6, 4 _0) = 17 , NSMusicDirectory NS_ENUM_AVAILABLE (10 _6, 4 _0) = 18 , NSPicturesDirectory NS_ENUM_AVAILABLE (10 _6, 4 _0) = 19 , NSPrinterDescriptionDirectory NS_ENUM_AVAILABLE (10 _6, 4 _0) = 20 , NSSharedPublicDirectory NS_ENUM_AVAILABLE (10 _6, 4 _0) = 21 , NSPreferencePanesDirectory NS_ENUM_AVAILABLE (10 _6, 4 _0) = 22 , NSApplicationScriptsDirectory NS_ENUM_AVAILABLE (10 _8, NA) = 23 , NSItemReplacementDirectory NS_ENUM_AVAILABLE (10 _6, 4 _0) = 99 , NSAllApplicationsDirectory = 100 , NSAllLibrariesDirectory = 101 , NSTrashDirectory NS_ENUM_AVAILABLE (10 _8, NA) = 102 }; NSHomeDirectory ()只能到达用户的主目录
这里不知道为何,NSSearchPathDomainMask中没有preference目录对应的枚举,而是有一个NSPreferencePanesDirectory,希望哪位高人可以解释以下。
2、NSString关于路径的处理方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 /* * NSString类中,路径的处理方法 * - (NSArray * )pathComponents; //获得组成此路径的各个组成部分,结果("/" ,"Users" ,"apple" ,"testfile.txt" ) * - (NSString * )lastPathComponent; 提取路径的最后一个组成部分,结果:testfile.txt * - (NSString * )stringByDeletingLastPathComponent; 删除路径的最后一个组成部分,结果:/Users/apple * - (NSString * )stringByAppendingPathComponent:(NSString * )str; 在原路径的末尾添加新元素,结果:/Users/apple/testfile.txt/app.txt * - (NSString * )pathExtension; 获取路径最后部分的扩展名,结果:text * - (NSString * )stringByDeletingPathExtension; 删除路径最后部分的扩展名,结果:/Users/apple/testfile * - (NSString * )stringByAppendingPathExtension:(NSString * )str; 路径最后部分追加扩展名,结果:/User/apple/testfile.txt.jpg * / - (void)filePath_NSStringPathExtensions { NSString * path = NSLog( NSLog( NSLog( NSLog( NSLog( NSLog( NSLog( NSLog( }
3、NSBundle bundle是一个目录,其中包含了程序会使用到的资源。 这些资源包含了如图像、声音、编译好的代码、nib文件(用户也会把bundle称为plug-in)。对应bundle,cocoa提供了类NSBundle。
我们的程序是一个bundle。在Finder中,一个应用程序看上去和其他文件没有什么区别。但是实际上它是一个包含了nib文件,编译代码,以及其他资源的目录. 我们把这个目录叫做程序的main bundle。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 * NSBundle用法 */ - (void )use_NSBundle { NSBundle *myBundle = [NSBundle mainBundle]; NSLog (@".app路径---%@" ,myBundle); NSLog (@".app应用包(文件)的详细信息---%@" ,[myBundle infoDictionary]); NSString *imgPathInGroup = [[NSBundle mainBundle] pathForResource:@"star_32x32" ofType:@"png" ]; NSLog (@"imgPathInGroup:%@" ,imgPathInGroup); NSString *imgPathInFolder = [[NSBundle mainBundle] pathForResource:@"map-marker-bubble-pink-small@3x" ofType:@"png" ]; NSLog (@"imgPathInFolder:%@" ,imgPathInFolder); NSString *amapBundlePath = [[NSBundle mainBundle] pathForResource:@"AMap" ofType:@"bundle" ]; NSLog (@"amapBundlePath: %@" , amapBundlePath); NSString *redPinImgPath = [[NSBundle bundleWithPath:amapBundlePath] pathForResource:@"redPin@3x" ofType:@"png" inDirectory:@"images" ]; NSLog (@"path: %@" , redPinImgPath); NSString *amap3DBundlePath = [[NSBundle mainBundle] pathForResource:@"AMap3D" ofType:@"bundle" inDirectory:@"AMap.bundle" ]; NSLog (@"amap3DBundlePath: %@" , amap3DBundlePath); NSString *tglDataPath = [[NSBundle bundleWithPath:amap3DBundlePath] pathForResource:@"tgl" ofType:@"data" ]; NSLog (@"tglDataPath: %@" , tglDataPath); }
需要说明的是:
1、bundle路径每次运行都会改变,原因上文介绍NSSearchPathForDirectoriesInDomains
方法时已经阐述。
2、bundle下的目录结构不同于project文档结构。不管把图片放在根目录,或者放在未做文件关联的Group下(实际还是根目录),还是放在文件夹下,编译完之后,都会直接放在.app包根目录下,不会在.app包内生成文件夹。.app包位置可通过上面代码打印结果查看。
图1:工程文件Xcode中结构
图2:工程文件Finder中结构
图3:工程名.app包内结构
3、如上图所示的avatar.png图片,同名文件,编译后,会只保留一个,会保留你最晚添加的那个文件。
4、对于Assets.xcassets中的素材:
- 只支持png格式的图片
- 图片只支持[UIImage imageNamed]的方式实例化,但是不能从Bundle 中加载
- 在编译时,Images .xcassets中的所有文件会被打包为Assets .car的文件
5、[[NSBundle mainBundle] infoDictionary] 返回内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 { BuildMachineOSBuild = 14 F27; CFBundleDevelopmentRegion = en; CFBundleExecutable = FileReadWrite; CFBundleIdentifier = "lyl.FileReadWrite" ; CFBundleInfoDictionaryVersion = "6.0" ; CFBundleInfoPlistURL = "Info.plist -- file:///Users/richfitbi/Library/Developer/CoreSimulator/Devices/B04AE26C-5A22-4FB1-A593-69678F42BF96/data/Containers/Bundle/Application/3F1A9136-B0D4-4373-AEC4-A208CFDF8039/FileReadWrite.app/" ; CFBundleName = FileReadWrite; CFBundleNumericVersion = 16809984 ; CFBundlePackageType = APPL; CFBundleShortVersionString = "1.0" ; CFBundleSignature = "????" ; CFBundleSupportedPlatforms = ( iPhoneSimulator ); CFBundleVersion = 1 ; DTCompiler = "com.apple.compilers.llvm.clang.1_0" ; DTPlatformBuild = "" ; DTPlatformName = iphonesimulator; DTPlatformVersion = "9.0" ; DTSDKBuild = 13 A340; DTSDKName = "iphonesimulator9.0" ; DTXcode = 0700 ; DTXcodeBuild = 7 A220; LSRequiresIPhoneOS = 1 ; MinimumOSVersion = "9.0" ; UIDeviceFamily = ( 1 ); UILaunchStoryboardName = LaunchScreen; UIMainStoryboardFile = Main; UIRequiredDeviceCapabilities = ( armv7 ); UISupportedInterfaceOrientations = ( UIInterfaceOrientationPortrait, UIInterfaceOrientationLandscapeLeft, UIInterfaceOrientationLandscapeRight ); }
我们可以根据这个Dictionary获取大量信息,例如[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]]
可任意获取版本信息。
4、NSData NSData是用来包装数据的。NSData存储的是二进制数据,屏蔽了数据之间的差异,文本、音频、图像等数据都可用NSData来存储。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 * NSData转换 */ - (void )transform_NSData { NSString *aString = @"1234abcd" ; NSData *aData = [aString dataUsingEncoding: NSUTF8StringEncoding ]; NSString *aString2 = [[NSString alloc] initWithData:aData encoding:NSUTF8StringEncoding ]; NSLog (@"%@" ,aString2); NSString *path = [[NSBundle mainBundle] bundlePath]; NSString *name = [NSString stringWithFormat:@"avatar.png" ]; NSString *finalPath = [path stringByAppendingPathComponent:name]; NSData *imageData = [NSData dataWithContentsOfFile: finalPath]; UIImage *aimage = [UIImage imageWithData: imageData]; NSString *plistPath1 = [[NSBundle mainBundle] pathForResource:@"Info" ofType:@"plist" ]; NSString *plistPath2 = [NSString stringWithFormat:@"%@/%@/%@" , NSHomeDirectory (),@"Documents" ,@"Info.plist" ]; NSLog (@"\n\n%@\n\n%@" ,plistPath1,plistPath2); NSDictionary *dic1 = [NSDictionary dictionaryWithContentsOfFile:plistPath1]; NSLog (@"%@" ,dic1); }
5、NSFileManager 用于执行一般的文件系统操作。 主要功能包括:从一个文件中读取数据;向一个文件中写入数据;删除文件;复制文件;移动文件;比较两个文件的内容;测试文件的存在性;读取/更改文件的属性等等。
没什么好说的,直接上代码,注意一下”文件移动”就行了。代码很凌乱,有兴趣的同学可以整理下,形成自己的工具库。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 * NSFileManager用法 */ - (void )use_NSFileManager { NSFileManager *fileManager = [NSFileManager defaultManager]; NSArray *docPaths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory , NSUserDomainMask , YES ); NSString *docPath = [docPaths lastObject]; NSString *folderPath1 = [docPath stringByAppendingPathComponent:@"MyFolder1" ]; BOOL isDir = YES ; if ([fileManager fileExistsAtPath:folderPath1 isDirectory:&isDir]) { NSLog (@"MyFolder1文件夹已存在" ); } BOOL folder1CreateSuccess = [fileManager createDirectoryAtPath:folderPath1 withIntermediateDirectories:YES attributes:nil error:nil ]; if (folder1CreateSuccess) { NSLog (@"文件夹创建成功" ); }else { NSLog (@"文件夹创建失败" ); } NSString *file1Path = [folderPath1 stringByAppendingPathComponent:@"test1.txt" ]; if ([fileManager fileExistsAtPath:file1Path]) { NSLog (@"test1.txt文件已存在" ); } NSString *file1Text = @"abcdefg" ; NSData *file1Data = [file1Text dataUsingEncoding: NSUTF8StringEncoding ]; BOOL file1CreateSuccess = [fileManager createFileAtPath:file1Path contents:file1Data attributes:nil ]; if (file1CreateSuccess) { NSLog (@"文件创建成功: %@" ,file1Path); }else { NSLog (@"文件创建失败" ); } NSString *folderPath2 = [docPath stringByAppendingPathComponent:@"MyFolder2" ]; folderPath2 = [folderPath2 stringByAppendingPathComponent:@"MyFolder2Sub" ]; [fileManager createDirectoryAtPath:folderPath2 withIntermediateDirectories:YES attributes:nil error:nil ]; NSString *file2Path = [folderPath2 stringByAppendingPathComponent:@"test2.txt" ]; NSString *file2Text = @"hijklmn" ; NSData *file2Data = [file2Text dataUsingEncoding: NSUTF8StringEncoding ]; [fileManager createFileAtPath:file2Path contents:file2Data attributes:nil ]; NSString *content = @"测试写入内容!" ; BOOL res = [content writeToFile:file1Path atomically:YES encoding:NSUTF8StringEncoding error:nil ]; if (res) { NSLog (@"文件写入成功" ); }else NSLog (@"文件写入失败" ); NSData *fileManagerGetData = [fileManager contentsAtPath:file1Path]; NSLog (@"NSFileManager-读取内容 : %@" ,[[NSString alloc] initWithData:fileManagerGetData encoding:NSUTF8StringEncoding ]); NSData *nsDataGetData = [NSData dataWithContentsOfFile:file1Path]; NSLog (@"NSData-读取内容 : %@" ,[[NSString alloc] initWithData:nsDataGetData encoding:NSUTF8StringEncoding ]); NSString *nsStringcontent = [NSString stringWithContentsOfFile:file1Path encoding:NSUTF8StringEncoding error:nil ]; NSLog (@"NSString-读取内容 : %@" ,nsStringcontent); NSDictionary *attrDic = [fileManager attributesOfItemAtPath:file1Path error:nil ]; NSNumber *fileSize = [attrDic objectForKey:NSFileSize ]; NSLog (@"fileSize : %llu" ,[fileSize unsignedLongLongValue]); NSDirectoryEnumerator *dirEnum = [fileManager enumeratorAtPath:docPath]; NSString *path = nil ; while ((path = [dirEnum nextObject]) != nil ){ NSLog (@"%@" ,path); } NSString *toPath = [docPath stringByAppendingPathComponent:@"MyFolder3" ]; [fileManager createDirectoryAtPath:toPath withIntermediateDirectories:YES attributes:nil error:nil ]; NSError *error; toPath = [toPath stringByAppendingPathComponent:@"test2.txt" ]; BOOL isMoveSuccess = [fileManager moveItemAtPath:file2Path toPath:toPath error:&error]; if (isMoveSuccess) { NSLog (@"文件移动成功" ); }else { NSLog (@"%@" ,error.localizedDescription ); } NSString *toPath2 = [docPath stringByAppendingPathComponent:@"MyFolder3" ]; [fileManager moveItemAtPath:[toPath2 stringByAppendingPathComponent:@"test2.txt" ] toPath:[toPath2 stringByAppendingPathComponent:@"test3.txt" ] error:&error]; NSString *srcPath3 = [docPath stringByAppendingPathComponent:@"MyFolder3" ]; srcPath3 = [srcPath3 stringByAppendingPathComponent:@"test3.txt" ]; NSString *dstPath3 = [docPath stringByAppendingPathComponent:@"MyFolder2" ]; dstPath3 = [dstPath3 stringByAppendingPathComponent:@"test2.txt" ]; [fileManager copyItemAtPath:srcPath3 toPath:dstPath3 error:&error]; [fileManager removeItemAtPath:[docPath stringByAppendingPathComponent:@"MyFolder2" ] error:&error]; }
6、NSFileHandle
NSFileHandle类主要对文件内容进行读取和写入操作
NSFileManager类主要对文件的操作(删除、修改、移动、复制等等)
NSFileHandle类可以实现如下功能:
打开一个文件,执行读、写或更新(读写)操作;
在文件中查找指定位置;
从文件中读取特定数目的字节,或将特定数目的字节写入文件中
另外,NSFileHandle类提供的方法也可以用于各种设备或套接字。
一般而言,我们处理文件时都要经历以下三个步骤:
打开文件,获取一个NSFileHandle对象(以便在后面的I/O操作中引用该文件)。
对打开文件执行I/O操作。
关闭文件。
1 2 3 4 5 6 7 8 NSFileHandle *fileHandle = ; fileHandle = ; //打开一个文件准备读取 fileHandle = ; fileHandle = ; fileData = ; // 从设备或者通道返回可用的数据 fileData = ; ; //将NSData数据写入文件 ; //关闭文件
常用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 + (id )fileHandleForReadingAtPath:(NSString *)path 打开一个文件准备读取 + (id )fileHandleForWritingAtPath:(NSString *)path 打开一个文件准备写入 + (id )fileHandleForUpdatingAtPath:(NSString *)path 打开一个文件准备更新 - (NSData *)availableData; 从设备或通道返回可用的数据 - (NSData *)readDataToEndOfFile; 从当前的节点读取到文件的末尾 - (NSData *)readDataOfLength:(NSUInteger )length; 从当前节点开始读取指定的长度数据 - (void )writeData:(NSData *)data; 写入数据 - (unsigned long long )offsetInFile; 获取当前文件的偏移量 - (void )seekToFileOffset:(unsigned long long )offset; 跳到指定文件的偏移量 - (unsigned long long )seekToEndOfFile; 跳到文件末尾 - (void )truncateFileAtOffset:(unsigned long long )offset; 将文件的长度设为offset字节 - (void )closeFile; 关闭文件
三、属性列表(Property List) 属性列表提供了一个方便的方式来存储简单的结构化数据。它是以XML格式存储的。您不能使用属性列表保存所有类型的数据。在属性列表中的项目的数据类型,包括:数组(Array)、字典(Dictionary)、字符串(String)等等。
属性列表常被IOS用于保存应用程序设置属性,但这并不意味着你不使用它到其他用途。但它是被设计用于存储少量的数据。
所谓用属性列表进行数据持久化, 就是针对数组、字典集合类调用writeToFile:atomically
方法和initWithContentsOfFile
方法来写入数据。数组、字典只能将BOOL、NSNumber、NSString、NSData、NSDate、NSArray、NSDictionary写入属性列表plist文件
1、属性列表的结构 当我们新建一个Contacts.plist文件之后,Open as Source Code,查看它的结构如下:
1 2 3 4 5 6 7 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version ="1.0" > <dict > </dict > </plist >
我们可以在Open as Property List的查看模式下,选中root,可以看到type列可以更改root为Dictionary或是Array。更改成Array之后,Open as Source Code,查看它的结构如下:
1 2 3 4 5 <?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 /> </plist >
plist文件root下添加其它节点时,可以看到type列可以选择:Dictionary、Array、Boolean、Data、Date、Number、String类型。
2、用属性列表保存数据 工程下的plist文件,我们不能在项目运行时修改,因此需要将项目资源的Contacts.plist文件中数据复制到沙箱Documents目录下再进行增删改查操作。
当我们用String、NSData类型直接写入文件时,会破坏plist文件结构: 比如:
1 2 3 4 - (void )writeStringDataToPList { NSString *content = @"abcd" ; [content writeToFile:self .operateFilePath atomically:YES encoding:NSUTF8StringEncoding error:nil ]; }
运行之后,你会发现plist文件原本的XML结构没有了,成了纯文本文件。因此,我们应该用属性列表保存的数据,应该是NSDictionary、NSArray。
示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 #import "ViewController.h" @interface ViewController ()@property (nonatomic , strong ) NSString *operateFilePath;@end @implementation ViewController - (void )viewDidLoad { [super viewDidLoad]; self .operateFilePath = [self applicationDocumentsDirectoryFile]; [self createEditableCopyOfDatabaseIfNeeded]; [self writeArrayDataToPList]; } - (NSString *)applicationDocumentsDirectoryFile { NSString *documentDirectory = [NSSearchPathForDirectoriesInDomains (NSDocumentDirectory , NSUserDomainMask , YES ) lastObject]; NSString *path = [documentDirectory stringByAppendingPathComponent:@"Contacts.plist" ]; NSLog (@"Document path :%@" ,path); return path; } -(void )createEditableCopyOfDatabaseIfNeeded { NSFileManager *fileManager = [NSFileManager defaultManager]; BOOL dbexits = [fileManager fileExistsAtPath:self .operateFilePath ]; if (!dbexits) { NSLog (@"resourcePath :%@" ,[[NSBundle mainBundle] resourcePath]); NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Contacts.plist" ]; NSError *error; BOOL success=[fileManager copyItemAtPath:defaultDBPath toPath:self .operateFilePath error:&error]; if (!success) { NSAssert1 (0 ,@"错误写入文件:‘%@’" ,[error localizedDescription]); } } } - (void )writeStringDataToPList { NSString *content = @"abcd" ; [content writeToFile:self .operateFilePath atomically:YES encoding:NSUTF8StringEncoding error:nil ]; } - (void )writeArrayDataToPList { NSArray *array = @[@(123 ), @"中国" , @(3 ), @(3 LL), @(-3 ), @(3.0 ), @(3.1 ), @(3.1 f)]; [array writeToFile:self .operateFilePath atomically:YES ]; NSString *stringcontent = [NSString stringWithContentsOfFile:self .operateFilePath encoding:NSUTF8StringEncoding error:nil ]; NSLog (@"NSString-读取内容 : %@" ,stringcontent); NSArray *arrayContent = [NSArray arrayWithContentsOfFile:self .operateFilePath ]; NSLog (@"arrayContent : %@" ,arrayContent); } - (void )writeDictionaryDataToPList { NSDictionary *dic = @{@"first" : @"中国" , @"second" : @(3 ), @"third" : @(3 LL), @"fourth" : @(-3 ), @"fifth" : @(3.0 ), @"sixth" : @(3.1 ), @"seventh" : @(3.1 f), }; [dic writeToFile:self .operateFilePath atomically:YES ]; NSString *stringcontent = [NSString stringWithContentsOfFile:self .operateFilePath encoding:NSUTF8StringEncoding error:nil ]; NSLog (@"NSString-读取内容 : %@" ,stringcontent); NSDictionary *dicContent = [NSDictionary dictionaryWithContentsOfFile:self .operateFilePath ]; NSLog (@"arrayContent : %@" ,dicContent); }
运行writeArrayDataToPList
方法之后,打印结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 NSString-读取内容 : <?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 > true<integer > 123</integer > true<string > 中国</string > true<integer > 3</integer > true<integer > 3</integer > true<integer > -3</integer > true<real > 3</real > true<real > 3.1000000000000001</real > true<real > 3.0999999046325684</real > </array > </plist > arrayContent : ( 123, "\U4e2d\U56fd", 3, 3, "-3", 3, "3.1", "3.099999904632568" )
你会发现对于浮点数,保存后不准确了,这个问题的根源大家自行google,或看下唐巧大神的这篇文章 。如果只是涉及存取,建议大家还是存字符串比较好。如果涉及精确计算,可以参考iOS开发:Objective-C精确的货币计算 。
另外需要注意的是,如果集合或者字典中包含Dictionary、Array、Boolean、Data、Date、Number、String之外的任何类型,都无法写入plist文件,您可以自行实验一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 //该方法无法写入plist文件 - (void)writeDictionaryHaveObjectDataToPList { Person *p1 = ; p1.personName = @"Jack" ; p1.personPhoneNumber = @"13100000000" ; Person *p2 = ; p2.personName = @"Rose" ; p2.personPhoneNumber = @"13788888888" ; NSDictionary *dic = @{@"first" : p1, @"second" : p2, }; ; //打印文件内容 NSString *stringcontent = ; NSLog(@"NSString-读取内容 : %@" ,stringcontent); //文件内容转换回Dictionary NSDictionary *dicContent = ; NSLog(@"arrayContent : %@" ,dicContent); }
四、偏好设置(NSUserDefaults) NSUserDefaults适合存储轻量级的本地数据,用来保存应用程序设置和属性,用户保存的数据如用户名、密码。用户再次打开程序或开机后这些数据仍然存在。
使用NSUserDefaults保存的数据,会保存在特定的plist文件中,实际上就是上文我们所说的使用属性列表进行数据持久化。只不过自己建立的plist文件,需要手动创建文件,读取文件,很麻烦,而使用NSUserDefaults则不用管这些东西,就像读字符串一样,直接读取就可以了。
NSUserDefaults支持的数据格式有:NSNumber(Integer、Float、Double),NSString,NSDate,NSArray,NSDictionary,BOOL类型。如果要存储其他类型,则需要转换为前面的类型,才能用NSUserDefaults存储。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #import "ViewController.h" @interface ViewController ()@end @implementation ViewController - (void )viewDidLoad { [super viewDidLoad]; [self saveNSUserDefaults ]; [self readNSUserDefaults ]; } -(void )saveNSUserDefaults { NSString *myString = @"lyl" ; int myInteger = 100 ; float myFloat = 50.0 f; double myDouble = 20.0 ; BOOL isMe = YES ; NSDate *myDate = [NSDate date]; NSArray *myArray = [NSArray arrayWithObjects:@"hello" , @"world" , nil ]; NSDictionary *myDictionary = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"lyl" , @"30" , nil ] forKeys:[NSArray arrayWithObjects:@"name" , @"age" , nil ]]; NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; [userDefaults setInteger:myInteger forKey:@"myInteger" ]; [userDefaults setFloat:myFloat forKey:@"myFloat" ]; [userDefaults setDouble:myDouble forKey:@"myDouble" ]; [userDefaults setBool:isMe forKey:@"isMe" ]; [userDefaults setObject:myString forKey:@"myString" ]; [userDefaults setObject:myDate forKey:@"myDate" ]; [userDefaults setObject:myArray forKey:@"myArray" ]; [userDefaults setObject:myDictionary forKey:@"myDictionary" ]; [userDefaults synchronize]; } -(void )readNSUserDefaults { NSUserDefaults *userDefaultes = [NSUserDefaults standardUserDefaults]; NSInteger myInteger = [userDefaultes integerForKey:@"myInteger" ]; NSLog (@"%@" ,[NSString stringWithFormat:@"%ld" ,myInteger]); float myFloat = [userDefaultes floatForKey:@"myFloat" ]; NSLog (@"%@" , [NSString stringWithFormat:@"%f" ,myFloat]); double myDouble = [userDefaultes doubleForKey:@"myDouble" ]; NSLog (@"%@" ,[NSString stringWithFormat:@"%f" ,myDouble]); NSString *myString = [userDefaultes stringForKey:@"myString" ]; NSLog (@"%@" , myString); NSDate *myDate = [userDefaultes valueForKey:@"myDate" ]; NSDateFormatter *df = [[NSDateFormatter alloc] init]; [df setDateFormat:@"yyyy-MM-dd HH:mm:ss" ]; NSLog (@"%@" ,[NSString stringWithFormat:@"%@" ,[df stringFromDate:myDate]]); NSArray *myArray = [userDefaultes arrayForKey:@"myArray" ]; NSString *myArrayString = [[NSString alloc] init]; for (NSString *str in myArray) { NSLog (@"str= %@" ,str); myArrayString = [NSString stringWithFormat:@"%@ %@" , myArrayString, str]; [myArrayString stringByAppendingString:str]; NSLog (@"myArrayString=%@" ,myArrayString); } NSLog (@"%@" ,myArrayString); NSDictionary *myDictionary = [userDefaultes dictionaryForKey:@"myDictionary" ]; NSString *myDicString = [NSString stringWithFormat:@"name:%@, age:%ld" ,[myDictionary valueForKey:@"name" ], [[myDictionary valueForKey:@"age" ] integerValue]]; NSLog (@"%@" ,myDicString); } @end
NSUserDefaults保存的数据,默认会存储在 /Library/Prefereces/[projectName].plist中,开发时可以通过Finder前往如下位置查看
1 2 > /Users/ richfitbi/Library/ Developer/CoreSimulator/ Devices/9DBB8396-CFC2-4320-A3D8-6FA2826DDF29/ data/Containers/ Data/Application/ >
请根据自己的目录结构自行调整。
使用偏好设置对数据进行保存之后, 它保存到系统的时间是不确定的,会在将来某一时间点自动将数据保存到Preferences文件夹下面,如果需要即刻将数据存储,可以使用[defaults synchronize];
所有的信息都写在一个文件中,即/Library/Prefereces/[projectName].plist中。
五、对象归档(NSKeyedArchiver) 属性列表、偏好设置使用都有局限性,就是无法保存自定义的数据类。要解决这个问题,我们使用归档方法。归档方法实际就是 用 NSKeyedArchiver 对 自定义类进行编解码成 NSMutableData 后,再对NSMutableData实行序列化。具体的编解码是由NSCoder实现的。
1、名词解释 在开始讲NSKeyedArchiver之前,我们先解释一些名词:
Archives/UnArchives
Serializations/UnSerializations
Encoding/Decoding
Boxing/UnBoxing
(1)归档(Archives)和序列化(Serializations) 归档,在其他语言中又叫“序列化”,就是将对象保存到硬盘;解档,在其他语言又叫“反序列化”就是将硬盘文件还原成对象。其实归档就是数据存储的过程。
事实上在iOS中,归档(Archives)和序列化(Serializations)有着不同的内涵。我们看一下官方文档Archives and Serializations Programming Guide 中的Introduction,翻译过来就是:
归档(Archives)和序列化(Serializations)是将对象转换成字节流的两种方式。字节流便于保存和网络传输。当字节流解码后,可恢复对象的数据结构。归档详细记录了数据和值的关联关系,而序列化仅仅记录属性列表的简单层级关系。
说白了,归档(Archives)和序列化(Serializations)都是将对象转换成字节流以便保存或传输,区别在于:
归档(Archives):自定义对象转换为字节流
序列化(Serializations):某些特定类型(NSDictionary, NSArray, NSString, NSDate, NSNumber,NSData)的数据转换为字节流(通常将其保存为plist文件)
相应的相反行为
解档(UnArchives):字节流转换为自定义对象
反序列化(UnSerializations):字节流(通常将其保存为plist文件)转换为某些特定类型(NSDictionary, NSArray, NSString, NSDate, NSNumber,NSData)
(2)编码(Encoding)和解码(Decoding) 归档时,我们需要对数据进行编码(Encoding),比如
1 2 3 [archiver encodeCGSize: size1 forKey: @"size" ]; [archiver encodeObject: number1 forKey: @"number" ]; [archiver encodeObject: str1 forKey: @"string" ];
解档时,我们需要对数据进行解码(Decoding),比如
1 2 3 size2 = [unarchiver decodeCGSizeForKey: @"size" ]; number2 = [unarchiver decodeObjectForKey: @"number" ]; str2 = [unarchiver decodeObjectForKey: @"string" ];
(3)装箱(Boxing)和拆箱(UnBoxing) 我们知道,数组和字典中只能存储对象类型,其他基本类型和结构体是无法直接放到数组和字典中。把基本类型或结构体转化为Object就是一个装箱(Boxing)的过程,反过来将这个Object对象转换为基本数据类型或结构体的过程就是拆箱(UnBoxing)。
一般将基本数据类型装箱成NSNumber类型。是NSObject的子类,提供了大量方法来简化装箱、拆箱的操作。如+(NSNumber *)numberWithInt:(int)value;
方法是对int类型数据装箱,而-(int)intValue
方式是把NSNumber类型拆箱为int类型。
对于结构体,则装箱成NSValue类型。对于常用的结构体Foundation已经为我们提供好了具体的装箱方法:
1 2 3 4 5 +(NSValue *)valueWithPoint :(NSPoint)point ; +(NSValue *)valueWithSize :(NSSize)size ; +(NSValue *)valueWithRect :(NSRect)rect ;
对应的拆箱方法:
1 2 3 4 5 -(NSPoint ) pointValue -(NSSize ) sizeValue -(NSRect ) rectValue
而对于自定义结构体,我们需要使用NSValue如下方法进行装箱:
1 +(NSValue *)valueWithBytes:(const void *)value objCType:(const char *)type;
调用下面的方法进行拆箱:
1 -(void )getValue:(void *)value ;
完整例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #import <Foundation/Foundation.h> typedef struct { int year; int month; int day; } Date; void test1(){ CGPoint point1=CGPointMake (10 , 20 ); NSValue *value1=[NSValue valueWithPoint:point1]; NSArray *array1=[NSArray arrayWithObject:value1]; NSLog (@"%@" ,array1); ( "NSPoint: {10, 20}" ) */ NSValue *value2=[array1 lastObject]; CGPoint point2=[value2 pointValue]; NSLog (@"x=%f,y=%f" ,point2.x ,point2.y ); } void test2(){ Date date={2015 ,9 ,21 }; char *type=@encode(Date); NSValue *value3=[NSValue value:&date withObjCType:type]; NSArray *array2=[NSArray arrayWithObject:value3]; NSLog (@"%@" ,array2); ( "<df070000 09000000 15000000>" ) */ Date date2; [value3 getValue:&date2]; NSLog (@"%i,%i,%i" ,date2.year ,date2.month ,date2.day ); } int main(int argc, const char * argv[]) { @autoreleasepool { test1(); test2(); } return 0 ; }
2、NSKeyedArchiver用法 要针对更多对象归档或者需要归档时能够加密的话就需要使用NSKeyedArchiver进行归档和解档,使用这种方式归档的范围更广而且归档内容是密文存储。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 #import "ViewController.h" @interface ViewController ()@property (nonatomic ,strong ) NSString *documentPath;@end @implementation ViewController - (NSString *)documentPath { if (!_documentPath) { _documentPath = [NSSearchPathForDirectoriesInDomains (NSDocumentDirectory , NSUserDomainMask , YES ) objectAtIndex:0 ]; } return _documentPath; } - (void )viewDidLoad { [super viewDidLoad]; [self test2]; } * 系统简单对象归档 */ - (void )test1 { NSString *str1 = @"Hello,world!" ; NSString *filePath1 = [self .documentPath stringByAppendingPathComponent:@"archiver1.arc" ]; [NSKeyedArchiver archiveRootObject:str1 toFile:filePath1]; NSString *str2= [NSKeyedUnarchiver unarchiveObjectWithFile:filePath1]; NSLog (@"str2=%@" ,str2); NSArray *array1 = @[@"A" ,@(123 ),@(123.5 )]; NSString *filePath2 = [self .documentPath stringByAppendingPathComponent:@"archiver2.arc" ]; [NSKeyedArchiver archiveRootObject:array1 toFile:filePath2]; NSArray *array2 = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath2]; [array2 enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { NSLog (@"array2[%lu]=%@" ,idx,obj); }]; } * 系统复杂对象归档(多对象归档) */ - (void )test2 { NSString *filePath3 = [self .documentPath stringByAppendingPathComponent:@"archiver3.arc" ]; int int1 = 64 ; CGSize size1 = {320.0 ,960.0 }; NSNumber *number1 = @3.14 ; NSString *str1 = @"Hello,world!" ; NSArray *array1 = @[@"A" ,@"B" ,@"C" ]; NSDictionary *dic1 = @{@"name" :@"LYL" ,@"age" :@30 }; NSMutableData *data1 = [[NSMutableData alloc]init]; NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data1]; [archiver encodeInt:int1 forKey:@"int" ]; [archiver encodeCGSize :size1 forKey:@"size" ]; [archiver encodeObject:number1 forKey:@"number" ]; [archiver encodeObject:str1 forKey:@"string" ]; [archiver encodeObject:array1 forKey:@"array" ]; [archiver encodeObject:dic1 forKey:@"dic" ]; [archiver finishEncoding]; [data1 writeToFile:filePath3 atomically:YES ]; int int2; CGSize size2; NSNumber *number2; NSString *str2; NSArray *array2; NSDictionary *dic2; NSData *data2 = [[NSData alloc] initWithContentsOfFile:filePath3]; NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData:data2]; int2 = [unarchiver decodeInt32ForKey:@"int" ]; size2 = [unarchiver decodeCGSizeForKey :@"size" ]; number2 = [unarchiver decodeObjectForKey:@"number" ]; str2 = [unarchiver decodeObjectForKey:@"string" ]; array2 = [unarchiver decodeObjectForKey:@"array" ]; dic2 = [unarchiver decodeObjectForKey:@"dic" ]; [unarchiver finishDecoding]; NSLog (@"int2=%i,size=%@,number2=%@,str2=%@,array2=%@,dic2=%@" ,int2,NSStringFromCGSize (size2),number2,str2,array2,dic2); } @end
接下来看一下自定义的对象如何归档,上面说了如果要对自定义对象进行归档那么这个对象必须实现NSCoding协议,在这个协议中有两个方法都必须实现:
1 2 3 -(void )encodeWithCoder:(NSCoder *)aCoder; -(id )initWithCoder:(NSCoder *)aDecoder;
这两个方法分别在归档和解档时调用。
Person.h1 2 3 4 5 6 7 8 9 10 #import <Foundation/Foundation.h> @interface Person : NSObject <NSCoding >@property (nonatomic ,copy ) NSString *name;@property (nonatomic ,assign ) int age;@property (nonatomic ,assign ) float height;@property (nonatomic ,assign ) NSDate *birthday;@end
Person.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #import "Person.h" @implementation Person #pragma mark 解码 -(id )initWithCoder:(NSCoder *)aDecoder{ NSLog (@"decode..." ); if (self = [super init]) { self .name = [aDecoder decodeObjectForKey:@"name" ]; self .age = [aDecoder decodeInt32ForKey:@"age" ]; self .height = [aDecoder decodeFloatForKey:@"heiht" ]; self .birthday = [aDecoder decodeObjectForKey:@"birthday" ]; } return self ; } #pragma mark 编码 -(void )encodeWithCoder:(NSCoder *)aCoder{ NSLog (@"encode..." ); [aCoder encodeObject:_name forKey:@"name" ]; [aCoder encodeInt32:_age forKey:@"age" ]; [aCoder encodeFloat:_height forKey:@"height" ]; [aCoder encodeObject:_birthday forKey:@"birthday" ]; } #pragma mark 重写描述 -(NSString *)description{ NSDateFormatter *formater1 = [[NSDateFormatter alloc]init]; formater1.dateFormat = @"yyyy-MM-dd" ; return [NSString stringWithFormat:@"name=%@,age=%i,height=%.2f,birthday=%@" ,_name,_age,_height,[formater1 stringFromDate:_birthday]]; } @end
ViewController.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 - (void)test3 { //归档 Person *person1 = ; person1.name = @"Kenshin" ; person1.age = 28; person1.height = 1.72; NSDateFormatter *formater1 = ; formater1.dateFormat = @"yyyy-MM-dd" ; person1.birthday = ; NSString *filePath4 = ; ; //解档 Person *person2= ; NSLog(@"%@" ,person2); }
注意:对自定义对象进行归档
必须遵循并实现NSCoding协议
保存文件的扩展名可以任意指定
继承时必须先调用父类的归档解档方法
六、SQLite3 之前的所有存储方法,都是覆盖存储。如果想要增加一条数据就必须把整个文件读出来,然后修改数据后再把整个内容覆盖写入文件。所以它们都不适合存储大量的内容。要存储大量数据,我们需要使用SQLite数据库。
Mac上自带安装了SQLite3 ,如果你之前接触过关系型数据库,可以通过命令行来对SQLite进行初步的认识:
1 2 3 4 5 6 7 8 Last login: Thu Oct 15 16 :52 :02 on ttys000 RichfitBIdeMacBook-Pro:~ richfitbi$ sqlite3 test.db SQLite version 3.8 .10.2 2015 -05 -20 18 :17 :19 Enter ".help" for usage hints. sqlite> create table if not exists names(id integer primary key asc, name text ); sqlite> insert into names(name ) values('LYL'); sqlite> select * from names; 1 |LYL
SQLite数据库的几个特点:
基于C语言开发的轻型数据库
在iOS中需要使用C语言语法进行数据库操作、访问(无法使用ObjC直接访问,因为libsqlite3框架基于C语言编写)
SQLite中采用的是动态数据类型,即使创建时定义了一种类型,在实际操作时也可以存储其他类型,但是推荐建库时使用合适的类型(特别是应用需要考虑跨平台的情况时)
建立连接后通常不需要关闭连接(尽管可以手动关闭)
SQLite数据类型:
integer : 整数
real : 实数(浮点数)
text : 文本字符串
blob : 二进制数据,比如文件,图片之类的
其中,主键必须设置成integer
iOS中操作SQLite数据库步骤 在iOS中操作SQLite数据库可以分为以下几步(注意先要添加库文件:libsqlite3.dylib,IOS9 之后变为libsqlite3.tbd, 并导入主头文件):
打开数据库,利用sqlite3_open()
打开数据库会指定一个数据库文件保存路径,如果文件存在则直接打开,否则创建并打开。打开数据库会得到一个sqlite3类型的对象,后面需要借助这个对象进行其他操作。
执行SQL语句,执行SQL语句又包括有返回值的语句和无返回值语句。
对于无返回值的语句(如增加、删除、修改等)直接通过sqlite3_exec()
函数执行;
对于有返回值的语句则: 首先通过sqlite3_prepare_v2()
进行sql语句评估(语法检测), 然后通过sqlite3_step()
依次取出查询结果的每一行数据,对于每行数据都可以通过对应的sqlite3_column_类型()
型方法获得对应列的数据,如此反复循环直到遍历完成。当然,最后需要sqlite3_finalize()
释放句柄。
在整个操作过程中无需管理数据库连接,对于嵌入式SQLite操作是持久连接(尽管可以通过sqlite3_close()
关闭),不需要开发人员自己释放连接。纵观整个操作过程,其实与其他平台的开发没有明显的区别,较为麻烦的就是数据读取,在iOS平台中使用C进行数据读取采用了游标的形式,每次只能读取一行数据,较为麻烦。
示例 下面通过一个简单的例子来体验一下如何在iOS中操作SQLite数据库:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 #import "ViewController.h" #import "sqlite3.h" #define kDatabaseName @"database.sqlite3" @interface ViewController ()@property (copy , nonatomic ) NSString *databaseFilePath;@end @implementation ViewController - (void )viewDidLoad { [super viewDidLoad]; [self testDatabase]; } - (void )testDatabase{ NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory , NSUserDomainMask , YES ); NSLog (@"paths: %@" ,paths); NSString *documentsDirectory = [paths objectAtIndex:0 ]; self .databaseFilePath = [documentsDirectory stringByAppendingPathComponent:kDatabaseName]; sqlite3 *database; NSInteger result = sqlite3_open([self .databaseFilePath UTF8String], &database); if (result == SQLITE_OK) { const char *createTableSql="CREATE TABLE IF NOT EXISTS t_person (id integer PRIMARY KEY AUTOINCREMENT,name text NOT NULL,age integer NOT NULL);" ; char *errmsg = NULL ; sqlite3_exec(database, createTableSql, NULL , NULL , &errmsg); if (errmsg) { NSLog (@"创表失败:%s---%s---%d" , errmsg,__FILE__,__LINE__); } else { NSLog (@"创表成功!" ); } char *errmsg2 = NULL ; for (int i=0 ; i<20 ; i++) { NSString *name = [NSString stringWithFormat:@"Person %d" ,i]; int age = arc4random_uniform(20 )+10 ; NSString *insertSql = [NSString stringWithFormat:@"INSERT INTO t_person (name,age) VALUES ('%@',%d);" ,name,age]; sqlite3_exec(database, insertSql.UTF8String , NULL , NULL , &errmsg2); if (errmsg2) { NSLog (@"插入数据失败--%s" ,errmsg); }else { NSLog (@"插入数据成功" ); } } const char *selectSql = "SELECT id,name,age FROM t_person WHERE age<20;" ; sqlite3_stmt *stmt = NULL ; if (sqlite3_prepare_v2(database, selectSql, -1 , &stmt, NULL )==SQLITE_OK) { NSLog (@"查询语句没有问题" ); while (sqlite3_step(stmt) == SQLITE_ROW) { int ID = sqlite3_column_int(stmt, 0 ); const unsigned char *name = sqlite3_column_text(stmt, 1 ); int age = sqlite3_column_int(stmt, 2 ); printf("%d %s %d\n" ,ID,name,age); } }else { NSLog (@"查询语句有问题" ); } sqlite3_finalize(stmt); char *insertSql2 = "insert into t_person(name, age) values(?, ?);" ; sqlite3_stmt *stmt2; if (sqlite3_prepare_v2(database, insertSql2, -1 , &stmt2, NULL ) == SQLITE_OK) { sqlite3_bind_text(stmt2, 1 , "母鸡" , -1 , NULL ); sqlite3_bind_int(stmt2, 2 , 27 ); } if (sqlite3_step(stmt2) != SQLITE_DONE) { NSLog (@"带占位符插入数据错误" ); } sqlite3_finalize(stmt2); } else { sqlite3_close(database); NSAssert (0 , @"打开数据库失败!" ); } } @end
重要函数说明 总结一下几个重要函数:
1 2 3 4 5 6 7 8 int sqlite3_open( const char *filename, sqlite3 **ppDb );
sqlite3_exec()
可以执行任何SQL语句,比如创表、更新、插入和删除操作。但是一般不用它执行查询语句,因为它不会返回查询到的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int sqlite3_exec( sqlite3*, const char *sql, int , void *, char **errmsg );
sqlite3_prepare_v2()
进行查询前的准备工作(验证SQL语句)。
1 2 3 4 5 6 7 8 9 10 11 12 13 int sqlite3_prepare_v2( sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail );
sqlite3_step()
遍历查询结果。参数为sqlite3_step(sqlite3_stmt*)
,stmt可以理解为查询出的一行数据。每调用一次sqlite3_step
函数,stmt就会指向下一行数据。
sqlite3_column_类型()
该方法用于获得对应列的数据。
1 2 3 4 int ID = sqlite3_column_int(stmt, 0 );const unsigned char *name = sqlite3_column_t ext(stmt, 1 );
数据类型分为以下几种:
1 2 3 4 5 6 7 8 9 double sqlite3_column_double (sqlite3_stmt*, int iCol) ; int sqlite3_column_int (sqlite3_stmt*, int iCol) ; sqlite3_int64 sqlite3_column_int64 (sqlite3_stmt*, int iCol) ; const void *sqlite3_column_blob(sqlite3_stmt*, int iCol); const unsigned char *sqlite3_column_t ext(sqlite3_stmt*, int iCol);
sqlite3_bind_text()
:大部分绑定函数都只需要前3个参数
第1个参数是sqlite3_stmt *类型
第2个参数指占位符的位置,第一个占位符的位置是1,不是0
第3个参数指占位符要绑定的值
第4个参数指在第3个参数中所传递数据的长度,对于C字符串,可以传递-1代替字符串的长度
第5个参数是一个可选的函数回调,一般用于在语句执行后完成内存清理工作
sqlite_finalize():销毁sqlite3_stmt *对象
PreparedStatement方式处理SQL请求 上面代码最后部分提到了“带占位符插入数据”,即PreparedStatement方式处理SQL请求,过程如下:
1、验证语句:sqlite3_prepare_v2
2、 绑定sqlite3_bind_XXX()
:大部分绑定函数都只需要前3个参数。
第1个参数是sqlite3_stmt *类型
第2个参数指占位符的位置,第一个占位符的位置是1,不是0
第3个参数指占位符要绑定的值
第4个参数指在第3个参数中所传递数据的长度,对于C字符串,可以传递-1代替字符串的长度
第5个参数是一个可选的函数回调,一般用于在语句执行后完成内存清理工作
3、执行过程:
1 int sqlite3_step (sqlite3_stmt*) ;
可能的返回值:
SQLITE_BUSY: 数据库被锁定,需要等待再次尝试直到成功。
SQLITE_DONE: 成功执行过程(需要再次执行一遍以恢复数据库状态)
SQLITE_ROW: 返回一行结果(使用sqlite3_column_xxx(sqlite3_stmt*,, int iCol)得到每一列的结果。
SQLITE_ERROR: 运行错误,过程无法再次调用(错误内容参考sqlite3_errmsg函数返回值)
SQLITE_MISUSE: 错误的使用了本函数(一般是过程没有正确的初始化)
4、结束的时候清理statement对象:
1 int sqlite3_finalize (sqlite3_stmt *pStmt) ;
应该在关闭数据库之前清理过程中占用的资源。
5、重置过程的执行
1 int sqlite3_reset (sqlite3_stmt *pStmt) ;
过程将回到没有执行之前的状态,绑定的参数不会变化。
其他工具函数
1 int sqlite3_column_count (sqlite3_stmt *pStmt) ;
如果过程没有返回值,如update,将返回0
1 int sqlite3_data_count (sqlite3_stmt *pStmt) ;
如果sqlite3_step返回SQLITE_ROW,可以得到列数,否则为零。
1 int sqlite3_column_type (sqlite3_stmt*, int iCol) ;
返回值:SQLITE_INTEGER,SQLITE_FLOAT,SQLITE_TEXT,SQLITE_BLOB,SQLITE_NULL
实际开发中,会对这些操作进行封装,这里就不再赘述了,大家可以参考:iOS开发系列—数据存取 。当然在实际开发中,我们用的更多的还是FMDB,我会在以后的博客中进行总结。
七、Core Data Core Data是IOS中的ORM(对象关系映射),类似Java中的Hibernate。
对于从未接触过Core Data的朋友,强烈推荐下斯坦福大学公开课 中关于Core Data的讲解。由于Core Data内容比较多,我会在后续博文中再介绍。
参考:
iOS中常用的四种数据持久化方法简介
IOS持久化数据——(保存数据的一系列方法 )
IOS 四种保存数据的方式
iOS开发-文件管理
iOS开发之查找目录
NSBundle介绍
iOS开发备忘录:属性列表文件数据持久化
iOS开发UI篇—ios应用数据存储方式(XML属性列表-plist)
iOS开发UI篇—ios应用数据存储方式(归档)
iOS开发UI篇—ios应用数据存储方式(偏好设置)
iOS中几种数据持久化方案:我要永远地记住你!
iOS开发系列—Objective-C之Foundation框架
iOS持久化
iOS开发系列—数据存取
iOS开发数据库篇—SQLite常用的函数