Can Nscoding and Codable Co-Exist

Can NSCoding and Codable co-exist?

The actual error you are getting is:

-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance

And this is coming from the line:

aCoder.encode(more, forKey: "more")

The cause of the problem is that more (of type Unward) doesn't conform to NSCoding. But a Swift struct can't conform to NSCoding. You need to change Unward to be a class that extends NSObject in addition to conforming to NSCoding. None of this affects the ability to conform to Codable.

Here's your updated classes:

class Unward: NSObject, Codable, NSCoding {
var id: Int
var job: String

init(id: Int, job: String) {
self.id = id
self.job = job
}

func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: "id")
aCoder.encode(job, forKey: "job")
}

required init?(coder aDecoder: NSCoder) {
id = aDecoder.decodeInteger(forKey: "id")
job = aDecoder.decodeObject(forKey: "job") as? String ?? ""
}
}

class Akward: NSObject, Codable, NSCoding {
var name: String
var more: Unward

init(name: String, more: Unward) {
self.name = name
self.more = more
}

func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
aCoder.encode(more, forKey: "more")
}

required init?(coder aDecoder: NSCoder) {
name = aDecoder.decodeObject(forKey: "name") as? String ?? ""
more = aDecoder.decodeObject(forKey: "more") as? Unward ?? Unward(id: -1, job: "unk")
}
}

And your test values:

var upone = Unward(id: 12, job: "testing")
var adone = Akward(name: "Adrian", more: upone)

You can now archive and unarchive:

let encodeit = NSKeyedArchiver.archivedData(withRootObject: adone)
let redone = NSKeyedUnarchiver.unarchiveObject(with: encodeit) as! Akward

And you can encode and decode:

let enc = try! JSONEncoder().encode(adone)
let dec = try! JSONDecoder().decode(Akward.self, from: enc)

make CAShapeLayer and CGPath conform to Codable or to NSCoding

I couldn't find a way to make CAShapeLayer conforms to Codable, so I created class for my data object:

public class ImageNode: NSObject, NSCoding, NSSecureCoding {
public static var supportsSecureCoding = true
var name: NSString
var baseLayer: CAShapeLayer

init(name: NSString,
baseLayer: CAShapeLayer
) {
self.name = name
self.baseLayer = baseLayer
}

public func encode(with coder: NSCoder) {

coder.encode(name, forKey: "name")
coder.encode(baseLayer, forKey: "baseLayer")
}

public required convenience init?(coder: NSCoder) {
guard let name = coder.decodeObject(forKey: "name") as? NSString
else { return nil }
guard let baseLayer = coder.decodeObject(forKey: "baseLayer") as? CAShapeLayer
else { return nil }

print("decode node")
self.init(
name: name,
baseLayer: baseLayer
)
} }

which works nice with NSKeyedArchiver and NSKeyedUnarchiver for saving data to file and retrieving it. Maybe this solution will helps someone.

Make a node Codable in swift

SceneKit predates Swift's 4's Codable API, and was never augmented to supported it. Instead, it supports Objective-C's NSCoding API (NSSecureCoding, to be exact).

It's actually possible to leverage the SCNNode implementation of NSCodable to wrap it into a Codable archive. Essentially you just use an NSArchiver to wrap the object into a Data, and that Data can then be encoded with Codable. It's possible to neatly tuck this all away into a property wrapper, which I demonstrate here:

https://stackoverflow.com/a/71279637/3141234

How to learn about KVC with swift 4.2

In Swift 4 exposing code to the Objective-C runtime is no longer inferred for performance reasons.

To avoid the crash you have to add the @objc attribute explicitly.

@objc var name: String = ""

But from the strong type perspective of Swift there are two better ways to get values with KVC:

  1. The #keyPath directive which uses the ObjC runtime, too, but checks the key path at compile time

    let keyPath = #keyPath(Student.name)
    student1.setValue("Benny", forKeyPath: keyPath)

    In this case you get a very descriptive compiler warning

    Argument of '#keyPath' refers to property 'name' in 'Student' that depends on '@objc' inference deprecated in Swift 4

  2. The (recommended) native way: Swift 4+ provides its own KVC pattern where subclassing NSObject is not required.

    Key paths are indicated by a leading backslash followed by the type and the property (or properties):

    class Student {
    var name: String = ""
    var gradeLevel: Int = 0
    }

    let student1 = Student()
    student1[keyPath: \Student.name] = "Benny"


Related Topics



Leave a reply



Submit