Objectmapper: NSCoding

Created on 14 Jul 2015  路  10Comments  路  Source: tristanhimmelman/ObjectMapper

Is there any support for NSCoding so we can persist some objects on disk for later use?

Thanks!

Most helpful comment

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 ?? "")
    }
}

All 10 comments

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().parseJSONDictionary(jsonString)
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!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Dbigshooter picture Dbigshooter  路  4Comments

jperera84 picture jperera84  路  4Comments

maksTheAwesome picture maksTheAwesome  路  4Comments

liltimtim picture liltimtim  路  3Comments

quetool picture quetool  路  3Comments