Peter71 Peter71 - 6 months ago 20
Swift Question

Swift: pList with "complex" data

As I read and tried out :-) I can only save some simple data types on pList files. Nevertheless I like to use structs, classes etc to represent my data. This should be saved as easily as possible to a pList file and gets reloaded.

I see, that NSData is a valid type for pLists. And also, that this is a general data type. So it is a good idea to move/convert/force a struct or class variable into a NSData object to be saved and reloaded? How would that be done?

Till now I'm using something like this for saving:

let dict: NSMutableDictionary = ["XYZ": "XYZ"]

// saving values
dict.setObject(myBasicArray, forKey: "BasicArray")

dict.writeToFile(path, atomically: false)

Answer

There's several ways to do this, you can adhere to the NSCoding protocol or you can write methods to convert your class/struct to a Dictionary and serialize from there.

Here's a good intro to using the NSCoding protocol.

As for converting to and from a Dictionary the usual way is to provide a failable init method that takes a Dictionary<String, AnyObject> which validates and copies the key:value pairs to the member variables. You also provide a method that returns a Dictionary<String, AnyObject> with the same key:value pairs as the init takes. Then you can serialize by calling the create method and serializing the resulting Dictionary, you deserialize by reading into a Dictionary and passing that in to the init method.

Here's an example of the conversion:

struct Foo {
  let one: Int
  let two: String

  init(one:Int, two: String) {
    self.one = one
    self.two = two
  }

  init?(dict:[String: AnyObject]) {
    guard let
      one = dict["one"] as? Int,
      two = dict["two"] as? String else { return nil }

    self.one = one
    self.two = two
  }

  func toDictionary() -> [String: AnyObject] {
    var retval = [String: AnyObject]()
    if let 
      one = self.one as? AnyObject,
      two = self.two as? AnyObject {
      retval["one"] = one
      retval["two"] = two
    }
    return retval
  }
}

Here's how to test it:

// create struct
let writeStruct = Foo(one: 1, two: "one")
print(writeStruct, "\n")

// write to plist
let writeDict = writeStruct.toDictionary() as NSDictionary
let path = ("~/test.plist" as NSString).stringByExpandingTildeInPath
writeDict.writeToFile(path, atomically: true)

// print contents of file
print(try NSString(contentsOfFile: path, encoding: NSUTF8StringEncoding))

// read plist and recreate struct
if let
  readDict = NSDictionary(contentsOfFile: path) as? [String:AnyObject],
  readStruct = Foo(dict: readDict) {
  print(readStruct)
}

Results:

Foo(one: 1, two: "one") 

<?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">
<dict>
    <key>one</key>
    <integer>1</integer>
    <key>two</key>
    <string>one</string>
</dict>
</plist>

Foo(one: 1, two: "one")