2015-10-02 11 views
28

NSKeyedUnarchiver.decodeObject sẽ gây ra sự cố/SIGABRT nếu lớp gốc không xác định. Giải pháp duy nhất tôi đã thấy để bắt gặp vấn đề này bắt nguồn từ lịch sử ban đầu của Swift và được yêu cầu sử dụng Objective C (cũng có sẵn trước ngày triển khai Swift 2 của guard, throws, try & catch). Tôi có thể tìm ra con đường Objective C - nhưng tôi muốn hiểu một giải pháp Swift-only nếu có thể.Cách nhanh nhất để ngăn chặn sự cố NSKeyedUnarchiver.decodeObject?

Ví dụ: dữ liệu đã được mã hóa với NSPropertyListFormat.XMLFormat_v1_0. Mã sau sẽ thất bại tại unarchiver.decodeObject() nếu lớp dữ liệu được mã hóa không xác định.

//... 
let dat = NSData(contentsOfURL: url)! 
let unarchiver = NSKeyedUnarchiver(forReadingWithData: dat) 

//it will crash after this if the class in the xml file is not known 

if let newListCollection = (unarchiver.decodeObject()) as? List { 
    return newListCollection 
} else { 
    return nil 
} 
//... 

Tôi đang tìm kiếm một Swift 2 cách duy nhất để kiểm tra xem dữ liệu có giá trị trước khi thử .decodeObject - kể từ .decodeObject không có throws - có nghĩa là try-catch dường như không phải là một lựa chọn trong Swift (phương pháp nếu không có throws không thể được bọc AFAIK). Hoặc một cách khác để giải mã dữ liệu mà sẽ ném một lỗi tôi có thể bắt nếu giải mã không thành công. Tôi muốn người dùng có thể nhập tệp từ ổ iCloud hoặc Dropbox - do đó cần phải được xác thực hợp lệ. Tôi không thể giả định rằng dữ liệu được mã hóa là an toàn.

Phương thức NSKeyedUnarchiver.unarchiveTopLevelObjectWithData & .validateValue cả hai đều có throws. Có lẽ một số cách mà chúng có thể được sử dụng? Tôi không thể tìm ra cách để bắt đầu thực hiện validateValue trong ngữ cảnh này. Đây có phải là một lộ trình có thể không? Hay tôi nên tìm đến một trong những phương pháp khác cho một giải pháp?

Hoặc có ai biết cách thay thế Swift 2 chỉ để giải quyết vấn đề này không? Tôi tin rằng chìa khóa mà tôi quan tâm có lẽ là $classname - nhưng TBH Tôi đã vượt quá chiều sâu của mình để tìm cách thực hiện validateValue - hoặc thậm chí cho dù đó có phải là con đường chính xác để kiên trì. Tôi có cảm giác rằng tôi đang thiếu một cái gì đó hiển nhiên.


EDIT: Đây là một giải pháp - nhờ của rintaro câu trả lời tuyệt vời (s) dưới đây

Câu trả lời đầu tiên giải quyết vấn đề này đối với tôi - ví dụ: thực hiện một đại biểu.

Còn bây giờ tuy nhiên tôi đã đi với một giải pháp xây dựng xung quanh phản ứng thay đổi nội dung thêm rintaro như sau:

//... 
let dat = NSData(contentsOfURL: url)! 
let unarchiver = NSKeyedUnarchiver(forReadingWithData: dat) 

do { 
    let decodedDataObject = try unarchiver.decodeTopLevelObject() 
    if let newListCollection = decodedDataObject as? List { 
     return newListCollection 
    } else { 
     return nil 
    } 
} 
catch { 
    return nil 
} 
//... 

Trả lời

22

Khi lớp NSKeyedUnarchiver cuộc gặp gỡ không rõ, unarchiver(_:cannotDecodeObjectOfClassName:originalClasses:) phương pháp đại biểu được gọi.

Các đại biểu có thể, ví dụ, tải một số mã để giới thiệu các lớp học để thời gian chạy và trở về lớp, hoặc thay thế một lớp đối tượng khác nhau. Nếu đại biểu trả lại nil, hủy lưu trữ hủy bỏ và phương pháp sẽ tăng NSInvalidUnarchiveOperationException.

Vì vậy, bạn có thể thực hiện các đại biểu như thế này:

class MyUnArchiverDelegate: NSObject, NSKeyedUnarchiverDelegate { 

    // This class is placeholder for unknown classes. 
    // It will eventually be `nil` when decoded. 
    final class Unknown: NSObject, NSCoding { 
     init?(coder aDecoder: NSCoder) { super.init(); return nil } 
     func encodeWithCoder(aCoder: NSCoder) {} 
    } 

    func unarchiver(unarchiver: NSKeyedUnarchiver, cannotDecodeObjectOfClassName name: String, originalClasses classNames: [String]) -> AnyClass? { 
     return Unknown.self 
    } 
} 

Sau đó:

let unarchiver = NSKeyedUnarchiver(forReadingWithData: dat) 
let delegate = MyUnArchiverDelegate() 
unarchiver.delegate = delegate 

unarchiver.decodeObjectForKey("root") 
// -> `nil` if the root object is unknown class. 

THÊM:

Tôi không nhận thấy rằng NSCoder ha s extension với các phương pháp Swifty hơn:

extension NSCoder { 
    @warn_unused_result 
    public func decodeObjectOfClass<DecodedObjectType : NSCoding where DecodedObjectType : NSObject>(cls: DecodedObjectType.Type, forKey key: String) -> DecodedObjectType? 
    @warn_unused_result 
    @nonobjc public func decodeObjectOfClasses(classes: NSSet?, forKey key: String) -> AnyObject? 
    @warn_unused_result 
    public func decodeTopLevelObject() throws -> AnyObject? 
    @warn_unused_result 
    public func decodeTopLevelObjectForKey(key: String) throws -> AnyObject? 
    @warn_unused_result 
    public func decodeTopLevelObjectOfClass<DecodedObjectType : NSCoding where DecodedObjectType : NSObject>(cls: DecodedObjectType.Type, forKey key: String) throws -> DecodedObjectType? 
    @warn_unused_result 
    public func decodeTopLevelObjectOfClasses(classes: NSSet?, forKey key: String) throws -> AnyObject? 
} 

Bạn có thể:

do { 
    try unarchiver.decodeTopLevelObjectForKey("root") 
    // OR `unarchiver.decodeTopLevelObject()` depends on how you archived. 
} 
catch let (err) { 
    print(err) 
} 
// -> emits something like: 
// Error Domain=NSCocoaErrorDomain Code=4864 "*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (MyProject.MyClass) for key (root); the class may be defined in source code or a library that is not linked" UserInfo={NSDebugDescription=*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (MyProject.MyClass) for key (root); the class may be defined in source code or a library that is not linked} 
+0

Cảm ơn bạn rất nhiều. Đó là một câu trả lời tuyệt vời. – simons

+1

Xem câu trả lời cập nhật của tôi. – rintaro

+0

Cảm ơn bạn lần nữa. 'nếu thử unarchiver.decodeTopLevelObject()! = nil' làm việc cho tôi theo cách tôi đã thực hiện nó. Nếu nó không phải là 'nil' thì tôi tốt để đi. Tôi đã bỏ lỡ cái đó khi tìm kiếm các phương pháp ném. Câu trả lời ban đầu của bạn cũng phù hợp với tôi. – simons

15

cách khác là để sửa chữa các tên của lớp được sử dụng để NSCoding. Bạn chỉ cần có để sử dụng:

  • NSKeyedArchiver.setClassName("List", forClass: List.self trước serializing
  • NSKeyedUnarchiver.setClass(List.self, forClassName: "List") trước deserializing

bất cứ nơi nào cần thiết.

Dường như tiện ích mở rộng iOS đặt trước tên lớp với tên của tiện ích mở rộng.

+1

Điều này rất hữu ích cho ứng dụng mới, tuy nhiên hãy thận trọng nếu ứng dụng của bạn đã có mặt trên cửa hàng, như lần đầu tiên sau khi nâng cấp, mọi dữ liệu hiện có sẽ không được lưu trữ với tên Lớp mới, dẫn đến tai nạn. Hãy chắc chắn rằng bạn có một số mã tại chỗ cho khả năng tương thích ngược. Một cách để làm điều này là vẫn sử dụng đại biểu để trả lại tên lớp chính xác. –

+0

@JamesKuang Tôi nghĩ anh ấy đang nói về trường hợp nó sẽ không bao giờ hoạt động nếu không có điều này, tức là. bạn đang mã hóa trong lớp và giải mã trong phần mở rộng, vì vậy tên lớp không khớp. – xaphod

+0

Oddly tôi đã phải sử dụng NSKeyedUnarchiver.setClass cho điều này để làm việc (trên một lớp lồng nhau Swift). Nhưng chỉ khi nhận được nó từ TestFlight. Chạy nó từ Xcode (8.3.1) trên điện thoại thử nghiệm của tôi hoạt động tốt. Và sử dụng @objc() không tạo ra bất kỳ sự khác biệt rõ ràng nào – Kristoffer

0

Thực ra, đó là lý do chúng tôi nên đào sâu những vấn đề. Có thể, bạn tạo một đường dẫn lưu trữ có tên xxx.archive, sau đó bạn hủy lưu trữ khỏi đường dẫn (xxx.archive), bây giờ mọi thứ đều ổn. Nhưng nếu thay đổi tên mục tiêu, khi bạn hủy lưu trữ, sự cố xảy ra !!! Đó là bởi vì lưu trữ & hủy lưu trữ đối tượng khác nhau (sự thật là chúng tôi lưu trữ & hủy lưu trữ target.obj, không chỉ là obj). cách đơn giản là xóa đường dẫn lưu trữ hoặc chỉ sử dụng một đường dẫn lưu trữ khác. Và sau đó chúng ta nên xem xét làm thế nào tránh tai nạn, try-catch là trợ giúp của chúng tôi được đề cập bởi rintaro.

Các vấn đề liên quan