如果要将一个对象持久化,需要把这个对象序列化。过去的做法是实现 NSCoding 协议,但实现 NSCoding 协议的代码写起来很繁琐,尤其是当属性非常多的时候。 Swift4 中引入了 Codable 协议,可以大大减轻了我们的工作量。我们只需要让需要序列化的对象符合 Codable 协议即可,不用再写任何其他的代码
先看简单示例:
struct Language: Codable {
var name: String
var version: Int
}
1,Encode 操作 我们可以直接把符合了 Codable 协议的对象 encode 成 JSON 或者 PropertyList。
let swift = Language(name: "Swift", version: 4)
//encoded对象
let encodedData = try JSONEncoder().encode(swift)
//从encoded对象获取String
let jsonString = String(data: encodedData, encoding: .utf8)
print(jsonString)
2,Decode 操作
let decodedData = try JSONDecoder().decode(Language.self, from: encodedData)
print(decodedData.name, decodedData.version)
在项目开发中,后台请求回来的JSON
一般用第三方库进行转换,比如Objective-C
中的MJExtension
,yyModel
等,Swift
中有HandyJSON
,SwiftyJSON
等,Swift4
有了Codable
,我们可以很方便的使用原生的api
解析JSON
并且支持JSON
数据中的key
和我们自定义的model
中key
的关联:有时候json数据中的key不一定和我的们模型中的字段名一样,这时候我们就要做关联
支持多层嵌套
class UserModel: Codable {
var name: String
var age: Int
var email: String
var qq: String? = nil // Optional
init(name: String, age: Int, email: String) {
self.name = name
self.age = age
self.email = email
}
enum CodingKeys : String, CodingKey {
// 有时候json数据中的key不一定和我的们模型中的字段名一样,这时候我们就要做关联
// 注意1:CodingKeys是固定的枚举的名称,不能自定义。
// 注意2:一旦定义了CodingKeys,JSONEncoder/JSONDecoder将根据CodingKeys去工作
// 没有在CodingKeys中的字段将被过滤掉了
case name = "userName"
case age
case email = "userEmail"
case qq
}
}
class Department: Codable {
var name: String
var id: Int
var member: [UserModel] = []
init(name: String, id: Int) {
self.name = name;
self.id = id;
}
}
将JSON字符串转成[模型]
@objc func JSONToModel() {
let jsonString = "{\"userName\":\"HarveyCC\",\"age\":20,\"userEmail\":\"yaozuopan@icloud.com\"}"
if let jsonData = jsonString.data(using: String.Encoding.utf8) {
if let user = try? JSONDecoder().decode(UserModel.self, from: jsonData) {
print("----------------Base--------------------")
print(user.name, user.age, user.email)
}
}
}
@objc func JSONToComplexModel(){
let jsonString = "{\"name\":\"软件部\",\"id\":888,\"member\":[{\"userName\":\"Yoyo\",\"age\":18,\"userEmail\":\"Yoyo@icloud.com\"},{\"userName\":\"Jackly\",\"age\":25,\"userEmail\":\"Jackly@icloud.com\",\"qq\":\"6554866\"}]}"
if let jsonData = jsonString.data(using: String.Encoding.utf8) {
if let group = try? JSONDecoder().decode(Department.self, from: jsonData) {
print("----------------Complex--------------------")
print(group.name, group.id, group.member.count)
print(group.member[0].name, group.member[0].age, group.member[0].email, group.member[0].qq ?? "nil")
print(group.member[1].name, group.member[1].age, group.member[1].email, group.member[1].qq ?? "nil")
}
}
}
将[模型]转成JSON字符串
@objc func modelToJSON() {
let user = UserModel(name: "Harvey", age: 18, email: "yaozuopan@icloud.com")
if let jsonData = try? JSONEncoder().encode(user) {
if let jsonString = String(data: jsonData, encoding: String.Encoding.utf8) {
print("----------------Base--------------------")
print(jsonString)
}
}
}
@objc func complexModelToJSON() {
let group = Department(name: "软件部", id: 889)
let user1 = UserModel(name: "Harvey", age: 18, email: "Harvey@icloud.com")
let user2 = UserModel(name: "Jojo", age: 25, email: "Jojo@icloud.com")
group.member = [user1, user2]
if let jsonData = try? JSONEncoder().encode(group) {
if let jsonString = String(data: jsonData, encoding: String.Encoding.utf8) {
print("----------------Complex--------------------")
print(jsonString)
}
}
}
数据持久化
Swift4 之前归档解档model写法
@objc class UserInfo: NSObject, NSCoding {
var name: String
var age: Int
var email: String
override init() {
super.init()
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
aCoder.encode(age, forKey: "age")
aCoder.encode(email, forKey: "email")
}
required init?(coder aDecoder: NSCoder) {
name = aDecoder.decodeObject(forKey: "name") as? String
age = aDecoder.decodeObject(forKey: "age") as? Int
email = aDecoder.decodeObject(forKey: "email") as? String
}
}
Swift4 之后写法
class UserModel: Codable {
var name: String
var age: Int
var email: String
}
Swift4 归档解档DXCacheManager
解析
public class DXCacheManager {
private var directoryUrl:URL?
private var fileManager : FileManager{
return FileManager.default
}
private var cacheQueue = DispatchQueue.init(label: "com.nikkscache.dev.cacheQueue")
public static var sharedInstance = DXCacheManager.init(cacheName: Bundle.main.infoDictionary?["TargetName"] as? String ?? "MyAppCache")
//MARK:- Initializers
/// Private class initializer
private init() {}
/// This initializer method will use ~/Library/Caches/com.nikkscache.dev/targetName to save data
///
/// - Parameter cacheName: Name of the cache (by default it is 'TargetName')
private init(cacheName: String) {
if let cacheDirectory = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.cachesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last {
let dir = cacheDirectory + "/com.nikkscache.dev/" + cacheName
directoryUrl = URL.init(fileURLWithPath: dir)
if fileManager.fileExists(atPath: dir) == false{
do{
try fileManager.createDirectory(atPath: dir, withIntermediateDirectories: true, attributes: nil)
}catch{}
}
}
}
//MARK:- Adding / Removing Cached Object
/// This method will write Object of specified type to a particular key in cache directory
///
/// - Parameters:
/// - object: Object to be cached
/// - key: Identifier in cache for object
public func setObject<T: Codable>(_ object: T,forKey key:String) {
cacheQueue.async { [weak self] in
//dispatch asynchronously on cacheQueue
guard let path = self?.pathForKey(key) else{
print("File at path for key : \(key) not found")
return
}
do{
let data = try PropertyListEncoder().encode(object)
let success = NSKeyedArchiver.archiveRootObject(data, toFile: path)
var fileUrl = URL.init(fileURLWithPath: path)
self?.excludeFromBackup(fileUrl: &fileUrl)
print(success ? "data saved to cache SUCCESSFULLY" : "data caching FAILED")
}catch{
print("data caching FAILED")
}
}
}
/// This method will remove Object corresponding to specified key
///
/// - Parameter key: Identifier in cache for object
func removeObjectForKey(_ key: String) {
//dispatch asynchronously on cacheQueue
cacheQueue.sync { [weak self] in
guard let path = self?.pathForKey(key) else{
print("File at path for key : \(key) not found")
return
}
do {
try fileManager.removeItem(atPath: path)
print("cached data for key \(key) removed SUCCESSFULLY")
} catch {
print("FAILED removing cachced data for key \(key)")
}
}
}
public func removeAllObjects(){
cacheQueue.async { [weak self] in
guard let `self` = self else{ return }
guard let directoryUrls = self.directoryUrl else{ return }
do {
try self.fileManager.contentsOfDirectory(at: directoryUrls, includingPropertiesForKeys: nil, options: FileManager.DirectoryEnumerationOptions.skipsHiddenFiles).forEach{
do{
try self.fileManager.removeItem(atPath: $0.path)
print("cached data item removed SUCCESSFULLY")
}catch{
print("FAILED removing all cached data")
}
}
}catch{
print("FAILED removing all cached data")
}
}
}
//MARK: - Fetching cached object
/// This method is used to retrieve value from cache for specified key
///
/// - Parameters:
/// - key: Identifier in cache for object
/// - completionHandler: For handling completion state of fetch operation
public func getObjectForKey<T: Codable>(_ key: String, completionHandler: @escaping (T?)->()) {
cacheQueue.async { [weak self] in
guard let path = self?.pathForKey(key) else{
print("File at path for key : \(key) not found")
return
}
guard let data = NSKeyedUnarchiver.unarchiveObject(withFile: path) as? Data else{
print("ERROR data retriving from cache")
DispatchQueue.main.async {
completionHandler(nil)
}
return
}
do {
let object = try PropertyListDecoder().decode(T.self, from: data)
print("data retriving SUCCESSFULLY from cache")
DispatchQueue.main.async {
completionHandler(object)
}
}catch{
print("ERROR data retriving from cache")
DispatchQueue.main.async {
completionHandler(nil)
}
}
}
}
//MARK: - Private Methods
private func pathForKey(_ key: String)->String?{
return directoryUrl?.appendingPathComponent(key).path
}
/// This method is used beacuse it is required as per App Store Review Guidelines/ iOS Data Storage Guidelines to exculude files from being backedup on iCloud
///
/// - Parameter fileUrl: filePath url for file to be excluded from backup
/// - Returns: <#return value description#>
@discardableResult
private func excludeFromBackup(fileUrl: inout URL) ->Bool {
if fileManager.fileExists(atPath: fileUrl.path) {
fileUrl.setTemporaryResourceValue(true, forKey: URLResourceKey.isExcludedFromBackupKey)
return true
}
return false
}
}
- 1.使用了单例
- 2.数据储存在
FileManager.SearchPathDirectory.cachesDirectory
文件中新建了文件 - 3.数据储存和获取的方法使用了泛型参数,并做了相关约束
- 4.使用异步方法进行了数据存储和获取
原作者使用异步方法获取存储的数据后,completionHandler
回调并没有切换到主线程,如果在completionHandler
中进行了UI
操作,可能会出现奇怪的问题因此,我认为有必有切回到主线程进行completionHandler
回调
使用示例
存储数据示例
@objc func storeSingleDataAction() {
let user = UserModel(name: "Harvey", age: 18, email: "yaozuopan@icloud.com")
DXCacheManager.sharedInstance.setObject(user, forKey: userModelKey)
}
@objc func storeComplexDataAction() {
let group = Department(name: "软件部", id: 889)
let user1 = UserModel(name: "Harvey", age: 18, email: "Harvey@icloud.com")
let user2 = UserModel(name: "Jojo", age: 25, email: "Jojo@icloud.com")
user2.qq = "863223764"
group.member = [user1, user2]
DXCacheManager.sharedInstance.setObject(group, forKey: departmentKey)
}
获取数据示例
@objc func fetchSingleDataAction() {
DXCacheManager.sharedInstance.getObjectForKey(userModelKey) { (result : UserModel?) in
guard let item = result else{
print("获取失败了")
return
}
print("name=\(item.name),age=\(item.age),email=\(item.email),qq=\(String(describing: item.qq))")
}
}
// 获取嵌套model
@objc func fetchComplexDataAction() {
DXCacheManager.sharedInstance.getObjectForKey(departmentKey) { (result : Department?) in
guard let model = result else{
print("获取失败了")
return
}
for item in model.member {
print("name=\(item.name),age=\(item.age),email=\(item.email),qq=\(item.qq ?? "无")")
}
}
}
移除数据示例
@objc func removeSingleDataAction() {
DXCacheManager.sharedInstance.removeObjectForKey(userModelKey)
}
@objc func removeAllDataAction() {
DXCacheManager.sharedInstance.removeAllObjects()
}