NSUserDefaults和我自己的链表实现

时间:2017-09-15 17:53:16

标签: ios iphone swift nsuserdefaults nscoding

我正在创建应用,我需要在其中列出最近10次搜索并保存(以显示应用启动之间的一致信息)。

为了实现这一点,我创建了链接列表(LinkedList和Node类)的实现,以及某种类型的包装类,它将它保存为最近10个字符串的列表。我使所有这3个类符合NSCoding协议,并且当它需要将其保存为NSUserDefaults时它可以工作。不幸的是,当我尝试加载它时,应用程序崩溃并出现错误:

  

由于未捕获的异常终止应用程序' NSInvalidUnarchiveOperationException',原因:' *** - [NSKeyedUnarchiver decodeObjectForKey:]:无法解码类(_TtGC26Informacje_o_organizacjach4NodeSS_)的对象(head);该类可以在源代码中定义,也可以在未链接的库中定义

这是所有3个类的代码:

类节点

public class Node<T>: NSObject, NSCoding {
var value: T

var next: Node<T>?
var previous: Node<T>?


init(value: T) {
    self.value = value
}

public required init(coder aDecoder: NSCoder) {
    value = aDecoder.decodeObject(forKey: "value") as! T

    next = aDecoder.decodeObject(forKey: "next") as? Node<T>
    previous = aDecoder.decodeObject(forKey: "previous") as? Node<T>
}

public func encode(with aCoder: NSCoder) {
    aCoder.encode(value, forKey: "value")

    aCoder.encode(next, forKey: "next")
    aCoder.encode(previous, forKey: "previous")
}
}

类LinkedList

public class LinkedList<T>: NSObject, NSCoding {
fileprivate var head: Node<T>?
private var tail: Node<T>?

override init() {
    head = nil
    tail  = nil
}

public var isEmpty: Bool {
    return head == nil
}

public var first: Node<T>? {
    return head
}

public var last: Node<T>? {
    return tail
}

public var count: Int {
    var node = head
    var count = 0

    while node != nil {
        count = count + 1
        node = node?.next
    }

    return count
}

public func removeLast() {
    if let lastNode = last {
        remove(node: lastNode)
    }
}

public func appendFirst(value: T) {
    let newNode = Node(value: value)

    if let headNode = head {
        headNode.previous = newNode
        newNode.next = headNode
    } else {
        tail = newNode
    }

    head = newNode
}

public func append(value: T) {
    let newNode = Node(value: value)

    if let tailNode = tail {
        newNode.previous = tailNode
        tailNode.next = newNode
    } else {
        head = newNode
    }

    tail = newNode
}

public func nodeAt(index: Int) -> Node<T>? {
    if index >= 0 {
        var node = head
        var i = index

        while node != nil {
            if i == 0 { return node }
            i -= 1
            node = node!.next
        }
    }

    return nil
}

public func removeAll() {
    head = nil
    tail = nil
}

public func remove(node: Node<T>) -> T {
    let prev = node.previous
    let next = node.next

    if let prev = prev {
        prev.next = next
    } else {
        head = next
    }

    next?.previous = prev

    if next == nil {
        tail = prev
    }

    node.previous = nil
    node.next = nil

    return node.value
}

public required init?(coder aDecoder: NSCoder) {
    head = aDecoder.decodeObject(forKey: "head") as? Node<T>
    tail = aDecoder.decodeObject(forKey: "tail") as? Node<T>
}

public func encode(with aCoder: NSCoder) {
    aCoder.encode(head, forKey: "head")
    aCoder.encode(tail, forKey: "tail")
}
}

课程最近

public class Recents: NSObject, NSCoding {
fileprivate var list: LinkedList<String>

override init() {
    list = LinkedList<String>()
}

public func enqueue(_ element: String) {
    if let node = search(for: element) {
        list.remove(node: node)
    } else {
        if list.count >= 10 {
            list.removeLast()
        }
    }

    list.appendFirst(value: element)
}

func search(for value: String) -> Node<String>? {
    var curr = list.first

    while curr != nil {
        if curr?.value == value {
            return curr
        }

        curr = curr?.next
    }

    return nil
}

public func count() -> Int {
    return list.count
}

public func nodeAt(index: Int) -> String {
    return list.nodeAt(index: index)!.value
}

public var isEmpty: Bool {
    return list.isEmpty
}

public required init(coder aDecoder: NSCoder) {
    list = aDecoder.decodeObject(forKey: "list") as! LinkedList<String>
}

public func encode(with aCoder: NSCoder) {
    aCoder.encode(list, forKey: "list")
}
}


I use this code to load and save data into NSUserDefaults:

    func saveRecents() {
        let savedData = NSKeyedArchiver.archivedData(withRootObject: recents)
        let defaults = UserDefaults.standard
        defaults.set(savedData, forKey: "recents")
    }

    func loadRecents() {
        let defaults = UserDefaults.standard

        if let savedRecents = defaults.object(forKey: "recents") as? Data {
            recents = NSKeyedUnarchiver.unarchiveObject(with: savedRecents) as! Recents
        }
    }

问题出在哪里?

2 个答案:

答案 0 :(得分:0)

您收到的错误消息:

  

由于未捕获的异常终止应用程序&#39; NSInvalidUnarchiveOperationException&#39;,原因:&#39; *** - [NSKeyedUnarchiver decodeObjectForKey:]:无法解码类(_TtGC26Informacje_o_organizacjach4NodeSS_)的对象(head);该类可以在源代码中定义,也可以在未链接的库中定义

表明NSKeyedUnarchiver很难找到具有给定名称的类。正如您所看到的,您的类的Objective-C名称非常严重(_TtGC26Informacje_o_organizacjach4NodeSS_),因为它使用了仅限Swift的功能(在本例中为泛型)。问题是,AFAIK,这种破坏不稳定,你需要一个档案格式的类名,你可以保证将来不会改变。所以我要做的是使用@objc()属性提供稳定的Objective-C名称,以确保类名保持不变。名称是什么并不重要,只要它是独一无二的,不会改变。

@objc(MyApp_Node) public class Node<S>: NSObject, NSCoding {

我无法保证这会解决问题,但它可能会成为您需要做的事情,无论您是否依赖NSCoding。< / p>

编辑:事实证明@objc不适用于具有通用参数的类。所以答案很简单,NSCoding支持和泛型参数是互斥的。你需要牺牲其中一个(幸运的是,Swift 4有Codable你可以使用NSCoding而不是node_acl。)

答案 1 :(得分:0)

您在错误消息_TtGC26Informacje_o_organizacjach4NodeSS_中看到的类名是一个受损的Swift类名。所有泛型类,甚至是继承自NSObject的泛型类,都会在运行时获得这个受损的名称,这就是Objective-C运行时(以及NSKeyedArchiver / NSKeyedUnarchiver)所看到的名称。 / p>

现在的问题是,在实例化泛型类时动态生成这些类名。这意味着如果您在实例化该类型的实例之前尝试解码其中一个链接列表类型,则该类将永远不会在Objective-C运行时中注册,并且您将收到您看到的错误消息,因为班级不存在。

对于大多数类型,Charles的答案是正确的 - 对该类采用明确的@objc名称来覆盖重整并给它一个稳定的名称。但是,泛型类不能为它们分配@objc个名称,因为每个实例化类型都是它自己的类:

导入基金会

@objc(MyX) // error: generic subclasses of '@objc' classes cannot have an explicit '@objc' because they are not directly visible from Objective-C
class X<T> : NSObject {
    @objc
    func foo() {
        NSLog("%@", self.className)
    }
}

let x = X<Int>()
x.foo()

这意味着除非您在尝试取消归档之前实例化您的类,否则它将无法在运行时中使用。

不幸的是,您的选择有限:

  1. 在尝试取消归档之前以某种方式实例化该类的实例( NOT 推荐,因为将来可能会更改已损坏的名称,并且您现有的归档将不再兼容)
  2. 使用不同的类型进行编码和解码(如nathan的评论所述 - 为什么不使用数组?)
  3. 如果适合您的用例(即您没有向后兼容性问题)并且您可以使用Swift 4,那么新的Codable API也可能值得研究,而不是使用{{1} }。