2017-09-15 132 views
0

我正在創建應用程序,我需要最近的10個搜索列表並保存它(以顯示應用程序啓動之間的一致信息)。NSUserDefaults和我自己的鏈表實現

爲了我已經創建了我的鏈接列表(LinkedList和Node類)的實現和某種包裝類,它將它保持爲最近10個字符串的列表。我讓所有這三個類都符合NSCoding協議,並且在將它保存爲NSUserDefaults時它可以工作。不幸的是,當我嘗試加載它,應用程序錯誤崩潰:

Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (_TtGC26Informacje_o_organizacjach4NodeSS_) for key (head); the class may be defined in source code or a library that is not linked'

這是所有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 
     } 
    } 

在哪裏的問題?

+1

爲什麼不直接使用數組? – nathan

回答

0

錯誤消息你得到:

Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (_TtGC26Informacje_o_organizacjach4NodeSS_) for key (head); the class may be defined in source code or a library that is not linked'

表明NSKeyedUnarchiver有困難給定名稱查找類。正如你所看到的,由於它使用了Swift-only特性(在這種情況下是泛型),所以你的類的Objective-C名稱是相當嚴重的(_TtGC26Informacje_o_organizacjach4NodeSS_)。問題是,AFAIK,這種損壞是不穩定的,你需要一個檔案格式的類名,你可以保證將來不會改變。所以我要做的是使用@objc()屬性提供一個穩定的Objective-C名稱,以確保類名稱保持不變。名稱是什麼並不重要,只要它是唯一的,不會改變。

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

我不能保證,這將解決這個問題,但它可能和您需要,無論做的,如果你要依靠NSCoding東西。

編輯:事實證明@objc不適用於具有通用參數的類。所以答案很簡單,NSCoding支持和一個通用參數是互斥的。您將需要犧牲其中一個或另一個(幸運的是,Swift 4有Codable,您可以使用它代替NSCoding)。

+0

你的答案大部分是正確的,但不幸的是,'NSObject'的通用子類不能有指定的'@ objc'名稱:'錯誤:'@objc'類的通用子類不能有明確的'@objc',因爲它們不是直接的從客觀-C' –

+0

可見啊,你說得對。在這種情況下,'NSCoding'和泛型參數可能根本不兼容。 –

0

您在錯誤消息_TtGC26Informacje_o_organizacjach4NodeSS_中看到的類名稱是一個錯位的Swift類名。所有通用類,甚至是從NSObject繼承的類,在運行時都會得到這個錯位的名稱,這就是Objective-C運行時的名稱(因此也就是NSKeyedArchiver/NSKeyedUnarchiver)。

問題的關鍵是,當你的泛型類被實例化時,這些類名是動態生成的。這意味着,如果您嘗試在實例化該類型的實例之前解碼其中一個鏈接列表類型,則該類將永遠不會被註冊到Objective-C運行時,並且會看到您看到的錯誤消息,因爲這個類不存在。

對於大多數類型,查爾斯的答案是正確的 - 爲它覆蓋的是混淆並給它一個穩定的名稱,該類採取一個明確的@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. 試圖解除存檔之前不知怎的,實例化類的一個實例(推薦爲重整名稱在未來可能會發生變化,現有的檔案將不再兼容)
  2. 使用不同類型的編碼和解碼(如彌敦道的評論中提到 - ?爲什麼不使用數組)

如果適合您用例(即你沒有向後兼容性問題),你可以使用Swift 4,新的Codable API也可能值得研究,而不是使用NSKeyedArchiver

+0

另一種解決方案可以是移動到夫特4和使用'Codable' NSCoding'的'代替。 –

+1

需要補充的另一件事是,選項1並不是一個好的選擇,因爲Swift沒有保證在連續的語言版本中mangling格式將保持穩定。如果未來發行版中的修改格式發生變化,則所有現有的歸檔文件都會突然變得與您的實現不兼容,因此這是一個糟糕的選擇。我會得出結論,'NSCoding'和泛型參數可悲地相互排斥。 –

+0

@CharlesSrstka正確的,而且值得一提的。但是,您可以擁有一個具有'@ objc'名稱的實例化泛型類的非泛型子類,所以它們不是100%互斥的,而只是大部分。 –