简体   繁体   English

符合 Codable 协议的类因 encodeWithCoder 失败:无法识别的选择器发送到实例

[英]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:我也尝试过两件事:

  • Adding func encode(with aCoder: NSCoder) to MyClassfunc encode(with aCoder: NSCoder)MyClass
  • Removed all properties from 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. NSKeyedArchiverNSKeyedUnarchiver要求所有涉及的类型都符合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 . CodableEncoderDecoder协议是 Swift 4 的新增内容。此类数据类型应与 Swift 编码器和解码器一起使用,例如JSONEncoderJSONDecoderPropertyListEncoderPropertyListDecoder

I suggest you remove the reference to NSCoder and remove the uses of NSKeyedArchiver and NSKeyedUnarchiver .我建议您删除对NSCoder的引用并删除NSKeyedArchiverNSKeyedUnarchiver的使用。 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 .在您的情况下,您想使用PropertyListEncoderPropertyListDecoder

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:几个亮点:

  1. Convert MyClass to MyStruct将 MyClass 转换为 MyStruct
  2. Removed NSCoder inheritance from the object I wished to save从我希望保存的对象中删除了 NSCoder 继承
  3. Removed calls to NSKeyedArchiver & NSKeyedUnarchiver删除了对NSKeyedArchiverNSKeyedUnarchiver调用
  4. No longer saving to UserDefaults不再保存到UserDefaults
  5. Relying on JSONEncoder & JSONDecoder to write out struct依靠JSONEncoder & JSONDecoder写出 struct
  6. Writing to file system now as a Data object现在作为Data对象写入文件系统

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM