Swift Encode Tuple Using Nscoding

Swift encode tuple using NSCoding

Tuple cannot be encoded because it is not a class, but one approach is to encode each component of a tuple separately and then upon decoding you decode each component and then set the value of the tuple to a tuple constructed from the decoded content.

class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

let obj = SomeClass()
obj.foo = (6,5)

let data = NSKeyedArchiver.archivedDataWithRootObject(obj)
NSUserDefaults.standardUserDefaults().setObject(data, forKey: "books")

if let data = NSUserDefaults.standardUserDefaults().objectForKey("books") as? NSData {
let o = NSKeyedUnarchiver.unarchiveObjectWithData(data) as SomeClass
println(o.foo) // (Optional(6), Optional(5))

}
}
}

class SomeClass: NSObject, NSCoding {
var foo: (x: Int?, y: Int?)!

required convenience init(coder decoder: NSCoder) {
self.init()
let x = decoder.decodeObjectForKey("myTupleX") as Int?
let y = decoder.decodeObjectForKey("myTupleY") as Int?
foo = (x,y)
}

func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(foo.x, forKey: "myTupleX")
coder.encodeObject(foo.y, forKey: "myTupleY")
}
}

NSCoding swift dictionary with swift tuple as values

The problem you are running into is that tuples are structs and not class (object) types. You'll notice this issue when you try to do the following:

if let dictionary = aDecoder.decodeObjectForKey("truthDict") as? [String : RelationshipType] { ... }

This issues unfortunately comes up a bit when trying to deal with value types in Swift. To get around this limitation, you can box your value types using a generic Box class like so:

class Box<T>
{
var value : T?

init(_ value: T?)
{
self.value = value
}
}

With the Box class, you can use this to encode and decode your dictionary tuples:

class TruthDictionary : NSObject, NSCoding, SequenceType
{
typealias RelationshipType = (ip: String, groupId: String?)

private var dictionary = [String : RelationshipType]()

subscript(key: String) -> RelationshipType?
{
get { return self.dictionary[key] }
set { self.dictionary[key] = newValue }
}

// MARK: - Initializers

override init()
{
super.init()
}

// MARK: - NSCoding

required init(coder aDecoder: NSCoder)
{
// Unbox each tuple from the decoded dictionary
if let boxedDictionary = aDecoder.decodeObjectForKey("truthDict") as? [String : Box<RelationshipType>]
{
for (key, boxedTuple) in boxedDictionary
{
self.dictionary[key] = boxedTuple.value
}
}
}

func encodeWithCoder(aCoder: NSCoder)
{
var boxedDictionary = [String: Box<RelationshipType>]()

// Box each tuple to the dictionary to be encoded
for (key, tuple) in self.dictionary
{
boxedDictionary[key] = Box(tuple)
}

aCoder.encodeObject(boxedDictionary, forKey: "truthDict")
}

// MARK: - SequenceType

func generate() -> DictionaryGenerator<String, RelationshipType>
{
return dictionary.generate()
}
}

Approach to encoding/decoding nontrivial property of a class in Swift

In addition to the information from Zaph's answer and his comments, I'm submitting my own solution as a standalone answer.

Because configuration is an array of tuples, you won't be able to use encodeObject because tuples are not supported by NSKeyedArchiver and NSKeyedUnarchiver.

As per "The Swift Programming Language" footnote on tuples:

Tuples are useful for temporary groups of related values. They are not suited to the creation of complex data structures. If your data structure is likely to persist beyond a temporary scope, model it as a class or structure, rather than as a tuple. For more information, see Classes and Structures.

The tuple can be represented as a class with 2 properties: one for the first part and one for the second part. While the extra boilerplate to make it a class and conform to NSObject and NSCoding is a lot, it makes the object more flexible for future use, so it has its trade-offs.

I created a Gist to show it in action with a sample project. Simply replace a new Single View Application project's ViewController.swift with this file and run (class object defined at the top of the file).

The first time you run it there is no stored data on viewDidLoad, and sample data will be created and stored on viewDidAppear. Run it again and you'll see that on viewDidLoad the stored data is loaded properly. You can uncomment the lines that remove the data from NSUserDefaults and run again to reset the stored data.

How to encode a unichar array using NSCoding

A C array of fixed-size data is best encoded using encodeBytes:length:forKey:. See Encoding and Decoding C Data Types for full details.

Models with refference to another models - saving using NSCoding

NSCoder subclasses know how to avoid circular references, see https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Archiving/Articles/archives.html.

That said, you should probably look into introducing weak references into your data model to avoid retain cycles.

Update a String in an NSCoding/NSObject Array using Swift 2

Probably i don't understand your problem. As I see, you wold like to update property of class 'saved' in Swift array. I don't see any relation to NSObject or NSCoding there.

class C {
var txt: String
init(txt: String) {
self.txt = txt
}
}

var arr: [C] = []

arr.append(C(txt: "a"))
arr.append(C(txt: "b"))
dump(arr)
/*
▿ 2 elements
▿ [0]: C #0
- txt: a
▿ [1]: C #1
- txt: b
*/

Updating some property of instance of class 'stored' in the array is simple. You need to know position of the instance in the array (index) and new value of the property, that's all.

arr[0].txt = "A"
dump(arr)
/*
▿ 2 elements
▿ [0]: C #0
- txt: A
▿ [1]: C #1
- txt: b
*/


Related Topics



Leave a reply



Submit