2017-09-15 3 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 지정된 이름으로 클래스를 찾는 어려움을 겪고 것을 제안합니다. 보시다시피, 클래스의 Objective-C 이름은 스위프트 전용 기능 (이 경우 generics)을 사용하기 때문에 상당히 엉망입니다 (_TtGC26Informacje_o_organizacjach4NodeSS_). 문제는, AFAIK,이 mangling은 안정적이지 않으며, 미래에 변경되지 않을 것을 보장 할 수있는 아카이브 형식의 클래스 이름이 필요합니다. 그래서 나는 클래스 이름이 일정하게 유지되도록 @objc() 속성을 사용하여 안정적인 Objective-C 이름을 제공합니다. 고유하고 변경되지 않는 한 이름이 무엇인지는 중요하지 않습니다.

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

나는이 문제를 해결할 것이라는 점을 보장 할 수 없습니다, 그러나 수도, 그리고 당신이 NSCoding에 의존려고하는 경우에 관계없이해야 할 일입니다.

편집 : 일반 매개 변수를 사용하는 클래스에서는 @objc이 작동하지 않습니다. 따라서 대답은 단순히 NSCoding 지원과 일반 매개 변수가 상호 배타적 인 것입니다. 다행스럽게도 스위프트 4는 NSCoding 대신 Codable을 사용할 수 있습니다.

+0

답은 거의 맞지만 불행히도'NSObject'의 일반적인 서브 클래스는 지정된 '@ objc' 이름을 가질 수 없습니다 :'error :'@objc '클래스의 generic 서브 클래스는 직접적으로'@objc '클래스를 가질 수 없습니다 Objective-C'에서 볼 수 있습니다. –

+0

아, 맞아요. 이 경우,'NSCoding'과 generic 매개 변수는 아마도 서로 호환되지 않을 것입니다. –

0

오류 메시지에 표시되는 클래스 이름은 _TtGC26Informacje_o_organizacjach4NodeSS_이며, 변경된 Swift 클래스 이름입니다. NSObject에서 상속받은 모든 일반 클래스는 런타임에이 맹 글링 된 이름을 가져옵니다.이 이름은 Objective-C 런타임 (따라서 NSKeyedArchiver/NSKeyedUnarchiver)이 볼 수있는 이름입니다.

제네릭 클래스가 인스턴스화 될 때 이러한 클래스 이름이 동적으로 생성된다는 것이 문제입니다. 즉, 해당 유형의 인스턴스를 인스턴스화하기 전에 연결된 목록 유형 중 하나를 디코딩하려고 시도하면 클래스가 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. 사용 인코딩 및 디코딩을위한 다른 종류의 (나단의 코멘트를 언급으로) 호환 - 적절한 왜 안 배열을 사용)

경우 유스 케이스 (즉, 이전 버전과의 호환성 문제는 없습니다.) Swift 4로 작업 할 수있는 경우 새로운 Codable API는 NSKeyedArchiver을 사용하는 대신 살펴볼 가치가 있습니다.

+0

또 다른 해결책은 Swift 4로 이동하고 NSCoding 대신 Codable을 사용하는 것입니다. –

+1

또 하나 추가 할 사항은 옵션 1이 좋은 옵션이 아니라는 것입니다. 왜냐하면 Swift는 언어의 연속 버전에서 mangling 형식이 안정적으로 유지 될 것이라고 보장하지 않기 때문입니다. 이후 릴리스에서 mangling 형식이 변경되면 기존 아카이브가 모두 사용자의 구현과 갑자기 호환되지 않으므로 잘못된 선택이됩니다. 나는 'NSCoding'과 일반적인 매개 변수가 슬프게도 상호 배타적이라고 결론 내릴 것이다. –

+0

@CharlesSrstka 정확하고 언급할만한 가치가 있습니다. 그러나'@ objc' 이름을 가진 인스턴스화 된 제네릭 클래스의 비 제네릭 하위 클래스를 가질 수 있으므로 상호 배타적이지 않습니다. –