2017-09-15 14 views
0

私は最近の10件の検索のリストを持って保存する必要があります(アプリの起動間に一貫性のある情報を表示するため)。NSUserDefaultsと私自身のリンクリストの実装

リンクリスト(LinkedListクラスとNodeクラス)の実装と最近の10個の文字列のリストになるような種類のラッパークラスを作成しました。私はこれらの3つのクラスをすべてNSCodingプロトコルに準拠させ、NSUserDefaultsとして保存するときに動作します。私はそれをロードしようとすると、残念ながら、アプリがエラーでクラッシュ:

クラスノード

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の

:すべての3つのクラスのコードだ

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'

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のサポートと汎用パラメータは相互排他的です。あなたはどちらか一方を犠牲にする必要があります(幸いにも、CodableはおそらくNSCodingの代わりに使用できます)。

+0

あなたの答えは大体正しいですが、残念ながら、 'NSObject'のジェネリックサブクラスは指定された' @ objc'という名前を持つことができません: 'error:' @objc 'クラスのジェネリックサブクラスは、 Objective-Cから見えるよ。 –

+0

ああ、そうだよ。その場合、 'NSCoding'とジェネリックパラメータはおそらく単に互いに互換性がありません。 –

0

エラーメッセージに表示されるクラス名_TtGC26Informacje_o_organizacjach4NodeSS_は、変更されたSwiftクラス名です。 NSObjectから継承したすべてのジェネリッククラスは、実行時にこの名前を取得します。これはObjective-Cランタイム(したがってNSKeyedArchiver/NSKeyedUnarchiver)が参照する名前です。

問題は、これらのクラス名がジェネリッククラスがインスタンス化されたときに動的に生成されることです。つまり、そのタイプのインスタンスをインスタンス化する前に、リンクされたリストタイプの1つをデコードしようとすると、そのクラスは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

もう1つの解決策は、Swift 4に移動し、「NSCoding」の代わりに「Codable」を使用することです。 –

+1

もう1つの追加点は、オプション1は本当に良い選択肢ではないということです。なぜなら、Swiftは、言語の連続したバージョンでマングリング形式が安定していることを何ら保証しないからです。将来のリリースでmangling形式が変更された場合、既存のすべてのアーカイブが突然あなたの実装と互換性がなくなり、これは貧しい選択です。私は、「NSCoding」とジェネリックパラメータは悲しむことに、互いに排他的であると結論づけます。 –

+0

@CharlesSrstka正しい、言及する価値がある。しかし、 '@ objc'という名前を持つインスタンス化されたジェネリッククラスの非ジェネリックサブクラスを持つことができるので、相互に排他的ではありません。 –

関連する問題