【问题标题】:What is wrong with the implementation of NSCoding protocol in Swift在 Swift 中实现 NSCoding 协议有什么问题
【发布时间】:2014-06-25 19:21:57
【问题描述】:

我认为我会谨慎并通过转换一个类来在现有的 Obj-C 项目上试用 Swift。还有一个小而简单的。亲爱的。

将原始的 obj-c 音译成 Swift 应该很简单,看起来也是如此。不幸的是,虽然持久存储的编码器似乎可以工作,但它在 init 编码器的第一行出现 EXC_BREAKPOINT 错误而崩溃。

如果(并且大写是有意的)NSCoding/Swift 提供与 NSCoding/ObjC 相同的持久内容,那么我的所有 obj-c 版本应该能够读取 Swift 编码的内容,反之亦然。事实证明并非如此 - 我的完美运行的 obj-c 版本在尝试从 Swift 版本中读取持久存储时崩溃了。当然,如果 NSCoding 被正确实现,它应该在一个中生成一些在另一个中可读的东西?否则,应该有单独的 NSCodingSwift 和 NSCodingObjC 协议?

所以,总而言之,我可以在 obj-c 中读/写。我不能写/obj-c和读/swift,我可以写/swift读/obj-c,我不能用swift读/写。

这里有两个版本:

let keyBeaconItemNameKey = "name"
let keyBeaconItemUUIDKey = "uuid"
let keyBeaconItemMajorValueKey = "major"
let keyBeaconItemMinorValueKey = "minor"

import UIKit
import CoreLocation

class SMBeaconItem : NSObject, NSCoding
{
    var name : String!
    var uuid : NSUUID!
    var major : NSNumber!
    var minor : NSNumber!

    init(newName : String, newUUID : NSUUID, newMajor : NSNumber, newMinor : NSNumber )
    {
        name = newName
        uuid = newUUID
        major = newMajor
        minor = newMinor
    }

    init( coder decoder : NSCoder!)
    {
        name = decoder.decodeObjectForKey(keyBeaconItemNameKey) as String
        uuid = decoder.decodeObjectForKey(keyBeaconItemUUIDKey) as NSUUID
        major = decoder.decodeObjectForKey(keyBeaconItemMajorValueKey) as NSNumber
        minor = decoder.decodeObjectForKey(keyBeaconItemMinorValueKey) as NSNumber
    }

    func encodeWithCoder( encoder: NSCoder!)
    {
        encoder.encodeObject(name, forKey:keyBeaconItemNameKey)
        encoder.encodeObject(uuid, forKey:keyBeaconItemUUIDKey)
        encoder.encodeObject(major, forKey:keyBeaconItemMajorValueKey)
        encoder.encodeObject(minor, forKey:keyBeaconItemMinorValueKey)
       }
    }

还有工作原件:

@implementation SMBeaconItem

- (instancetype)initWithName:(NSString *)name uuid:(NSUUID *)uuid major:(CLBeaconMajorValue)major minor:(CLBeaconMinorValue)minor
{
    self = [super init];
    if (!self)
    {
        return nil;
    }

    _name = name;
    _uuid = uuid;
    _majorValue = major;
    _minorValue = minor;

    return self;
}

#pragma mark - Persistence

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if (!self)
    {
        return nil;
    }

    _name = [aDecoder decodeObjectForKey:keyBeaconItemNameKey];
    _uuid = [aDecoder decodeObjectForKey:keyBeaconItemUUIDKey];
    _majorValue = [[aDecoder decodeObjectForKey:keyBeaconItemMajorValueKey] unsignedIntegerValue];
    _minorValue = [[aDecoder decodeObjectForKey:keyBeaconItemMinorValueKey] unsignedIntegerValue];

    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject:self.name forKey:keyBeaconItemNameKey];
    [aCoder encodeObject:self.uuid forKey:keyBeaconItemUUIDKey];
    [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:self.majorValue] forKey:keyBeaconItemMajorValueKey];
    [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:self.minorValue] forKey:keyBeaconItemMinorValueKey];
}

@end

感谢您提供的任何帮助。

【问题讨论】:

  • 也许勇敢但匿名的人否决了这个投票会足够亲切地解释一下吗?我已经做了几个小时的研究,这是相关的。并非所有人都有时间快速重写我们所有的代码,但我们都在认真尝试与时俱进。如果你这么聪明,请告诉我我没有做什么。
  • 它适用于我,将 ObjC 编码为 Swift,将 Swift 编码为 Objc。我认为您发布的代码没有任何问题。该问题可能与您设置 NSKeyedArchiver 和 NSKeyedUnarchiver 的方式有关。如果在 ObjC 中失败,调试控制台上应该会有更多错误信息。
  • 谢谢,达伦。如果我注释掉读回 name 属性的尝试(并将值设置为文字),那么它就可以工作。 init 的其余部分工作正常。调试控制台上的唯一信息是错误 EXC_BREAKPOINT,代码 = 1,子代码 = 0x1000a26e0,我找不到解释。
  • 我猜这是因为你的 swift 类名被弄乱了,试着把 @objc(SMBeaconItem) 放在你的类声明之前。
  • 当你继承 NSObject 时,为什么不在 Init 方法中调用 Super - 你可能不应该继承 NSObject?还是打电话给super?

标签: objective-c swift


【解决方案1】:

对我来说唯一突出的是您使用String 而不是NSString 作为name 的类型。在 Apple 推出的 beta 版本中,String(奇怪的是)并不是NSString 的一对一替代品。即,缺少某些方法,需要调用.bridgeToObjectiveC() 来获取NSString 版本。使用该类型可能会符合NSCoder 的预期,尽管这种差异不应该如此。

因为我不在我的开发机器上,所以我实际上并没有测试这个声明。但这是我的直觉。尝试一下,看看会发生什么!如果没有任何变化,请尝试切换设置 vars 的顺序,看看是与 name 字段相关的问题还是与 init 函数的第一行有关。

【讨论】:

  • 不要使用bridgeToObjectiveC()。请改用(thing as NSString)
  • 是的,这是@JackLawrence 的好建议。但是,如果他们想要/需要使用String,同时拥有这两种类型的一个好方法是桥接。
  • @Nick 这个解决方案运气好吗?
  • (thing as NSString)(thing as String) 致电 bridgeToObjectiveC()bridgeToObjectiveC() 函数是私有 API 以支持那些桥接演员。
【解决方案2】:

下面的代码可以快速保存检索 UserDefaults 中的 NSCoding 值

import UIKit
import Foundation

class ViewController: UIViewController {

    var employees: Employees?
    let static_key = "nscdeing_data_saved"

    override func viewDidLoad() {
        super.viewDidLoad()
        var request = URLRequest(url: URL(string: "http://dummy.restapiexample.com/api/v1/employees")!, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 60)
        request.httpMethod = "GET"
        URLSession.shared.dataTask(with: request) { (data, response, error) in
            if let status = (response as? HTTPURLResponse)?.statusCode, status == 200, let data = data{
                do {
                    guard let dic = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:Any] else { return }
                    self.employees = Employees.init(fromDictionary: dic)
                    let archiveData = try NSKeyedArchiver.archivedData(withRootObject: self.employees as Any, requiringSecureCoding: true)
                    UserDefaults.standard.set(archiveData, forKey: self.static_key)
                } catch let error {
                    fatalError(error.localizedDescription)
                }
            }
        }.resume()
    }
    @IBAction func printAction(_ sender: Any) {
        if let data = UserDefaults.standard.data(forKey: static_key){
            do {
                let value = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)
                print(value as Any)
            } catch let error {
                fatalError(error.localizedDescription)
            }
        }
    }
}

class Employees : NSObject, NSCoding, NSSecureCoding{
    static var supportsSecureCoding: Bool{
        return true
    }

    var data : [Datum]!
    var status : String!
    /**
     * Instantiate the instance using the passed dictionary values to set the properties values
     */
    init(fromDictionary dictionary: [String:Any]){
        status = dictionary["status"] as? String
        data = [Datum]()
        if let dataArray = dictionary["data"] as? [[String:Any]]{
            for dic in dataArray{
                let value = Datum(fromDictionary: dic)
                data.append(value)
            }
        }
    }

    /**
     * Returns all the available property values in the form of [String:Any] object where the key is the approperiate json key and the value is the value of the corresponding property
     */
    func toDictionary() -> [String:Any]{
        var dictionary = [String:Any]()
        if status != nil{
            dictionary["status"] = status
        }
        if data != nil{
            var dictionaryElements = [[String:Any]]()
            for dataElement in data {
                dictionaryElements.append(dataElement.toDictionary())
            }
            dictionary["data"] = dictionaryElements
        }
        return dictionary
    }

    /**
     * NSCoding required initializer.
     * Fills the data from the passed decoder
     */
    @objc required init(coder aDecoder: NSCoder){
        data = aDecoder.decodeObject(forKey: "data") as? [Datum]
        status = aDecoder.decodeObject(forKey: "status") as? String
    }

    /**
     * NSCoding required method.
     * Encodes mode properties into the decoder
     */
    @objc func encode(with aCoder: NSCoder){
        if data != nil{
            aCoder.encode(data, forKey: "data")
        }
        if status != nil{
            aCoder.encode(status, forKey: "status")
        }
    }
}
class Datum : NSObject, NSCoding, NSSecureCoding{
    static var supportsSecureCoding: Bool{
        return true
    }

    var employeeAge : String!
    var employeeName : String!
    var employeeSalary : String!
    var id : String!
    var profileImage : String!

    /**
     * Instantiate the instance using the passed dictionary values to set the properties values
     */
    init(fromDictionary dictionary: [String:Any]){
        employeeAge = dictionary["employee_age"] as? String
        employeeName = dictionary["employee_name"] as? String
        employeeSalary = dictionary["employee_salary"] as? String
        id = dictionary["id"] as? String
        profileImage = dictionary["profile_image"] as? String
    }

    /**
     * Returns all the available property values in the form of [String:Any] object where the key is the approperiate json key and the value is the value of the corresponding property
     */
    func toDictionary() -> [String:Any]{
        var dictionary = [String:Any]()
        if employeeAge != nil{
            dictionary["employee_age"] = employeeAge
        }
        if employeeName != nil{
            dictionary["employee_name"] = employeeName
        }
        if employeeSalary != nil{
            dictionary["employee_salary"] = employeeSalary
        }
        if id != nil{
            dictionary["id"] = id
        }
        if profileImage != nil{
            dictionary["profile_image"] = profileImage
        }
        return dictionary
    }

    /**
     * NSCoding required initializer.
     * Fills the data from the passed decoder
     */
    @objc required init(coder aDecoder: NSCoder){
        employeeAge = aDecoder.decodeObject(forKey: "employee_age") as? String
        employeeName = aDecoder.decodeObject(forKey: "employee_name") as? String
        employeeSalary = aDecoder.decodeObject(forKey: "employee_salary") as? String
        id = aDecoder.decodeObject(forKey: "id") as? String
        profileImage = aDecoder.decodeObject(forKey: "profile_image") as? String
    }

    /**
     * NSCoding required method.
     * Encodes mode properties into the decoder
     */
    @objc func encode(with aCoder: NSCoder){
        if employeeAge != nil{
            aCoder.encode(employeeAge, forKey: "employee_age")
        }
        if employeeName != nil{
            aCoder.encode(employeeName, forKey: "employee_name")
        }
        if employeeSalary != nil{
            aCoder.encode(employeeSalary, forKey: "employee_salary")
        }
        if id != nil{
            aCoder.encode(id, forKey: "id")
        }
        if profileImage != nil{
            aCoder.encode(profileImage, forKey: "profile_image")
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-04
    • 2015-11-02
    • 2015-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多