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#

我也试过两件事:

你有很多不匹配的尝试和各种encoding/decoding机制。

NSKeyedArchiverNSKeyedUnarchiver 要求所有涉及的类型都符合 NSCoding 协议。这是 Objective-C 框架中的旧机制。

协议 CodableEncoderDecoder 是 Swift 4 的新协议。此类数据类型应与 Swift 编码器和解码器一起使用例如 JSONEncoderJSONDecoderPropertyListEncoderPropertyListDecoder.

我建议您删除对 NSCoder 的引用并删除对 NSKeyedArchiverNSKeyedUnarchiver 的使用。由于您已经实施了 Codable 协议,因此请使用适当的 Swift 编码器和解码器。在您的情况下,您想使用 PropertyListEncoderPropertyListDecoder.

完成后,您可能应该将 MyClass 更改为 struct 而不是 class

您还应该避免使用 UserDefaults 来存储数据。而是将编码数据写入 plist 文件。

这是从上面 rmaddy 提供的答案派生的工作代码。

几个亮点:

  1. 将 MyClass 转换为 MyStruct
  2. 从我希望保存的对象中删除了 NSCoder 继承
  3. 删除了对 NSKeyedArchiverNSKeyedUnarchiver
  4. 的调用
  5. 不再保存到 UserDefaults
  6. JSONEncoder&JSONDecoder写出struct
  7. 现在作为 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)
        }
    }

}