I have custom class where I declare
var metros: [[String: AnyObject]] = []
I have to save this into user defaults so I init NSCoding
#objc required init(coder aDecoder: NSCoder) {
metros = (aDecoder.decodeObjectForKey("metros")! as? [[String: AnyObject]])!
}
#objc func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(metros, forKey: "metros")
}
The problem is while decoding it returns the same dictionary multiple times like [key1: value1, key2: value2] and [key2 : value2, key1 : value1] and so on. How should I avoid this?
To be more exact I have 31 duplicates, 55 duplicates of some of the values
UPDATE:
I solved this issue like
#objc required init(coder aDecoder: NSCoder) {
if let metroObject = (aDecoder.decodeObjectForKey("metros")! as? [[String: AnyObject]]) {
for object in metroObject {
var alreadyExists = false
for objectAppended in metros {
if String(objectAppended["name"]) == String(object["name"]) {
alreadyExists = true
}
}
if alreadyExists == false {
metros.append(object)
print(object["name"])
}
}
}
}
but really curious if there is another way
Related
NSCoding requires init(coder:), but there is also the optional version of this method init?(coder:).
What exactly should one do if this returns nil? Is this even an issue?
Say you are initializing a large hierarchy of objects with init(coder:), with each object's child objects themselves being initialized using init?(coder:). If somewhere along the way one of those objects is nil, wouldn't the app crash? The parent object is not expecting a nil child.
What does it even mean to "init nil"?
class Parent: NSCoding {
var children: [Child]
required init?(coder aDecoder: NSCoder) {
guard let children = aDecoder.decodeObject(forKey: "children") as? [Child] else { return nil }
self.children = children
}
}
class Child: NSCoding {
var name: String
required init?(coder aDecoder: NSCoder) {
guard let name = aDecoder.decodeObject(forKey: "name") as? String else { return nil }
self.name = name
}
}
One strategy would be to simply return a new instance rather than simply returning nil. The data would be lost, but it the app would run.
You'd better not return nil.
As my test in Xcode 8.3.2 (8E2002), return nil in init(coder:) cause NSKeyedUnarchiver.unarchiveObject crash or return unexpected result.
Prepare a class which encode wrong data type for "test2":
class MyClass: NSObject, NSCoding {
var x: String
init(_ x: String) {
self.x = x
}
required init?(coder aDecoder: NSCoder) {
guard let x = aDecoder.decodeObject(forKey: "x") as? String else {
return nil
}
self.x = x
}
func encode(with aCoder: NSCoder) {
if x == "test2" {
aCoder.encode(Int(4), forKey: "x")
} else {
aCoder.encode(x, forKey: "x")
}
}
}
TestCaseA: archive a dictionary which contains above MyClass, then unarchive.
Result: crash on NSKeyedUnarchiver.unarchiveObject.
let encodedData = NSKeyedArchiver.archivedData(withRootObject: [
"k1":MyClass("test1"),
"k2":MyClass("test2"),
"k3":"normal things"
])
UserDefaults.standard.set(encodedData, forKey: "xx")
if let data = UserDefaults.standard.data(forKey: "xx"),
let _data = NSKeyedUnarchiver.unarchiveObject(with: data) {
if let dict = _data as? [String:Any] {
debugPrint(dict.count)
}
}
TestCaseB: archive an array which contains above MyClass, then unarchive.
Result: return an empty array (but expected is an array with 1 element)
let encodedData = NSKeyedArchiver.archivedData(withRootObject: [
MyClass("test1"),
MyClass("test2")
])
UserDefaults.standard.set(encodedData, forKey: "xx")
if let data = UserDefaults.standard.data(forKey: "xx"),
let _data = NSKeyedUnarchiver.unarchiveObject(with: data) {
if let dict = _data as? [Any] {
debugPrint(dict.count)
}
}
The decodeDouble on NSCoder returns a non-optional value, but I would like to identify whether a value was nil before it was encoded.
This is my scenario:
var optionalDouble: Double? = nil
func encode(with aCoder: NSCoder) {
if let optionalDouble {
aCoder.encode(optionalDouble, forKey: "myOptionalDouble")
}
}
convenience required init?(coder aDecoder: NSCoder) {
optionalDouble = aDecoder.decodeDouble(forKey: "myOptionalDouble")
// here optionalDouble is never nil anymore
}
So decoding double returns 0 in case the value was never set, so it seems like I can't identify whether a value was actually 0 or nil before encoding
Is there a way for me to check if a double was nil before it was encoded?
The solution is to use NSNumber instead of Double when you encode, then use decodeObject to get back (if it exists) the double value. For example
class A: NSCoding {
var optionalDouble: Double? = nil
#objc func encodeWithCoder(aCoder: NSCoder) {
if let optionalDouble = optionalDouble {
aCoder.encodeObject(NSNumber(double: optionalDouble), forKey: "myOptionalDouble")
}
}
#objc required init?(coder aDecoder: NSCoder) {
if let decodedDoubleNumber = aDecoder.decodeObjectForKey("myOptionalDouble") as? NSNumber {
self.optionalDouble = decodedDoubleNumber.doubleValue
} else {
self.optionalDouble = nil
}
}
}
With suggestion from #Hamish, here is the version for Swift 3. Be aware that we need to inherit the class to NSObject in order to make NSEncoding work (Got Unrecognized selector -replacementObjectForKeyedArchiver: crash when implementing NSCoding in Swift)
class A: NSObject, NSCoding {
var optionalDouble: Double? = nil
func encode(with aCoder: NSCoder) {
if let optionalDouble = optionalDouble {
aCoder.encode(optionalDouble, forKey: "myOptionalDouble")
}
}
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
optionalDouble = aDecoder.decodeObject(forKey: "myOptionalDouble") as? Double
}
}
I have a class Person, which is a model
import Foundation
public class Person : NSObject , NSCoding{
public var name:String="";
public var gender:Int = 0;
public var status:Int = 0
override init (){
super.init()
}
public init (name:String, gender:Int){
self.name=name;
self.gender=gender;
}
required public init(coder aDecoder: NSCoder) { }
public func encodeWithCoder(_aCoder: NSCoder) { }
}
Now when I try to retreive [Person] array I use method getPersons(arr : String!) . If there is no data in NSUserDefaults , I create it, put 1 element into it and store using setPersons()method.
The problem is that on retrieving I get an array with correct count (1), but the object is not filled, having "" in String and 0 in Int variables.
private func archivePeople (people : [Person]) -> NSData{
return NSKeyedArchiver.archivedDataWithRootObject(people as NSArray)
}
public func getPersons(arr : String!) -> [Person]{
var array : [Person] = []
if let unarchivedObject = NSUserDefaults.standardUserDefaults().objectForKey(arr) as? NSData {
array = (NSKeyedUnarchiver.unarchiveObjectWithData(unarchivedObject) as? [Person])!
}
if array.count == 0 {
array.append(Person(name: A.DEFAULT_THEIR_NAMES,gender: 0))
setPersons(arr, people: array)
}
return array
}
public func setPersons(key: String, people : [Person]){
let archivedObject = archivePeople(people)
userDefaults.setObject(archivedObject, forKey: key)
userDefaults.synchronize()
}
What is wrong? Do I set or retrieve this in a wrong way?
You need to override properly the NSCoding functions.
It should look like something the code below:
required public convenience init(coder aDecoder: NSCoder) {
let name: String = aDecoder.decodeObject(forKey: "name") as! String
let gender = aDecoder.decodeInt64(forKey: "gender")
let status = aDecoder.decodeInt64(forKey: "status")
self.init(name: name, gender: Int(gender))
self.status = Int(status)
}
public func encodeWithCoder(_aCoder: NSCoder) {
_aCoder.encode(self.name, forKey: "name")
_aCoder.encode(Int64(self.gender), forKey: "gender")
_aCoder.encode(Int64(self.status), forKey: "status")
}
Current Format
Optional(posterPrint.Frame(name: "RIBBA", productNumber: "303.016.24", productSize: (63.0, 93.0), pictureSize: (61.0, 91.0), pictureWithMatSize: (50.0, 70.0), frameColor: [UIDeviceRGBColorSpace 0.92549 0.92549 0.92549 1], matColor: [UIDeviceRGBColorSpace 0.92549 0.92549 0.92549 1]))
I want to add this one to array and convert any object. I tried a lot in this to stored in NSkeyedArchiver. I read somewhere tuples are not possible to store in NSKeyedArchiever. Anyone help in this
Thanks in advance .
To use NSKeyedArchiver, you make your object conform to NSCoding. And if you have tuples, you can encode/decode the elements separately:
class MyObject: NSObject, NSCoding {
var id: Int
var size: (Float, Float)
init(id: Int, size: (Float, Float)) {
self.id = id
self.size = size
super.init()
}
required init?(coder aDecoder: NSCoder) {
guard let id = aDecoder.decodeObjectForKey("id") as? Int else { return nil }
guard let size0 = aDecoder.decodeObjectForKey("size.0") as? Float else { return nil }
guard let size1 = aDecoder.decodeObjectForKey("size.1") as? Float else { return nil }
self.id = id
self.size = (size0, size1)
super.init()
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(id, forKey: "id")
aCoder.encodeObject(size.0, forKey: "size.0")
aCoder.encodeObject(size.1, forKey: "size.1")
}
}
You mention arrays, so I'm not sure if you're talking about an array of your custom objects, but now that your object conforms to NSCoding, you can now archive and unarchive arrays of these objects with no further effort.
Likewise, if you have some other object that has a MyObject property, you can apply NSCoding to that, and it can take advantage of the fact that MyObject already conforms to NSCoding:
class ParentObject: NSObject, NSCoding {
var name: String
var value: Int
var child: MyObject
init(name: String, value: Int, child: MyObject) {
self.name = name
self.value = value
self.child = child
super.init()
}
required init?(coder aDecoder: NSCoder) {
name = aDecoder.decodeObjectForKey("name") as! String
value = aDecoder.decodeObjectForKey("value") as! Int
child = aDecoder.decodeObjectForKey("child") as! MyObject
super.init()
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(name, forKey: "name")
aCoder.encodeObject(value, forKey: "value")
aCoder.encodeObject(child, forKey: "child")
}
}
I try to persiste my object with NSCoding but i always get BAD_ACCESS ERROR To avoid multi multiple like variable, class, i put all common variable in RObject. I think i do something wrong the the init but i don't know what.
the error was thow in this function
func parseInfo(allInfos : String) -> Void {
if let all : JSON = JSON.parse(allInfos) as JSON? {
if let info = all.asArray
{
for description in info
{
var track : RInfo = SCTracks(js: description)
self.arrayTracks.addObject(track)
} // Therad 1: EXC_BAD_ACCESS(code=2, address=0x27...)
}
}
}
The Log doesn't show any thing
My Common Class
class RObject : NSObject, NSCoding {
var id : Int? = 0
var kind : String?
override init() { super.init() }
init(js :JSON) {
self.kind = js["kind"].asString
self.id = js["id"].asInt
super.init()
}
required
init(coder aDecoder: NSCoder) {
self.id = aDecoder.decodeIntegerForKey("id") as Int
self.kind = aDecoder.decodeObjectForKey("kind") as? String
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeInteger(self.id!, forKey: "id")
aCoder.encodeObject(self.kind, forKey: "kind")
}
}
My class Rinfo who inherits from RObject
class RInfo : RObject {
var title :String?
var uri :String?
var license :String?
var release :String?
var user :RUser!
override init() { super.init() }
required init(coder: NSCoder) {
self.title = coder.decodeObjectForKey("title") as? String
self.user = coder.decodeObjectForKey("user") as RUser
self.license = coder.decodeObjectForKey("license") as? String
self.uri = coder.decodeObjectForKey("uri") as? String
self.release = coder.decodeObjectForKey("release") as? String
super.init(coder: coder)
}
init(js :JSON) {
self.user = js(js: js["user"])
self.title = js["title"].asString
self.license = js["license"].asString
self.uri = js["uri"].asString
self.release = js["release"].asString
super.init(js: js)
}
override func encodeWithCoder(encoder: NSCoder) {
encoder.encodeObject(self.title, forKey: "title")
encoder.encodeObject(self.user, forKey: "user")
encoder.encodeObject(self.uri, forKey: "uri")
encoder.encodeObject(self.license, forKey: "license")
}
}
Thanks for any help !
I Solve my problem by remove variable release in RInfo. that strange