NSKeyedArchive. I'm getting a nil on decoding a property that is encoded.

I can't figure it out.

mac os, Swift 5, Cocoa, drag n drop, NSPasteboard


first, this is not an NSCoding issue. It's an NSPasteboard issue. Which is gratuitously backwards. it may be related, but NSPasteboardWritable is different enough to be punishing all the way through, and similar enough to remind you all the way that it doesn't need to be this horrible.


my class supports the PasteboardWriting protocol.


public func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any?{
        
        let coder = NSKeyedArchiver(requiringSecureCoding: false) // whatever. secure is fine.
        //coder.
        
        coder.encode(name, forKey: "name") // these work as expected.
        coder.encode(hint, forKey: "hint")
        
        coder.encode(selectedMode, forKey: "selectedMode")  // this is where the problem starts
        coder.encode(modes, forKey: "modes") // this one too.
        coder.encode(baseMode, forKey: "baseMode") // and this one as well.
        coder.encode(delegate, forKey:"delegate") // at the moment, this IS nil
        coder.encode(rep, forKey: "rep") // this is also nil
        coder.encode(subCons, forKey:"subCons") // and this is an empty array.
        
        coder.finishEncoding()
        
        let theData = coder.encodedData
        
        return theData
    }


the properties : selectedMode, baseMode, and modes are all NSCoding compliant classes. In modes case, it's an array of the same class as baseMode and selectedMode. I have stepped through the code. They definetly get encoded.


then on decoding the coder reports that there is indeed an entry for the key, and proceeds to produce a nil value for it.


public required init?(pasteboardPropertyList propertyList: Any, ofType type: NSPasteboard.PasteboardType) {
        

        super.init()
        do {
            let decoder = try NSKeyedUnarchiver(forReadingFrom: propertyList as! Data)

            
            self.name = decoder.decodeObject(forKey: "name") as! String
            self.hint = decoder.decodeObject(forKey: "hint") as! String
            if decoder.containsValue(forKey: "selectedMode"){
                self.selectedMode = (decoder.decodeObject(forKey: "selectedMode") as? BKLayConMode) // this always returns,and it returns a nil
            }
            if decoder.containsValue(forKey: "baseMode"){
                if let b = (decoder.decodeObject(forKey: "baseMode") as? BKLayConMode){
                    self.baseMode = b
                }
            }
            
            self.modes = (decoder.decodeObject(forKey: "modes") as! [BKLayConMode])
            
            if decoder.containsValue(forKey: "rep"){
                self.rep = (decoder.decodeObject(forKey: "rep") as? NSObject)
            }
            
            if decoder.containsValue(forKey: "delegate"){
                self.delegate = (decoder.decodeObject(forKey:"delegate") as? BKLayConDelegate)
            }
            
            self.subCons = (decoder.decodeObject(forKey: "subCons") as! [BKLayerController])
            
            
            
            // now we set it up.
            self.createLayer()
            
            for each in self.subCons{
                each.registerForNotes(observer: self)
                self.layer.addSublayer(each.layer)
            }
            
            if self.selectedMode != nil{
                self.selectedMode.registerForNotes(observer: self)
            }
            
            self.baseMode.registerForNotes(observer: self)
            
        } catch  {
            debugPrint("i screwed up the drag n drop pasteboard reading for BKLayerController.")
        }
    }

I use conditional tests (which I WOULD comment, but the cursor no longer goes where I put it) to test for "selectedMode" "baseMode" and "modes" the archive Has those values set. It just returns nil for every one of them, and my NSCoding initWithCoder method (in the property's class) never gets called.


I don't want to be making my own NSKeyedArchiver, and doing all this myself. It's brutally difficult, punitive, gratuitously different, and required. You can't properly support drag n drop without making your own NSKeyedArchiver, and individually archiving the properties of the Object you need to drag. There's no other way. (Codable is out of bounds at the moment, so please... don;t even mention it)


I think I have done everything correctly here. There's certainly no docs, examples or tutorials that cover this subject. Anyone with experience that can see an obvious problem?

I recently got issues with secureCoding (for IOS App in this case).


You may look at this thread, may be can help. I had to use secureCoding (required by compiler for Swift5 ) and could not use Codable.


https://forums.developer.apple.com/message/356718#356718


May be the discussion there will give you some hints…

Hi Claude31,

hrmmm... I'm still at Swift 4.2, not using secure coding (still just trying to get 25 year old technology to work) I'm reading through anyway. thanks.


in order to test further I enabled my document's ability to save (which works with the same objects)

there are some optionals in my code, and they seem to be messing with things, but even as I have built support for those (conditional encoding AND conditional decoding) I still come up with the same problem in my pasteboard. just refuses to do the work.


meanwhile I can save the doc just fine, and i can load it (meaning: all of my code executes without issue) but the Open file process fails with an error message (but not a compile error)


hands firmly thrown into the air on this one.

I have finally gotten an actual error on the file load error:


2019-05-01 23:30:53.955709-0400 uiDsgnr[9942:1538707] -[NSDocumentController reopenDocumentForURL:withContentsOfURL:display:completionHandler:] failed during state restoration. Here's the error:

Error Domain=NSOSStatusErrorDomain Code=-4 "kCFMessagePortTransportError / kCSIdentityDeletedErr / unimpErr: / / unimplemented core routine" UserInfo={NSLocalizedDescription=The autosaved document “tst.uDsgn” could not be reopened. , NSUnderlyingError=0x600000c90900 {Error Domain=NSOSStatusErrorDomain Code=-4 "kCFMessagePortTransportError / kCSIdentityDeletedErr / unimpErr: / / unimplemented core routine"}}


none of this means anything to me, except to say that it looks like it's a Foundation bug, or some kind of configuration issue/bug. google searches reveal nothing.

but the Open file process fails with an error message

Just in case, what is the error message ?


A problem here is how poor (to the the least) documentation is. Really a pity, worth a bug report I feel.


Good luck.

I posted the error message. I don't see that error all the time either.


but I decided to isolate the issue. I tried to write a playground to test it. And it didn't take too long to duplicate the problem I am seeing in my drag n drop code. you need a mac os based playground, but here's all of the code:


import Cocoa

// first we would like a generic class that we can test.

class gen : NSObject, NSCoding{
    
    var prop01 : Float = 23
    var name : String = "name"
    var opt : Int? = nil
    
    func encode(with aCoder: NSCoder) {
        aCoder.encode(prop01, forKey: "prop01")
        aCoder.encode(name, forKey:"name")
        aCoder.encode(opt, forKey:"opt")
    }
    
    required init?(coder aDecoder: NSCoder) {
        self.prop01 = aDecoder.decodeFloat(forKey: "prop01")
        self.name = aDecoder.decodeObject(forKey: "name") as! String
        self.opt = aDecoder.decodeObject(forKey: "opt") as? Int
        super.init()
    }
    
    override init(){
        super.init()
    }
}

//
let aGen = gen()

// now we make the code that will compress it down to data.

let arch = NSKeyedArchiver(requiringSecureCoding: false)
arch.encode(aGen, forKey: "gen")
arch.finishEncoding()

let theData = arch.encodedData

// now we have the data. let's see if we can uncompress it.
let unarch = try NSKeyedUnarchiver(forReadingFrom: theData)

if unarch.containsValue(forKey: "gen"){
    let oldGen = unarch.decodeObject(forKey: "gen") as! gen
    let nameTest = oldGen.name
    let prop01Test = oldGen.prop01
    let opttest = oldGen.opt
}
// nope. theData contains value for the key "gen" but it returns nil.


maybe I'm doing something wrong, but as you pointed out: there's no guidance in any form for this stuff. My next steps will to see if turning on Secure coding changes anything.

they've purposefully disabled NSCoding, without providing _any_ support for it in the compiler. You are now FORCED to use Secure coding.

this would have been SO much easier had they... I don't know... added an error message that explains this.


here's the playground that works:


import Cocoa

// first we would like a generic class that we can test.

class gen : NSObject, NSSecureCoding{
    static var supportsSecureCoding: Bool = true
    
    var prop01 : Float = 23
    var name : String = "name"
    var opt : Int? = nil
    
    func encode(with aCoder: NSCoder) {
        aCoder.encode(prop01, forKey: "prop01")
        aCoder.encode(name, forKey:"name")
        aCoder.encode(opt, forKey:"opt")
    }
    
    required init?(coder aDecoder: NSCoder) {
        //self.opt = aDecoder.decodeObject(of:Int.self, forKey: "opt")
        self.prop01 = aDecoder.decodeFloat(forKey: "prop01")
        self.name = aDecoder.decodeObject(forKey: "name") as! String
        self.opt = aDecoder.decodeObject(forKey: "opt") as? Int
        super.init()
    }
    
    override init(){
        super.init()
    }
}

//
let aGen = gen()

// now we make the code that will compress it down to data.

let arch = NSKeyedArchiver(requiringSecureCoding: true)
arch.encode(aGen, forKey: "gen")
arch.finishEncoding()

let theData = arch.encodedData

// now we have the data. let's see if we can uncompress it.
let unarch = try NSKeyedUnarchiver(forReadingFrom: theData)

if unarch.containsValue(forKey: "gen"){
    let oldGen = unarch.decodeObject(of:gen.self, forKey: "gen")
    let nameTest = oldGen?.name
    let prop01Test = oldGen?.prop01
    let opttest = oldGen?.opt
}



what did I change? line 46. First I tried changing line 19, but secure coding is not applied to Types, just classes. (something that was not clear at all from documentation.)


I think I can fairly quickly test this in the wild. But it's going to be a drastic amount of work to get the change in my larger project. Not happy with the way this was handled. not one bit.

Fortunately my project did not make a lot of use of NSKeyedArchiver, so change was limited. But the headache was the same.


I did file a bug report against documentation:#49875853


You should probably do the same.


Good luck.

I will.

meanwhile... how in the heck are you supposed to unarchive an array???

more documentation failure as far as I am cocerned.


this:

self.constraints = aDecoder.decodeObject(of: [BKConstraintDefinition.self], forKey: "constraints")


reports this error:

Cannot assign value of type 'Any?' to type '[BKConstraintDefinition]?'


which is of course, no help.

BKConstraintDefinition is an NSSercureCoding compliant class.

If I remember (not sure), if you include the base class in the list of secureCoding, the array should automatically be accepted.

if i understand you correctly, you simply omit the brackets and it should work.


self.constraints = aDecoder.decodeObjectof: BKConstraintDefinition.self, forKey: "constraints") 


I can without any doubt about it say that this does not work. I really wanted to avoid coming back to this forum and ask for even more help, but frankly Apple is No help, and web based tutorials are even worse. This is not something that should be difficult, or even ignored. People use arrays ALL-THE-TIME. Yet, archiving them is ignored in all documentation.


I just went through an unearthly hassle making my small app (which is itself a side project for a much larger project that will require major retooling for this nightmare of an issue) compatible with NSSecureCoding... one arrow through the heart change is in a spot where I need to store a reference to an unidentified object type. it's a representedObject style reference, popular in Cocoa. you cannot know in advance what type it is, And so you try to reference a type that it will definetly be descended from. In this case: NSObject. Which is Not coding compliant at all. encoding that is dead easy. decoding it, used to be just fine.


I was forced to write a framework in which there is a single NSObject descendant class, that is NSSecureCoding compliant. This forced me to support initWithCoder... and by extension to explicitly write out an init() method for the class. Then, I had a fistfight with every single class in my hierarchy, being forced to write an inti(), initWithCoder(), and encodeWithCoder() method for each and every one of them, regardless of whether or not they add properties to the class definition.


and I find that the code example above: doesn't work. I have no idea why. the error message is inscutable :

Cannot assign value of type 'BKLayerController?' to type '[BKLayerController]'



So I said: I'm screwed anyway, why not look into Codable, and do an end run around this nightmare?

good luck with that. The docs are as good as those of NSCoding and NSSecureCoding, and the very best tutorial I found... compares NSCoding, to Codable. So as far as I can tell... Codable is Not secure.


still doesn't answer one of the most basic questions of Archiving: How to archive arrays?


I can't believe how much work this is taking.

I think I'm going to try Codable.

I can't believe it, But it may be the only way I get out of this snarl.

Here is what I did in a class; I copy the whole, just to show pattern, did not edit the names


@objc class MyFavoris : NSObject, NSSecureCoding {    // formerly NSCoding {

    var mesDatas: [[String: Any]]?  
 

    static var supportsSecureCoding: Bool {
        return true
    }

    override init() {
        super.init()
        self.mesDatas = []
    }
 
    required init(coder decoder: NSCoder) {
     
        mesDatas = decoder.decodeObject(of: [NSArray.self, NSDictionary.self, MyFavoris.self, NSDate.self], forKey: favorisKey) as? [[String: Any]] ?? []
    }


Important line is 17


And you're right. The documenation is really a shame. Letting thousands (we are a million according to Apple) developers loose their time to adapt code to something that was forced on us, without any decent documentation is incredible.


Those who have the chance to be at WWDC should tell this loud and clear…


Unless it is an education strategy, to let developers transpirate, fail, to learn deeper 😉😉

wow. I don't think I can do that. I think... maybe it's impossible to not know beforehand what explicit type to expect in an initWithCoder method. That breaks one of the cardinal rules of Objective programming.

and the net result is: the same.

different names on all the problems, but they are essentially the same.


there's no documentation.

crap that is supposed to work, just doesn't.

more of the same, after 3 hours of making my classes Codable compliant. Add to that the week of trying to make my classes compatible with NSPasteboard, NSCoding and then NSSecureCoding ... and we have a giant waste of time... All because drag N Drop is made out of sadness.

NSKeyedArchive. I'm getting a nil on decoding a property that is encoded.
 
 
Q