在苹果中,每个应用都有自己对应的沙盒,每个应用程序之间不能相互访问非本程序的沙盒。
bundle就是通常所说的应用程序在手里里面的安装路径,是一个目录,这个目录就是程序的main bundle。这个目录里面包含nib文件、编译代码、以及其他资源的目录等。
let myBundle = Bundle.main
let myBundlePath = myBundle.bundlePath
if let imagePath = Bundle.main.path(forResource: "tongyuan", ofType: "png") {
let img = UIImage(contentsOfFile: imagePath)
print(img)
}
沙盒是一种安全机制,用于防止不同应用之间互相访问。iOS系统下每个应用都有自己对应的沙盒,每个沙盒之间都是相互独立的,互不能访问(没有越狱的情况下)。
沙盒的作用就是存储数据,每个沙盒就相当于每个每个应用的系统目录。
沙盒目录中包含:Documents、Library、tmp和一个xxx.app⽂件
用于存放程序中的文件数据,应用程序在运行时生成的一些需要长久保存的数据(比如:游戏进度存档、应用程序个人设置等等),通过 iTunes、iCloud 备份时, 会备份这个目录下的数据,此目录下保存相对重要的数据。
//1、获取程序的根目录
let homeDirectory = NSHomeDirectory()
//2、获取document目录
let documentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last
Caches:存放缓存文件,从网络上下载的文件或者数据(如:音乐缓存、图片缓存等),此目录下的文件不会在应用退出时自动删除 ,需要程序员手动清除改目录下的数据。iTunes、iCloud 备份时不会备份此目录下的数据。主要用于保存应用在运行时生成的需要长期使用的数据,一般用于存储体积较大,不需要备份的非重要数据。
//获取Library目录
let libraryPath = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true).last
//获取Caches目录
let cachesPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).last
Preferences:存放的是基于NSUserDefaults的设置数据,文件格式为 “plist”。设置应用的一些功能会在该目录中查找相应设置的信息,iTunes、iCloud备份时会备份此目录下的数据。该目录由系统自动管理,通常用来储存一些基本的应用配置信息。比如账号密码、自动登录等。
tmp:存放应用运行时产生的一些临时数据和文件,当应用程序退出、系统磁盘空间不足、手机重启时,都会自动清除该目录的数据。无需程序员手动清除该目录中的数据,iTunes、iCloud备份时不会备份此目录。
//获取Tmp目录
let tmpDirectory = NSTemporaryDirectory()
print(tmpDirectory)
xxx.app(应用程序包):包含程序中的nib⽂件、图片、音频等资源。
//获取xxx.app程序包
let myBundle = Bundle.main
Document : 存储用户数据,需要备份的信息
Library/Caches : 存储缓存文件,程序专用的支持文件
Library/Preferences : 存储应用程序的偏好设置⽂件
tmp : 存储临时文件,比如:下载的zip包,解压后的再删除
xxx.app : 应用程序包
iTunes在与iPhone同步时,会备份 `Documents` 和 `Preferences` 目录下的⽂件 。
系统提供的最简便的key-value本地存储方案,适合比较轻量的数据存储,比如一些业务flag。主要原因数据是明文存储在 plist 文件中,不安全,即使只是修改一个 key 都会 load 整个文件,其底层是用plist文件存储的,在数据量逐步变大后,可能会发生性能问题。
它是单例的,也是线程安全的,是以键值对 key-value 的形式保存在沙盒中。
存储路径为:路径为Library/Preferences/xxx.plist,xxx为项目的Bundle Identifier。
NSUserDefault是用户轻量级的数据持久化,主要用于保存用户程序的配置等信息,以便下次启动程序后能恢复上次的设置。
具体使用:
let key = "age"
//1. 获取一个UserDefaults引用:
let userDefault = UserDefaults.standard
//2. 保存数据
userDefault.set(18, forKey: key)
userDefault.synchronize()
// 3. 读取数据
let age = userDefault.value(forKey: key)
print(age)
// 4. 删除
userDefault.removeObject(forKey: key)
项目中有时会需要存储敏感信息(如密码、密钥等),苹果官方提供了一种存储机制–钥匙串(keychain)。
keychain是一种存储在硬盘上的加密的SQLite数据库(AES256加密)。这个可能是卸载App后,keychain信息还在的原因。
keychain适合存储 较小的数据量(不超过上千字节或上兆字节)
的内容。
如上图,每一个keyChain的组成如图,整体是一个字典结构.
(1)kSecClass key 定义属于哪一种类型的keyChain(通用密码、互联网密码、证书、密钥和身份)
(2)不同的类型包含不同的Attributes,这些attributes定义了这个item的具体信息
(3)每个item可以包含一个密码项来存储对应的密码
Keychain可以包含任意数量的keychain item(keychain item称为SecItem,但它是存储在CFDictionary中的).每一个keychain item包含数据和一组属性。SecItem有五类:通用密码、互联网密码、证书、密钥和身份。在大多数情况下,我们用到的都是通用密码。
在macOS中,当keychain被锁的时候加密的item没办法访问,如果你想要该问被锁的item,就会弹出一个对话框,需要你输入对应keychain的密码。当然,未有密码的keychain你可以随时访问。但在iOS中,你只可以访问你自已的keychain items
数据并不存放在App的Sanbox中,即使删除了App,资料依然保存在keychain中。如果重新安装了app,还可以从keychain获取数据。
keychain的数据可以通过group方式,让程序可以在App间共享。不过得要相同TeamID
keychain的数据是经过加密的
Apple针对keychain也提供了丰富的开发文档说明,包括有Keychain Services Programming Guide:文章中包含了使用mac和ios的keychain开发,首先介绍的是keychain的基本功能和概念,然后还有一个基本的例子介绍了基本的使用keychain API的方法
对于每一个应用来说,KeyChain都有两个访问区,私有区和公共区。私有区是一个sandbox,本程序存储的任何数据都对其他程序不可见,其他应用程序无法访问该区数据。如果要想将存储的内容放在公共区,实现多个应用程序间可以共同访问一些数据,则可以先声明公共区的名称,官方文档管这个名称叫“keychain access group”
这里我们先介绍 KeyChain 私有区的访问存储,即该私有区只能给自己程序使用。比如保存用户密码,设备唯一码(UDID)等等
大多数iOS应用需要用到Keychain, 都用来添加一个密码,修改一个已存在Keychain item或者取回密码。Keychain提供了以下的操作
1、SecItemAdd 添加一个item
2、SecItemUpdate 更新已存在的item
3、SecItemCopyMatching 搜索一个已存在的item
4、SecItemDelete 删除一个keychain item
当然对于只需要保存用户名和密码的应用来说,SSKeyChain可能更加适合,它对keychain做了相应的封装,接口相对来说更加简单 通过以下类方法来使用SSKeyChain(请查看SSKeyChain.h)。
+ (NSArray *)allAccounts;
+ (NSArray *)accountsForService:(NSString *)serviceName;
+ (NSString *)passwordForService:(NSString *)serviceNameaccount:(NSString *)account;
+ (BOOL)deletePasswordForService:(NSString *)serviceNameaccount:(NSString *)account;
+ (BOOL)setPassword:(NSString *)password forService:
(NSString*)serviceName account:(NSString *)account;
SSKeyChain的方法中涉及到的变量主要有三个,分别是password、service、account。password、account分别保存的是密码和用户名信息。service保存的是服务的类型,就是用户名和密码是为什么应用保存的一个标志。比如一个用户可以在不同的论坛中使用相同的用户名和密码,那么service保存的信息分别标识不同的论坛。由于包名通常具有一定的唯一性,通常在程序中可以用包的名称来作为service的标识。
在 iOS 3.0 之后,应用间共享 keychain 数据成为了一种可能。但这是被严格限制的,只有拥有相同 App ID 前缀的应用才有可能共享 keychai,并且各应用存储的 keychain item 都标记了相同的 kSecAccessGroup 字段值
一个开发者账号可以有不同的几个 Team ID。但 Apple 不会为不同的开发者生成一样的 Team ID。这样,不同的开发者账号发布的应用想共享 keychain 数据,在现在来看是无法实现的。而要做到 Keychain 数据共享,要求是同一个开发账号开发的,同时选择了相同的 Team ID
一个Swift的第三方框架:
https://github.com/kishikawakatsumi/KeychainAccess
直接将数据写在代码里面,不是一种合理的做法。如果数据经常改,就要经常翻开对应的代码进行修改,造成代码扩展性低。因此,可以考虑将经常变的数据放在文件中进行存储,程序启动后从文件中读取最新的数据。如果要变动数据,直接修改数据文件即可,不用修改代码。一般可以使用属性列表文件存储 NSArray 或者 NSDictionary 之类的数据,这种 “属性列表文件” 的扩展名是 plist,因此也称为 “plist 文件”。 plist 是以 xml 文件形式存储的。
如果对象是 NSString、NSArray、NSDictionary、NSData 和 NSNumber 类型,可以用这些类中实现的 writeToFile: atomically: 方法将数据写到文件中。
当根据字典创建属性列表时,字典中的键必须都是 NSString 对象。数组中的元素或字典中的值可以是 NSString、NSArray、NSDictionary、NSData、NSDate 和 NSNumber 对象。
iOS 实现的序列化方式的两种:NSKeyedArchiver,NSPropertyListSerialization。在这两种序列化方式中,NSData 都是序列化的目标。两种方式的不同点在于 NSPropertyListSerialization 是针对数组和字典类型的,而 NSKeyedArchiver 是针对对象的。
永久保存在磁盘中。具体方法为:
第一步:获得文件即将保存的路径:
let documentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last
// 将 Documents 添加到 sandbox 路径上
let documentPath = NSHomeDirectory().appending("/Documents")
第二步:生成该路径下的文件:
let documentPath = NSHomeDirectory().appending("/Documents")
let filename = documentPath.appending("/filename")
第三步:往文件中写入数据:
let data = Data()
if let url = URL(string: documentPath) {
do {
try data.write(to: url, options: .atomic)
}catch {}
}
第四步:从文件中读出数据
if let url = URL(string: documentPath) {
let data = try? Data(contentsOf: url)
}
let dictionaryPath:String = NSHomeDirectory().appending("/PropertyDictionaryList.plist")
let arrayPath:String = NSHomeDirectory().appending("/PropertyArrayList.plist")
//待写入的数组数据
let array = ["my","name","is","lichangan"]
//待写入的字典数据
let dictionary = ["name":"lichangan","age":18,"sex":"female"] as [String : Any]
//写入数组
let writeArrayIsSucceed = (array as NSArray).write(toFile: arrayPath, atomically: true)
print(writeArrayIsSucceed)
//写入字典
let writeDictionaryIsSucceed = (dictionary as NSDictionary).write(toFile: dictionaryPath, atomically: true)
print(writeDictionaryIsSucceed)
//读plist文件
let arrayFromPlist = NSArray(contentsOfFile: arrayPath)
let dictionaryFromPlist = NSDictionary(contentsOfFile: dictionaryPath)
print(arrayFromPlist)
print(dictionaryFromPlist)
let dictionaryPath:String = NSHomeDirectory().appending("/PropertyDictionaryList.plist")
let arrayPath:String = NSHomeDirectory().appending("/PropertyArrayList.plist")
//待写入的数组数据
let array = ["my","name","is","lichangan"]
//待写入的字典数据
let dictionary = ["name":"lichangan","age":18,"sex":"female"] as [String : Any]
//序列化,将数据转换成 XML 格式的文件
let arrayData = try PropertyListSerialization.data(fromPropertyList: array, format: .xml, options: .bitWidth)
let dictionaryData = try PropertyListSerialization.data(fromPropertyList: dictionary, format: .xml, options: .bitWidth)
do {
// 输出到 .txt 格式文件中
try (arrayData as NSData).write(toFile: arrayPath, options: .atomic)
}catch {
print(error.localizedDescription)
}
do{
// 输出到 .txt 格式文件中
try (dictionaryData as NSData).write(toFile: dictionaryPath, options: .atomic)
}catch{
print(error.localizedDescription)
}
//反序列化
let arrayFromFile:NSArray = NSArray(contentsOfFile: arrayPath) ?? []
let dictionaryFromFile:NSDictionary = NSDictionary(contentsOfFile: dictionaryPath) ?? [:]
print(arrayFromFile)
print(dictionaryFromFile)
if let path = Bundle.main.path(forResource: "area", ofType: "plist") {
let areas:NSArray = NSArray(contentsOfFile: path) ?? []
print(areas)
}
所谓文件归档,就是把需要存储的对象数据存储到沙盒的Documents目录下的文件中,即存储到了磁盘上, 实现数据的持久性存储和备份。解归档,就是从磁盘上读取该文件下的数据,用来完成用户的需求。 对象归档是将对象归档以文件的形式保存到磁盘中(也称为序列化,持久化),使用的时候读取该文件的保存路径 的 读取文件的内容(也称为接档,反 序列化),(对象归档的文件是保密的,在磁盘上无法查看文件中的内容, 而 属性列表是明文的,可以查看)。
通过文件归档产生的文件是不可见的,如果打开归档文件的话,内容是乱码的;它不同于属性列表和plist文件是 可见的, 正因为不可见的缘故,使得这种持久性的数据保存更有可靠性。
func arcFunc(filename:String) {
//1、获取归档文件的路径(归档文件名可以自己随意取名)
var documentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last
let arrayArcPath = documentPath?.appending("/\(filename)")
print(arrayArcPath)
//2、准备归档的对象数据
let array = [1,2,3,4,5]
guard let arrayArcPath = arrayArcPath else{return}
//3、对数据进行归档(根据flag标识进行判断,如果返回的是YES,归档成功;反之,归档失败)
let isSucceed = NSKeyedArchiver.archiveRootObject(array, toFile: arrayArcPath)
if isSucceed {
print("归档对象成功")
//4、对数据进行解归档
let array2 = NSKeyedUnarchiver.unarchiveObject(withFile: arrayArcPath) as? NSArray
print(array2)
}
}
由于自定义对象不具有归档的性质,所以要实现归档和解归档,首先必须实现协议。
//1.自定义一个归档对象类,实现协议
//新建立的类必须要继承自NSObject,否则会报错
//Unrecognized selector -[__lldb_expr_94.Person replacementObjectForKeyedArchiver:]
class Person : NSObject,NSCoding {
var name:String?
var age:Int?
override init() {
}
//解归档的协议方法
required init?(coder: NSCoder) {//将归档对象序列化
name = coder.decodeObject(forKey: "name") as? String
age = coder.decodeInteger(forKey: "age")
}
//归档的协议方法
func encode(with coder: NSCoder) {//将归档对象反序列化
coder.encode(name, forKey: "name")
//如果Int类型直接用Optional的会报错
//libc++abi: terminating with uncaught exception of type NSException
if let age = age {
coder.encode(age, forKey: "age")
}
}
}
func arcFunc(filename:String) {
//1、获取归档文件的路径(归档文件名可以自己随意取名)
var documentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last
let arrayArcPath = documentPath?.appending("/\(filename)")
//2、准备归档的对象数据
let person = Person()
person.name = "lichangan"
person.age = 18
guard let arrayArcPath = arrayArcPath else{return}
print(arrayArcPath)
//3、对数据进行归档(根据flag标识进行判断,如果返回的是YES,归档成功;反之,归档失败)
let isSucceed = NSKeyedArchiver.archiveRootObject(person, toFile: arrayArcPath)
if isSucceed {
print("归档对象成功")
//4、对数据进行解归档
let person = NSKeyedUnarchiver.unarchiveObject(withFile: arrayArcPath) as? Person
print(person?.name)
print(person?.age)
}
}
arcFunc(filename: "Lichangan")
前面的两种方式都是针对于单个对象进行归档和解归档,如果需要对多个对象进行归档,它们就无用武之地了,局限性很大,因此,这里介绍一个新的归档方式,多对象归档和解归档。
IOS沙盒基本机制(sandbox)
iOS 钥匙串的基本使用
iOS - Swift PList 数据存储
iOS:文件归档和解归档的详解和使用