NSKeyedUnarchiver decodeObjectForKey:]:无法解码类的对象

时间:2018-02-07 14:31:54

标签: ios swift xcode throw

我有一个具有多个目标的应用程序(每个目标是另一个客户端作为具有不同名称,捆绑标识符等的单独应用程序)。

我有方法:

fileprivate static func loadSessionFromKeychain() -> UserSession? {
    if let sessionData = KeychainWrapper.standard.data(forKey: UserSession.sessionDefaultsKey) {

        print("sessionData:")
        print(sessionData.debugDescription)
            if let session = NSKeyedUnarchiver.unarchiveObject(with: sessionData) as? UserSession {
                _current = session
                return session
            } else {
                print("ERROR: Could not parse UserSession from Keychain")
            }
        return nil
    }
    return nil
}

if let session = NSKeyedUnarchiver.unarchiveObject(with: sessionData) as? UserSession {行引发错误:

  

*由于未捕获的异常'NSInvalidUnarchiveOperationException'而终止应用,原因:'*    - [NSKeyedUnarchiver decodeObjectForKey:]:无法解码类(_test__MyApp.UserSession)对象的密钥(root);班级可能是   在源代码中定义或未链接的库

我试图抓住do {} catch {},但它没有抓住并抛出相同的错误+ xCode说

  

catch'块无法访问,因为'do'block`中没有抛出错误

任何想法如何解决这个问题?

UserSessionSwift.swift

    import UIKit
    import SwiftKeychainWrapper

    class UserSession: NSObject, NSCoding {
        // Static vars
        fileprivate static var _current: UserSession?
        static var current: UserSession? {
            get {
                // If there is already a session return it
                if _current != nil {
                    return _current
                }
                // If there is no session yet but one is persistently stored return it
                if let session = self.persistentLoadCurrentSession() {
                    _current = session
                    self.persistentLoadCookies()
                    return session
                }
                // Otherwise return nil
                return nil
            }
            set(value) {
                // Store the actual value
                _current = value

                // Perform hooks after changing the current session
                if value == nil {
                    self.afterLogout()
                } else {
                    self.afterLogin()
                }
            }
        }
        // Constants
        fileprivate static let cookiesDefaultsKey: String = "NSUserDefaultsKeyCookieStorage"
        fileprivate static let sessionDefaultsKey: String = "NSUserDefaultsKeyUserSessionStorage"
        // Instance properties
        let client: Client


        // -------------------------------------------------------------------------------
        // MARK: - Lifecycle
        // -------------------------------------------------------------------------------

        required init(client: Client) {
            // set local properties
            self.client = client

            // call super init
            super.init()

            // Store cookies after a session was initialized
            UserSession.persistentStoreCookies()
        }

        required init?(coder aDecoder: NSCoder) {
            self.client = aDecoder.decodeObject(forKey: "client") as! Client
            super.init()
        }


        // -------------------------------------------------------------------------------
        // MARK: - Public
        // -------------------------------------------------------------------------------

        func encode(with aCoder: NSCoder) {
            aCoder.encode(self.client, forKey: "client")
        }

        /**
         Performs all necessary operations after user logs in: stores current cookies and user session for the case user stops and reruns the application later
         */
        static func afterLogin() {
            // Persistently store session data
            self.persistentStoreCookies()
            self.persistentStoreCurrentSession()

            // Register user & device for PUSH notifications
            NotificationsManager.registerForNotifications()
        }

        /**
         Performs all necessary operations after user logs out: deletes stored cookies and user session so that the next time the user runs this application he gets the login prompt
         */
        static func afterLogout() {
            // Erase user session data
            self.persistentEraseCookies()
            self.persistentEraseCurrentSession()

            // Delete all offers from local database
            CoreDataHelper.deleteEntitiesInContext(CoreDataHelper.mainContext, entityName: UsedOffer.entityName)
            CoreDataHelper.saveContext()
        }

        static func requestPopup() {
            // Get popup from server
            print("INFO: Checking for popups on the server...")
            ClientPopupRequest.send({ (popup) -> Void in
                if let popup = popup {
                    // If there is one, show it
                    popup.showAlertAndPerform(in: RootVC.sharedInstance) {
                        // After the popup performs its action, ask for another one
                        self.requestPopup()
                    }
                } else {
                    // If none, exit
                    print("INFO: No new popups found.")
                }
            }) { (error) -> Void in
            }
        }


        // -------------------------------------------------------------------------------
        // MARK: - Private
        // -------------------------------------------------------------------------------

        /**
         Saves current user session to persistent store (currently NSUserDefaults)
         */
        static func persistentStoreCurrentSession() {
            if let session = _current {
                // Archive session
                let sessionData = NSMutableData()
                let archiver = NSKeyedArchiver(forWritingWith: sessionData)
                archiver.encode(session)
                archiver.finishEncoding()
                // Session encoded

                KeychainWrapper.standard.set(session, forKey: UserSession.sessionDefaultsKey)

    //            UserDefaults.standard.set(sessionData, forKey: UserSession.sessionDefaultsKey)
    //            UserDefaults.standard.synchronize()
            } else {
                print("WARNING: No session to store")
            }
        }

        /**
         Tries to load an user session from persistent store (currently NSUserDefaults) and store it as current session in UserSession class. Returns the loaded instance of user session if it succeeds, otherwise returns nil
         */
        fileprivate static func persistentLoadCurrentSession() -> UserSession? {
            if let keychainData = loadSessionFromKeychain() {
                persistentEraseUserDataSession()

                return keychainData
            } else if let userData = loadSessionFromStore() {
                return userData
            }
            return nil
        }

        fileprivate static func loadSessionFromKeychain() -> UserSession? {
            if let sessionData = KeychainWrapper.standard.data(forKey: UserSession.sessionDefaultsKey) {

                print("sessionData:")
                print(sessionData.debugDescription)
                if let session = NSKeyedUnarchiver.unarchiveObject(with: sessionData) as? UserSession {
                    _current = session
                    return session
                } else {
                    print("ERROR: Could not parse UserSession from Keychain")
                }
                return nil
            }
            return nil
        }

        fileprivate static func loadSessionFromStore() -> UserSession? {
            if let sessionData = UserDefaults.standard.object(forKey: UserSession.sessionDefaultsKey) as? Data {
                let unarchiver = NSKeyedUnarchiver(forReadingWith: sessionData)
                if let session = unarchiver.decodeObject() as? UserSession {
                    unarchiver.finishDecoding()
                    // Session decoded

                    _current = session
                    return session
                } else {
                    print("ERROR: Could not parse UserSession from Store")
                }
                return nil
            }
            return nil
        }

        fileprivate static func persistentEraseCurrentSession() {
            // Remove the current session object
            _current = nil

            // Remove the persisted session object
            UserDefaults.standard.removeObject(forKey: UserSession.sessionDefaultsKey)
            KeychainWrapper.standard.removeObject(forKey: UserSession.sessionDefaultsKey)
        }

        fileprivate static func persistentEraseUserDataSession() {
            // Remove the persisted session object
            UserDefaults.standard.removeObject(forKey: UserSession.sessionDefaultsKey)
        }

        fileprivate static func persistentStoreCookies() {
            if let cookies = HTTPCookieStorage.shared.cookies {
                let cookieData = NSKeyedArchiver.archivedData(withRootObject: cookies)

                UserDefaults.standard.set(cookieData, forKey: UserSession.sessionDefaultsKey)
                KeychainWrapper.standard.set(cookieData, forKey: UserSession.cookiesDefaultsKey)
            } else {
                print("WARNING: No cookies to store")
            }
        }

        fileprivate static func persistentLoadCookies() {

            var cookieData: Data?

            if let keychainData = KeychainWrapper.standard.data(forKey: UserSession.cookiesDefaultsKey) {
                cookieData = keychainData
            } else if let userData = UserDefaults.standard.object(forKey: UserSession.cookiesDefaultsKey) as? Data {
                cookieData = userData
            }

            if (cookieData != nil) {
                if let cookies = NSKeyedUnarchiver.unarchiveObject(with: cookieData!) as? [HTTPCookie] {
                    cookies.forEach { HTTPCookieStorage.shared.setCookie($0) }
                } else {
                    print("ERROR: Could not parse [NSHTTPCookie] from unarchived data")
                }
            } else {
                print("WARNING: No cookies to load")
            }
        }

        fileprivate static func persistentEraseCookies() {
            UserDefaults.standard.removeObject(forKey: UserSession.cookiesDefaultsKey)
            KeychainWrapper.standard.removeObject(forKey: UserSession.cookiesDefaultsKey)
        }
    }

//编辑:添加了UserSession.swift

2 个答案:

答案 0 :(得分:8)

你来这里的是exception;无法在Swift中捕获或处理异常,并且与errors不同,这就是您无法在do {} catch {}中打包调用的原因。

这里的问题是您的存档包含一个类的名称,该类在运行时不可用,这可能由于以下几个原因而发生:

  1. 您在包含该类的应用中对存档进行了编码,并尝试在不包含该类的其他应用中进行解码。如果您忘记将类实现与您正在使用的目标相关联,就会发生这种情况,但这在Swift中的可能性要小得多,因为您无法导入标题而忘记链接实现
  2. 班级名称已更改。这可能由于一些原因而发生,但在Swift中,最可能的原因是您的应用程序/模块名称发生了变化。 Swift中的类具有运行时名称,其中包括类的完整路径。如果您有一个名为" MyApp"的应用程序,则称为" Foo"有一个合格的名称" MyApp.Foo"。同样,一个班级" Bar"嵌套在" Foo"将有一个合格的名称" MyApp.Foo.Bar"。重要的是,如果您更改应用程序的名称(这是主模块的名称),则类的名称会更改!
  3. 这里可能发生的事情是你自编写档案以来已经重命名了目标(这会改变班级名称),或者你在一个目标中写了档案,但是在另一个解码。即使你在两者中包含相同的类,它们也有不同的名称(" MyTarget1.UserSession" vs." MyTarget2.UserSession")。

    您可以通过以下几个步骤来解决此问题:

    1. 为该课程提供一个稳定的名称,该名称不会因@objc而改变,例如@objc(UserSession) class UserSession { ... }。这将为类提供一个常量的Objective-C名称,并且以任何方式依赖于模块名称
    2. 使用NSKeyedUnarchiver.setClass(_:forClassName:)迁移旧档案以使用新的稳定类
    3. 有关如何向前迁移存档的完整详细信息,请参阅NSKeyedArchiver and sharing a custom class between targets

答案 1 :(得分:0)

Itai的回答太棒了,我无法同他们的解释相提并论。就我而言,主要目标是寻找仅在测试目标中存在的类。解决方案是运行所有测试以完成并再次构建主要目标。我想我上次运行测试时还没有做一些清理工作。