Class 符合 Codable 协议失败,encodeWithCoder:无法识别的选择器发送到实例
Class conforming to Codable protocol fails with encodeWithCoder: unrecognized selector sent to instance
我知道有几个与此类似的问题,它们往往都围绕着 class 没有正确遵守协议,但这不应该是这里的直接问题。
以下是目前给我这个问题的代码的浓缩版:
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)
}
}
我创建了以下 class 然后尝试调用 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)")
}
}
}
虽然我一调用 writeMyClass 函数,我就得到这个错误:
[DemoDecoder.MyClass encodeWithCoder:]: unrecognized selector sent to
instance #blahblah#
我也试过两件事:
- 将
func encode(with aCoder: NSCoder)
添加到 MyClass
- 删除了
MyClass
& CodingKeys
和 init/encode 函数的所有属性
你有很多不匹配的尝试和各种encoding/decoding机制。
NSKeyedArchiver
和 NSKeyedUnarchiver
要求所有涉及的类型都符合 NSCoding
协议。这是 Objective-C 框架中的旧机制。
协议 Codable
、Encoder
和 Decoder
是 Swift 4 的新协议。此类数据类型应与 Swift 编码器和解码器一起使用例如 JSONEncoder
和 JSONDecoder
或 PropertyListEncoder
和 PropertyListDecoder
.
我建议您删除对 NSCoder
的引用并删除对 NSKeyedArchiver
和 NSKeyedUnarchiver
的使用。由于您已经实施了 Codable
协议,因此请使用适当的 Swift 编码器和解码器。在您的情况下,您想使用 PropertyListEncoder
和 PropertyListDecoder
.
完成后,您可能应该将 MyClass
更改为 struct
而不是 class
。
您还应该避免使用 UserDefaults
来存储数据。而是将编码数据写入 plist 文件。
这是从上面 rmaddy 提供的答案派生的工作代码。
几个亮点:
- 将 MyClass 转换为 MyStruct
- 从我希望保存的对象中删除了 NSCoder 继承
- 删除了对
NSKeyedArchiver
和 NSKeyedUnarchiver
的调用
- 不再保存到
UserDefaults
- 靠
JSONEncoder
&JSONDecoder
写出struct
- 现在作为
Data
对象写入文件系统
这是我希望保存的更新后的结构和枚举:
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)
}
}
处理读写输出的更新控制器class。就我而言,写信给 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)
}
}
}
@CodeBender 的解决方案工作得很好,尽管 不需要使用 init(from decoder: Decoder)
和 encode(to encoder: Encoder)
方法进行手动编码/解码,这样做只是违背了 GREAT Codable 协议的目的,除非你需要做一些复杂的编码/解码。
下面是使用 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)
}
}
}
我知道有几个与此类似的问题,它们往往都围绕着 class 没有正确遵守协议,但这不应该是这里的直接问题。
以下是目前给我这个问题的代码的浓缩版:
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)
}
}
我创建了以下 class 然后尝试调用 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)")
}
}
}
虽然我一调用 writeMyClass 函数,我就得到这个错误:
[DemoDecoder.MyClass encodeWithCoder:]: unrecognized selector sent to instance #blahblah#
我也试过两件事:
- 将
func encode(with aCoder: NSCoder)
添加到MyClass
- 删除了
MyClass
&CodingKeys
和 init/encode 函数的所有属性
你有很多不匹配的尝试和各种encoding/decoding机制。
NSKeyedArchiver
和 NSKeyedUnarchiver
要求所有涉及的类型都符合 NSCoding
协议。这是 Objective-C 框架中的旧机制。
协议 Codable
、Encoder
和 Decoder
是 Swift 4 的新协议。此类数据类型应与 Swift 编码器和解码器一起使用例如 JSONEncoder
和 JSONDecoder
或 PropertyListEncoder
和 PropertyListDecoder
.
我建议您删除对 NSCoder
的引用并删除对 NSKeyedArchiver
和 NSKeyedUnarchiver
的使用。由于您已经实施了 Codable
协议,因此请使用适当的 Swift 编码器和解码器。在您的情况下,您想使用 PropertyListEncoder
和 PropertyListDecoder
.
完成后,您可能应该将 MyClass
更改为 struct
而不是 class
。
您还应该避免使用 UserDefaults
来存储数据。而是将编码数据写入 plist 文件。
这是从上面 rmaddy 提供的答案派生的工作代码。
几个亮点:
- 将 MyClass 转换为 MyStruct
- 从我希望保存的对象中删除了 NSCoder 继承
- 删除了对
NSKeyedArchiver
和NSKeyedUnarchiver
的调用
- 不再保存到
UserDefaults
- 靠
JSONEncoder
&JSONDecoder
写出struct - 现在作为
Data
对象写入文件系统
这是我希望保存的更新后的结构和枚举:
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)
}
}
处理读写输出的更新控制器class。就我而言,写信给 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)
}
}
}
@CodeBender 的解决方案工作得很好,尽管 不需要使用 init(from decoder: Decoder)
和 encode(to encoder: Encoder)
方法进行手动编码/解码,这样做只是违背了 GREAT Codable 协议的目的,除非你需要做一些复杂的编码/解码。
下面是使用 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)
}
}
}