NSKeyedUnarchiver decodeObjectForKey:]: 无法解码 class 的对象
NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class
我有一个具有多个目标的应用程序(每个目标作为具有不同名称、包标识符等的单独应用程序供另一个客户端使用)。
我有方法:
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 {
行抛出错误:
* Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*
-[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (_test__MyApp.UserSession) for key (root); the class may be
defined in source code or a library that is not linked'
我试图捕获 do {} catch {}
但它没有捕获并抛出相同的错误 + xCode 说
catch' block is unreachable because no errors are thrown in '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([=12=]) }
} 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
class
你在这里得到的是 exception
; exceptions cannot be caught or handled in Swift, and are different from errors
,这就是为什么你不能将调用包装在 do {} catch {}
中的原因。
这里的问题是您的存档包含 class 的名称,然后在运行时不可用,这可能有多种原因:
- 您在包含 class 的应用程序中对存档进行编码,并试图在不包含 class 的其他应用程序中解码。如果您忘记 link 使用您正在使用的目标的 class 实现,则可能会发生这种情况,但在 Swift 中这种可能性要小得多,因为您无法导入 header 而忘记 link 实现
- class 名称已更改。发生这种情况本身可能有几个原因,但在 Swift 中,最可能的原因是您的 app/module 名称更改。 Swift 中的 类 具有运行时名称,其中包括 class 的完整路径。如果您有一个名为 "MyApp" 的应用程序,一个名为 "Foo" 的 class 的限定名称为 "MyApp.Foo"。类似地,嵌套在 "Foo" 中的 class "Bar" 将具有限定名称 "MyApp.Foo.Bar"。重要的是,如果您更改应用程序的名称(即主模块的名称),class 的名称也会更改!
这里可能发生的情况是,您在写入存档后重命名了目标(这会更改 class 名称),或者您使用 class 写入存档目标,但正在另一个解码。即使您在两者中包含相同的 class,它们的名称也不同("MyTarget1.UserSession" 与 "MyTarget2.UserSession")。
您可以通过几个步骤解决这个问题:
- 给 class 一个不会随着
@objc
改变的稳定名称,例如@objc(UserSession) class UserSession { ... }
。这将为 class 提供一个 Objective-C 名称,该名称是常量并且 不 以任何方式 依赖于模块名称
- 使用
NSKeyedUnarchiver.setClass(_:forClassName:)
迁移旧存档以使用新的、稳定的 class
有关如何向前迁移存档的完整详细信息,请参阅 NSKeyedArchiver and sharing a custom class between targets。
Itai 的回答太棒了,我无法匹配他们的解释。就我而言,主要目标是寻找仅存在于测试目标中的 class 。解决方案是 运行 完成所有测试并再次构建主要目标。我想我上次 运行 我的测试时没有做一些清理工作。
我有一个具有多个目标的应用程序(每个目标作为具有不同名称、包标识符等的单独应用程序供另一个客户端使用)。
我有方法:
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 {
行抛出错误:
* Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '* -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (_test__MyApp.UserSession) for key (root); the class may be defined in source code or a library that is not linked'
我试图捕获 do {} catch {}
但它没有捕获并抛出相同的错误 + xCode 说
catch' block is unreachable because no errors are thrown in '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([=12=]) }
} 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
class
你在这里得到的是 exception
; exceptions cannot be caught or handled in Swift, and are different from errors
,这就是为什么你不能将调用包装在 do {} catch {}
中的原因。
这里的问题是您的存档包含 class 的名称,然后在运行时不可用,这可能有多种原因:
- 您在包含 class 的应用程序中对存档进行编码,并试图在不包含 class 的其他应用程序中解码。如果您忘记 link 使用您正在使用的目标的 class 实现,则可能会发生这种情况,但在 Swift 中这种可能性要小得多,因为您无法导入 header 而忘记 link 实现
- class 名称已更改。发生这种情况本身可能有几个原因,但在 Swift 中,最可能的原因是您的 app/module 名称更改。 Swift 中的 类 具有运行时名称,其中包括 class 的完整路径。如果您有一个名为 "MyApp" 的应用程序,一个名为 "Foo" 的 class 的限定名称为 "MyApp.Foo"。类似地,嵌套在 "Foo" 中的 class "Bar" 将具有限定名称 "MyApp.Foo.Bar"。重要的是,如果您更改应用程序的名称(即主模块的名称),class 的名称也会更改!
这里可能发生的情况是,您在写入存档后重命名了目标(这会更改 class 名称),或者您使用 class 写入存档目标,但正在另一个解码。即使您在两者中包含相同的 class,它们的名称也不同("MyTarget1.UserSession" 与 "MyTarget2.UserSession")。
您可以通过几个步骤解决这个问题:
- 给 class 一个不会随着
@objc
改变的稳定名称,例如@objc(UserSession) class UserSession { ... }
。这将为 class 提供一个 Objective-C 名称,该名称是常量并且 不 以任何方式 依赖于模块名称
- 使用
NSKeyedUnarchiver.setClass(_:forClassName:)
迁移旧存档以使用新的、稳定的 class
有关如何向前迁移存档的完整详细信息,请参阅 NSKeyedArchiver and sharing a custom class between targets。
Itai 的回答太棒了,我无法匹配他们的解释。就我而言,主要目标是寻找仅存在于测试目标中的 class 。解决方案是 运行 完成所有测试并再次构建主要目标。我想我上次 运行 我的测试时没有做一些清理工作。