簡體   English   中英

符合 Codable 協議的類因 encodeWithCoder 失敗:無法識別的選擇器發送到實例

[英]Class conforming to Codable protocol fails with encodeWithCoder: unrecognized selector sent to instance

我知道有幾個與此類似的問題,往往都圍繞着不正確遵守協議的類,但這不應該是這里的直接問題。

以下是目前給我這個問題的代碼的精簡版本:

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)
    }
}

我創建了以下類,然后嘗試調用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:]:無法識別的選擇器發送到實例#blahblah#

我也嘗試過兩件事:

  • func encode(with aCoder: NSCoder)MyClass
  • MyClass & CodingKeys和 init/encode 函數中刪除了所有屬性

您有很多不匹配的嘗試和各種編碼/解碼機制。

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. 不再保存到UserDefaults
  5. 依靠JSONEncoder & JSONDecoder寫出 struct
  6. 現在作為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)
    }
}

處理讀取和寫入輸出的更新的控制器類。 就我而言,寫出 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) fromdecoder 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)
        }
    }

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM