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

时间:2017-12-05 17:12:29

标签: ios swift encoding nskeyedarchiver

我知道有几个与此类似的问题,这些问题往往都围绕着不正确地遵守协议的类,但这不应该是这里的直接问题。

以下是目前给我这个问题的代码的精简版本:

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函数

3 个答案:

答案 0 :(得分:4)

你有很多不匹配的尝试和各种编码/解码机制。

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

协议CodableEncoderDecoder是Swift 4的新功能。此类数据类型应与Swift编码器和解码器一起使用,例如JSONEncoder和{{ 1}}或JSONDecoderPropertyListEncoder

我建议您删除对PropertyListDecoder的引用,并删除NSCoderNSKeyedArchiver的使用。由于您已实现NSKeyedUnarchiver协议,请使用适当的Swift编码器和解码器。在您的情况下,您希望使用CodablePropertyListEncoder

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

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

答案 1 :(得分:2)

这是从上面的rmaddy提供的答案中得出的工作代码。

一些亮点:

  1. 将MyClass转换为MyStruct
  2. 从我想保存的对象中删除了NSCoder继承
  3. 删除了对NSKeyedArchiver& NSKeyedUnarchiver
  4. 不再保存到UserDefaults
  5. 依靠JSONEncoder& JSONDecoder写出struct
  6. 现在以Data对象
  7. 写入文件系统

    这是更新后的结构&我想保存的枚举:

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

答案 2 :(得分:0)

来自@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)
        }
    }

}

快乐的编码/解码!