[英]Class conforming to Codable protocol fails with encodeWithCoder: unrecognized selector sent to instance
I know that there are several questions similar to this, that tend to all revolve around the class not conforming to the protocol properly, but that should not be the immediate issue here.我知道有几个与此类似的问题,往往都围绕着不正确遵守协议的类,但这不应该是这里的直接问题。
The following is a condensed version of the code that is currently giving me this problem:以下是目前给我这个问题的代码的精简版本:
enum Binary: Int {
case a = 0
case b = 1
case c = 9
}
final class MyClass: NSCoder {
var string: String?
var date: Date?
var binary: Binary = .c
override init() { }
enum CodingKeys: CodingKey {
case string, date, binary
}
}
extension MyClass: Codable {
convenience init(from decoder: Decoder) throws {
self.init()
let values = try decoder.container(keyedBy: CodingKeys.self)
string = try values.decode(String.self, forKey: .string)
date = try values.decode(Date.self, forKey: .date)
binary = try Binary(rawValue: values.decode(Int.self, forKey: .binary)) ?? .c
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(string, forKey: .string)
try container.encode(date, forKey: .date)
try container.encode(binary.rawValue, forKey: .binary)
}
}
I have created the following class which then attempts to call MyClass
with the purpose of writing & reading it to UserDefaults
:我创建了以下类,然后尝试调用
MyClass
以将其写入和读取到UserDefaults
:
class MyClassController {
private let myClass: MyClass
init() {
self.myClass = MyClass()
self.myClass.string = "string"
self.myClass.date = Date()
self.myClass.binary = .a
}
func writeMyClass() {
let encodedData = NSKeyedArchiver.archivedData(withRootObject: myClass)
UserDefaults.standard.set(encodedData, forKey: String(describing: MyClass.self))
}
func readMyClass() {
if let decoded = UserDefaults.standard.object(forKey: String(describing: MyClass.self)) as? Data,
let myClass = NSKeyedUnarchiver.unarchiveObject(with: decoded as Data) as? MyClass {
print("string: \(myClass.string ?? "nil") date: \(myClass.date ?? Date()) binary: \(myClass.binary)")
}
}
}
As soon as I call the writeMyClass function though, I get this error:但是,只要我调用 writeMyClass 函数,就会收到此错误:
[DemoDecoder.MyClass encodeWithCoder:]: unrecognized selector sent to instance #blahblah#
[DemoDecoder.MyClass encodeWithCoder:]:无法识别的选择器发送到实例#blahblah#
Two things I have also tried:我也尝试过两件事:
func encode(with aCoder: NSCoder)
to MyClass
func encode(with aCoder: NSCoder)
到MyClass
MyClass
& CodingKeys
and the init/encode functionsMyClass
& CodingKeys
和 init/encode 函数中删除了所有属性You have a lot of mismatched attempts and various encoding/decoding mechanisms.您有很多不匹配的尝试和各种编码/解码机制。
NSKeyedArchiver
and NSKeyedUnarchiver
require that all involved types conform to the NSCoding
protocol. NSKeyedArchiver
和NSKeyedUnarchiver
要求所有涉及的类型都符合NSCoding
协议。 This is the older mechanism from the Objective-C frameworks.这是来自 Objective-C 框架的旧机制。
The protocols Codable
, Encoder
, and Decoder
are new to Swift 4. Such data types should be used with Swift encoder and decoders such as JSONEncoder
and JSONDecoder
or PropertyListEncoder
and PropertyListDecoder
. Codable
、 Encoder
和Decoder
协议是 Swift 4 的新增内容。此类数据类型应与 Swift 编码器和解码器一起使用,例如JSONEncoder
和JSONDecoder
或PropertyListEncoder
和PropertyListDecoder
。
I suggest you remove the reference to NSCoder
and remove the uses of NSKeyedArchiver
and NSKeyedUnarchiver
.我建议您删除对
NSCoder
的引用并删除NSKeyedArchiver
和NSKeyedUnarchiver
的使用。 Since you have implemented the Codable
protocol, use an appropriate Swift encoder and decoder.由于您已经实现了
Codable
协议,因此请使用合适的 Swift 编码器和解码器。 In your case you want to use PropertyListEncoder
and PropertyListDecoder
.在您的情况下,您想使用
PropertyListEncoder
和PropertyListDecoder
。
Once that is done you should probably change MyClass
to be a struct
instead of a class
.完成后,您可能应该将
MyClass
更改为struct
而不是class
。
You should also avoid use UserDefaults
to store data.您还应该避免使用
UserDefaults
来存储数据。 Write the encoded data to a plist file instead.而是将编码数据写入 plist 文件。
This is the working code derived from the answer provided by rmaddy above.这是从上面 rmaddy 提供的答案派生的工作代码。
A few highlights:几个亮点:
NSKeyedArchiver
& NSKeyedUnarchiver
NSKeyedArchiver
和NSKeyedUnarchiver
调用UserDefaults
UserDefaults
JSONEncoder
& JSONDecoder
to write out structJSONEncoder
& JSONDecoder
写出 structData
objectData
对象写入文件系统This is the updated struct & enum that I wish to save:这是我希望保存的更新后的结构和枚举:
enum Binary: Int {
case a = 0
case b = 1
case c = 9
}
struct MyStruct {
var string: String?
var date: Date?
var binary: Binary = .c
init() { }
enum CodingKeys: CodingKey {
case string, date, binary
}
}
extension MyStruct: Codable {
init(from decoder: Decoder) throws {
self.init()
let values = try decoder.container(keyedBy: CodingKeys.self)
string = try values.decode(String.self, forKey: .string)
date = try values.decode(Date.self, forKey: .date)
binary = try Binary(rawValue: values.decode(Int.self, forKey: .binary)) ?? .c
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(string, forKey: .string)
try container.encode(date, forKey: .date)
try container.encode(binary.rawValue, forKey: .binary)
}
}
The updated controller class that handles reading & writing the output.处理读取和写入输出的更新的控制器类。 In my case, writing out to JSON was fine, so I went with that approach.
就我而言,写出 JSON 很好,所以我采用了这种方法。
class MyStructController {
private var myStruct: MyStruct
init() {
self.myStruct = MyStruct()
self.myStruct.string = "string"
self.myStruct.date = Date()
self.myStruct.binary = .a
}
func writeMyStruct() {
let encoder = JSONEncoder()
do {
let data = try encoder.encode(myStruct)
let documentDirectory = try FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor:nil,
create:false)
let url = documentDirectory.appendingPathComponent(String(describing: MyStruct.self))
try data.write(to: url)
} catch {
print(error.localizedDescription)
}
}
func readMyStruct() {
do {
let documentDirectory = try FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor:nil,
create:false)
let url = documentDirectory.appendingPathComponent(String(describing: MyStruct.self))
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
let myNewStruct = try decoder.decode(MyStruct.self, from: data)
print("string: \(myNewStruct.string ?? "nil") date: \(myNewStruct.date ?? Date()) binary: \(myNewStruct.binary)")
} catch {
print(error.localizedDescription)
}
}
}
Solution from @CodeBender works just fine, though there is no need to do manual encoding / decoding using init(from decoder: Decoder)
and encode(to encoder: Encoder)
methods, doing so just defeats the very purpose of the GREAT Codable protocol, unless you need to do some complex level of encoding / decoding. @CodeBender 的解决方案工作得很好,尽管不需要使用
init(from decoder: Decoder)
fromdecoder init(from decoder: Decoder)
和encode(to encoder: Encoder)
方法进行手动编码/解码,这样做只会违背 GREAT Codable协议的真正目的,除非您需要进行一些复杂级别的编码/解码。
Here is the code that works just well using the pure benefit of Codable protocol:这是使用 Codable 协议的纯粹好处的代码:
import UIKit
struct Movie: Codable {
enum MovieGenere: String, Codable {
case horror, drama, comedy, adventure, animation
}
var name : String
var moviesGenere : [MovieGenere]
var rating : Int
}
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
writeMyMovie(movie: Movie(name: "Titanic", moviesGenere: [Movie.MovieGenere.drama], rating: 1))
readMyMovie()
}
var documentDirectoryURL:URL? {
do {
let documentDirectory = try FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor:nil,
create:false)
return documentDirectory.appendingPathComponent(String(describing: Movie.self))
} catch {
return nil
}
}
func writeMyMovie(movie:Movie) {
do {
let data = try JSONEncoder().encode(movie)
try data.write(to: documentDirectoryURL!) // CAN USE GUARD STATEMENT HERE TO CHECK VALID URL INSTEAD OF FORCE UNWRAPPING, IN MY CASE AM 100% SURE, HENCE NOT GUARDING ;)
} catch {
print(error.localizedDescription)
}
}
func readMyMovie() {
do {
let data = try Data(contentsOf: documentDirectoryURL!)
let movie = try JSONDecoder().decode(Movie.self, from: data)
print("MOVIE DECODED: \(movie.name)")
} catch {
print(error.localizedDescription)
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.