App crashed with NSInvalidUnarchiveOperationException when run from different target and scheme.

Hi,

I'm trying to create a new target duplicated from the main target (cdx_ios) called cdx-ios-dev02. I also made a new scheme called cdx-ios-dev02 It can be built just fine however when I run it, it crashed and it throws this exception:

*** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (cdx_ios.AuthObject) for key (root) because no class named "cdx_ios.AuthObject" was found; the class needs to be defined in source code or linked in from a library (ensure the class is part of the correct target). If the class was renamed, use setClassName:forClass: to add a class translation mapping to NSKeyedUnarchiver'

This is the class:

class AuthObject: NSObject, NSCoding {
    
    var accessT1: String = ""
    var t1Type: String = "bearer"
    var refreshT1: String = ""
    var expiresIn: Int = 0
    var scope: String = ""
    var jti: String = ""
    
    
    init(accessT1: String = "",
         t1Type: String = "bearer",
         refreshT1: String = "",
         expiresIn: Int = 0,
         scope: String = "",
         jti: String = "") {
        self.accessT1 = accessT1
        self.t1Type = t1Type
        self.refreshT1 = refreshT1
        self.expiresIn = expiresIn
        self.scope = scope
        self.jti = jti
    }
    
    convenience init(dic: [String: Any]) {
        self.init()
        mapping(dic)
    }

    required convenience init(coder aDecoder: NSCoder) {
        let t1 = aDecoder.decodeObject(forKey: "accessT1") as? String ?? ""
        let t1Type = aDecoder.decodeObject(forKey: "t1Type") as? String ?? ""
        let refreshT1 = aDecoder.decodeObject(forKey: "refreshT1") as? String ?? ""
        let expiresIn = aDecoder.decodeInteger(forKey: "expiresIn")
        let scope = aDecoder.decodeObject(forKey: "scope") as? String ?? ""
        let jti = aDecoder.decodeObject(forKey: "jti") as? String ?? ""
        self.init(
            accessT1: t1,
            t1Type: t1Type,
            refreshT1: refreshT1,
            expiresIn: expiresIn,
            scope: scope,
            jti: jti
        )
    }
    
    func mapping(_ dic: [String: Any]) {
        accessT1 = ParseUtil.dictionaryValue(dic, "access_token", "")
        t1Type = ParseUtil.dictionaryValue(dic, "token_type", "bearer")
        refreshT1 = ParseUtil.dictionaryValue(dic, "refresh_token", "")
        expiresIn = ParseUtil.dictionaryValue(dic, "expires_in", 0)
        scope = ParseUtil.dictionaryValue(dic, "scope", "")
        jti = ParseUtil.dictionaryValue(dic, "jti", "")
    }

    func encode(with nsCoder: NSCoder) {
        nsCoder.encode(accessT1, forKey: "accessT1")
        nsCoder.encode(t1Type, forKey: "t1Type")
        nsCoder.encode(refreshT1, forKey: "refreshT1")
        nsCoder.encode(expiresIn, forKey: "expiresIn")
        nsCoder.encode(scope, forKey: "scope")
        nsCoder.encode(jti, forKey: "jti")
    }
}

It worked fine on the original target, cdx-ios. Can anybody help me? Thank you.

Answered by DTS Engineer in 791136022

I'm trying to create a new target duplicated from the main target (cdx_ios) called cdx-ios-dev02.

I think the clue to the answer is in the error message:

cannot decode object of class (cdx_ios.AuthObject) for key (root) because

By default, in an target, the module name for types you declare is the target name, so it's expecting cdx_ios_dev02.AuthObject, not cdx_ios.AuthObject to be in the archive you're trying to read.

Presunably you created that archive with the other target.

(There are other ways of configuring the targets that could cause this kind of mixup to occur, but I just picked the easiest one to mention here. 😄 )

I think the likely solution is to make sure your class has a global Obj-C name instead of a module-qualified name:

@objc(AuthObject)
class AuthObject: NSObject, NSCoding {
…

and you would have to recreate your existing archive that already uses the module qualification..

As I said already, there are several ways that module qualification of type names might get in the way, so the actual answer might be a bit different, but I think this is the general area you should be investigating.

because no class named "cdx_ios.AuthObject" was found;

Could you check that the file that contains AuthObject's definition is included in your second target? You can do this in Xcode by using the inspector area on the right (Identity inspector - Target Membership) when you're viewing that file.

I'm trying to create a new target duplicated from the main target (cdx_ios) called cdx-ios-dev02.

I think the clue to the answer is in the error message:

cannot decode object of class (cdx_ios.AuthObject) for key (root) because

By default, in an target, the module name for types you declare is the target name, so it's expecting cdx_ios_dev02.AuthObject, not cdx_ios.AuthObject to be in the archive you're trying to read.

Presunably you created that archive with the other target.

(There are other ways of configuring the targets that could cause this kind of mixup to occur, but I just picked the easiest one to mention here. 😄 )

I think the likely solution is to make sure your class has a global Obj-C name instead of a module-qualified name:

@objc(AuthObject)
class AuthObject: NSObject, NSCoding {
…

and you would have to recreate your existing archive that already uses the module qualification..

As I said already, there are several ways that module qualification of type names might get in the way, so the actual answer might be a bit different, but I think this is the general area you should be investigating.

Accepted Answer

I've fixed this by using a custom NSKeyedUnarchiver based on this SO answer: https://stackoverflow.com/a/46832840/4124849. Here's the class:

class KeyedUnarchiver: NSKeyedUnarchiver {
    override open func `class`(forClassName codedName: String) -> Swift.AnyClass? {
        let legacyModuleString = "cdx_ios."
        if let range = codedName.range(of: legacyModuleString), range.lowerBound.encodedOffset == 0  {
            return NSClassFromString(codedName.replacingOccurrences(of: legacyModuleString, with: ""))
        }
        return NSClassFromString(codedName)
    }
}

So I guess the problem is that my object has nested object so NSKeyedUnarchiver.setClass(_:forClassName:) doesn't work.

App crashed with NSInvalidUnarchiveOperationException when run from different target and scheme.
 
 
Q