Is there any support for NSCoding so we can persist some objects on disk for later use?
Thanks!
I dont know what are the plans about NSCoding in swift, but it's only going to work with NSObject subclasses. Which automatically excludes Swift struct, enum and other value types.
However, ObjectMapper lets you easily serialize your Mappable objects or values into a JSON representation. That might be a good start to persist them into NSData using NSJSONSerialization afterwards.
@damienrambout I was able to use NSCoding myself with ObjectMapper. No idea if this is legit, but it seems to work, maybe you can advice @damienrambout @tristanhimmelman
Here's my model:
class User: NSObject, NSCoding, Mappable {
// MARK: Properties
var id: String!
var email: String!
var superAdmin: Bool!
var locale: String!
var highestRole: String!
var timeFormat: Int!
var confirmedAt: NSDate?
var createdAt: NSDate!
var unconfirmedEmail: String?
var profile: Profile!
var members: [Member]?
var weekDayStart: Int!
// MARK: Initializers
override init() {}
required init?(_ map: Map) {
super.init()
mapping(map)
}
// MARK: NSCoding
required init(coder decoder: NSCoder) {
super.init()
id = decoder.decodeObjectForKey("id") as! String
email = decoder.decodeObjectForKey("email") as! String
superAdmin = decoder.decodeBoolForKey("superAdmin")
locale = decoder.decodeObjectForKey("locale") as! String
highestRole = decoder.decodeObjectForKey("highestRole") as! String
timeFormat = decoder.decodeIntegerForKey("timeFormat")
confirmedAt = decoder.decodeObjectForKey("confirmedAt") as? NSDate
createdAt = decoder.decodeObjectForKey("createdAt") as! NSDate
unconfirmedEmail = decoder.decodeObjectForKey("unconfirmedEmail") as? String
profile = decoder.decodeObjectForKey("profile") as? Profile
weekDayStart = decoder.decodeIntegerForKey("weekDayStart")
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(id, forKey: "id")
coder.encodeObject(email, forKey: "email")
coder.encodeBool(superAdmin, forKey: "superAdmin")
coder.encodeObject(locale, forKey: "locale")
coder.encodeObject(highestRole, forKey: "highestRole")
coder.encodeInteger(timeFormat, forKey: "timeFormat")
coder.encodeObject(confirmedAt, forKey: "confirmedAt")
coder.encodeObject(createdAt, forKey: "createdAt")
coder.encodeObject(unconfirmedEmail, forKey: "unconfirmedEmail")
coder.encodeObject(profile, forKey: "profile")
coder.encodeInteger(weekDayStart, forKey: "weekDayStart")
}
// MARK: Mappable
func mapping(map: Map) {
id <- map["id"]
email <- map["email"]
highestRole <- map["highest_role"]
superAdmin <- map["super_admin"]
locale <- map["locale"]
timeFormat <- map["time_format"]
confirmedAt <- (map["confirmed_at"], ISO8601DateTransform())
createdAt <- (map["created_at"], ISO8601DateTransform())
unconfirmedEmail <- map["unconfirmed_email"]
profile <- map["profile"]
members <- map["members"]
weekDayStart <- map["week_day_start"]
}
This seems like it should be fine to me.
Another option which would remove the boiler plate code needed for NSCoding would be to save JSON strings (generated by ObjectMapper) to disk.
let mapper = Mapper<User>()
let JSONString = mapper.toJSON(user)
// save JSONString to disk
You can then retrieve the JSON string and again use ObjectMapper to build your user object.
@tristanhimmelman what would you use to save it on disk?
Here are two functions I used in the past to save and read to disk using ObjectMapper (it may need a bit of tweaking):
func saveToFile<N: Mappable>(object: N){
println("PersistentStore: Saving \'\(self.fileName())\' (\(object)) to file...")
let dict = Mapper().toJSON(object)
var err: NSError?
if NSJSONSerialization.isValidJSONObject(dict) {
var jsonData: NSData? = NSJSONSerialization.dataWithJSONObject(dict, options: NSJSONWritingOptions.PrettyPrinted, error: &err)
if let error = err {
println(error)
}
if let json = jsonData {
var string = NSString(data: json, encoding: NSUTF8StringEncoding)
// Create directory if necessary
if NSFileManager.defaultManager().fileExistsAtPath(self.filePath().stringByDeletingLastPathComponent) == false {
NSFileManager.defaultManager().createDirectoryAtPath(self.filePath().stringByDeletingLastPathComponent, withIntermediateDirectories: false, attributes: nil, error: nil)
}
// write data
let path = self.filePath()
var error: NSError?
if let string = string {
string.writeToFile(path, atomically: true, encoding: NSUTF8StringEncoding, error: &error)
}
if let error = error {
println("PersistentStore: ERROR writing file: \(error)")
} else {
println("PersistentStore: Saved file successfully")
}
}
} else {
println("PersistentStore: ERROR: couldn't save to file...")
}
}
func loadFromFile<N: Mappable>() -> N?{
let path = self.filePath()
println("PersistentStore: Loading \(fileName()) from file... \(object)")
var error: NSError?
let jsonString = NSString(contentsOfFile: path, encoding: NSUTF8StringEncoding, error: &error)
if let jsonString = jsonString as? String {
return Mapper().map(jsonString)
}
return nil
}
thanks for this @tristanhimmelman. Do you think https://github.com/Haneke/HanekeSwift would work to cache JSON here? Might save some code since I already have it for image caching!
If this is for pure caching, this could be a good solution (even though Haneke was mainly created for image caching at first). If you want to persist your data, then don't use a cache-based solution.
Note: I have to warn you that I used Haneke (Objective-C version) in one of my apps and it was a source of crash. I heard things about the Swift version as well... But you should definitely try it yourself ;)
Hi @tristanhimmelman ... please consider to make Mapper().parseJSONDictionary and Map constructor PUBLIC to enable do this
// MARK: NSCoding
required convenience init?(coder decoder: NSCoder) {
if let jsonString:String = decoder.decodeObjectForKey("jsonString") as! String? {
let jsonDict = Mapper
let map = Map(mappingType: MappingType.FromJSON, JSONDictionary: jsonDict!)
self.init()
self.mapping(map)
} else {
self.init()
}
}
THX!
class Topic: Mappable, NSCoding {
// Mappable
required init?(map: Map) {
}
func mapping(map: Map) {
}
// NSCoding
public func encode(with aCoder: NSCoder) {
let jsonString = self.toJSONString() ?? ""
aCoder.encode(jsonString, forKey: "jsonString")
}
required public convenience init?(coder aDecoder: NSCoder) {
let jsonString = aDecoder.decodeObject(forKey: "jsonString") as? String
self.init(JSONString: jsonString ?? "")
}
}
Here how to use ObjectMapper with NSCoding.
We use it with PINCache for cache
Base class:
class CodableObject: Mappable, NSCoding {
// MARK: - Properties
// MARK: Class
// MARK: Public
// MARK: Private
private static let nsCoderKey = "coderKey"
// MARK: Lifecycle
override init() {}
// MARK: NSCoding
required init?(coder aDecoder: NSCoder) {
guard let json = aDecoder.decodeObject(forKey: CodableObject.nsCoderKey) as? [String : Any] else {
return nil
}
super.init()
let map = Map(mappingType: .fromJSON, JSON: json)
mapping(map: map)
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(toJSON(), forKey: CodableObject.nsCoderKey)
}
// MARK: Mappable
required init?(map: Map) {
}
public func mapping(map: Map) {
}
}
All children and nested classes:
class User: CodableObject {
// MARK: - Properties
var name: String?
var surname: String?
var nestedObjs: [NestedClass]?
// MARK: Lifecycle
override init() {
super.init()
}
// NSCoding
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// Mappable
required init?(map: Map) {
super.init(map: map)
}
override func mapping(map: Map) {
super.mapping(map: map)
name <- map["Name"]
surname <- map["Surname"]
nestedObjs <- map["Children"]
}
}
Simply save with:
PINCache.shared.setObjectAsync(user, forKey: "key", completion: { (cache, key, object) in
completion?()
})
Great!
Most helpful comment