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:
The
#keyPath
directive which uses the ObjC runtime, too, but checks the key path at compile timelet 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
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
How to Immediately See Swift Errors in Appcode
In What Situation Would One Use Expectationfornotification in Swift Testing
Conform to Protocol and Keep Property Private
Swfitui List Make Scrolling Disabled
Firebase References Undeclared
App Crashes from IPA File But Runs Fine from Xcode
How to Get the Kvc-String from Swift 4 Keypath
Pop View Controller Using Screen Edge Pan Gesture Recogniser Not Following the Thumb
How to Suppress a Specific Warning in Swift
More Concise Way to Nest Enums for Access by Switch Statements in Swift
Swift Combine Publishers VS Completion Handler and When to Cancel
How to Do a Long Press in Swift
Typecast Unsafemutablepointer<Void> to Unsafemutablepointer<#Struct Type#>
Swiftlint Overriding Project Settings Related to Spm
Does Untimeintervalnotificationtrigger Nexttriggerdate() Give the Wrong Date
Why Does Type(Of:) Return Metatype, Rather Than T.Type
Compiler Cannot Infer Return Type
iOS Swift Error: 'T' Is Not Convertible to 'Mirrordisposition'