NSKeyedArchiver

I am having trouble with NSKeyedArchiver. I create an archive with


    archive = [NSDictionary dictionaryWithObjectsAndKeys:
               pruneTableP2,   @"depth table",
               nil];

    archiveData = [NSKeyedArchiver archivedDataWithRootObject: archive
                                        requiringSecureCoding: NO
                                                        error: nil];

I then get a "The data isn’t in the correct format" error when I go to unarchive it with unarchivedObjectOfClass:. The subsequent call to a depreciated method works however.

        archive = [NSKeyedUnarchiver unarchivedObjectOfClass: [NSDictionary class]
                                                    fromData: fileData
                                                       error: &error];


        if( archive == nil )
        {
            archive = [NSKeyedUnarchiver unarchiveObjectWithData: fileData];
            NSLog( @"%@", [error localizedFailureReason]);
        }

What am I doing wrong?

  • Can you show the details of pruneTableP2? Especially its type?

  • It's an NSMutableData object.

    pruneTableP2 NSConcreteMutableData * 274337280 bytes 0x00006000002b8120

  • I copied the pruneTableP2 into an NSData object and now unarchivedObjectOfClass: will read in the archive. It doesn't like NSMutableData. But now it throws out a couple of warnings.

    2021-11-03 22:31:46.749411-0500 Cube Console[8846:238300] [general] *** -[NSKeyedUnarchiver validateAllowedClass:forKey:] allowed unarchiving safe plist type ''NSData' (0x1ed406558) [/System/Library/Frameworks/CoreFoundation.framework]' for key 'NS.objects', even though it was not explicitly included in the client allowed classes set: '{( "'NSDictionary' (0x1ed4065d0) [/System/Library/Frameworks/CoreFoundation.framework]" )}'. This will be disallowed in the future. 2021-11-03 22:31:46.749801-0500 Cube Console[8846:238300] [general] *** -[NSKeyedUnarchiver validateAllowedClass:forKey:] allowed unarchiving safe plist type ''NSString' (0x1ed434848) [/System/Library/Frameworks/Foundation.framework]' for key 'NS.keys', even though it was not explicitly included in the client allowed classes set: '{( "'NSDictionary' (0x1ed4065d0) [/System/Library/Frameworks/CoreFoundation.framework]" )}'. This will be disallowed in the future.

Accepted Reply

Using the more explicit method: unarchivedDictionaryWithKeysOfClasses:objectsOfClasses:fromData:error: eliminates all the problems. The following code executes with no errors.


-(void)test: (NSString *)input
{
    NSDictionary        *source,
                        *destination;
    NSSet               *classes;
    NSMutableData       *mData1,
                        *mData2;
    NSData              *table1,
                        *table2,
                        *archive;
    NSError             *error;

    @autoreleasepool
    {
        mData1 = [NSMutableData dataWithData: edgeTableC3v];
        mData2 = [NSMutableData dataWithData: cornerTableC3v];

        table1 = mData1;
        table2 = mData2;

        source = [NSDictionary dictionaryWithObjectsAndKeys:
                  input,        @"input",
                  table1,       @"edge table",
                  table2,       @"corner table",
                  nil];

        archive = [NSKeyedArchiver archivedDataWithRootObject: source
                                        requiringSecureCoding: YES
                                                        error: &error];

        if( error != nil )
        {
            [self report: [NSString stringWithFormat: @"\nError creating archive\n\t%@", [error localizedFailureReason]]];
        }
        else
        {
            classes = [NSSet setWithObjects:
                       [NSData class],
                       [NSString class],
                       nil];

            destination = [NSKeyedUnarchiver unarchivedDictionaryWithKeysOfClasses: [NSSet setWithObject: [NSString class]]
                                                                  objectsOfClasses: classes
                                                                          fromData: archive
                                                                             error: &error];

            if( error != nil)
            {
                [self report: [NSString stringWithFormat: @"\nError reading archive\n\t%@", [error localizedFailureReason]]];
            }
            else
                [self report: [NSString stringWithFormat: @"\nNo Error\n%@", [destination description]]];
        }
        [self reportDone];
    }
}

OUTPUT

No Error
{
    "corner table" = {length = 2449440, bytes = 0x88a40000 e0970000 98c40000 48b70000 ... f3b00000 85a40000 };
    "edge table" = {length = 145152, bytes = 0x00000000 00000000 140b0000 250a0000 ... 99090000 bf080000 };
    input = Parameters;
}


Note that I archived NSMutableData objects and they were unarchived with no error. The runtime doesn't send any warnings when unarchiving.

Replies

pruneTableP2 is an NSData object holding a 200+mbyte table. The first time the user launches the program the table must be calculated and that routine uses an NSMutableData object for that. This is archived to the users home directory to be loaded from disk thereafter. So, although typed as NSData, pruneTableP2 actually is a pointer to an NSMutableData object when it is archived. Sometime after the app was written stricter type checking was added to NSKeyedUnarchiver and the code broke. The fix is to use a pure NSData object rather than its subclass to create the archive.

It is disturbing that even then the runtime sends out these warnings:

Multiline

2021-11-04 08:24:55.954653-0500 Cube Console[1366:19975] [general] *** -[NSKeyedUnarchiver validateAllowedClass:forKey:] allowed unarchiving safe plist type ''NSData' (0x1dda3e558) [/System/Library/Frameworks/CoreFoundation.framework]' for key 'NS.objects', even though it was not explicitly included in the client allowed classes set: '{( "'NSDictionary' (0x1dda3e5d0) [/System/Library/Frameworks/CoreFoundation.framework]" )}'. This will be disallowed in the future

. 2021-11-04 08:24:55.955503-0500 Cube Console[1366:19975] [general] *** -[NSKeyedUnarchiver validateAllowedClass:forKey:] allowed unarchiving safe plist type ''NSString' (0x1dda6c848) [/System/Library/Frameworks/Foundation.framework]' for key 'NS.keys', even though it was not explicitly included in the client allowed classes set: '{( "'NSDictionary' (0x1dda3e5d0) [/System/Library/Frameworks/CoreFoundation.framework]" )}'. This will be disallowed in the future.

BlockQuote

code-block

Did you try using +unarchivedObjectOfClasses:fromData:error: method instead and specify all the classes that may appear in your dictionary in the classes param?

 NSError *error = nil;

  id rootObject =  [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithArray:@[[NSDictionary class],

                                                                       [NSData class],

                                                                     [NSString class]]] //include whatever classes are in your dictionary

                                        fromData:data

                                           error:&error];

Using the more explicit method: unarchivedDictionaryWithKeysOfClasses:objectsOfClasses:fromData:error: eliminates all the problems. The following code executes with no errors.


-(void)test: (NSString *)input
{
    NSDictionary        *source,
                        *destination;
    NSSet               *classes;
    NSMutableData       *mData1,
                        *mData2;
    NSData              *table1,
                        *table2,
                        *archive;
    NSError             *error;

    @autoreleasepool
    {
        mData1 = [NSMutableData dataWithData: edgeTableC3v];
        mData2 = [NSMutableData dataWithData: cornerTableC3v];

        table1 = mData1;
        table2 = mData2;

        source = [NSDictionary dictionaryWithObjectsAndKeys:
                  input,        @"input",
                  table1,       @"edge table",
                  table2,       @"corner table",
                  nil];

        archive = [NSKeyedArchiver archivedDataWithRootObject: source
                                        requiringSecureCoding: YES
                                                        error: &error];

        if( error != nil )
        {
            [self report: [NSString stringWithFormat: @"\nError creating archive\n\t%@", [error localizedFailureReason]]];
        }
        else
        {
            classes = [NSSet setWithObjects:
                       [NSData class],
                       [NSString class],
                       nil];

            destination = [NSKeyedUnarchiver unarchivedDictionaryWithKeysOfClasses: [NSSet setWithObject: [NSString class]]
                                                                  objectsOfClasses: classes
                                                                          fromData: archive
                                                                             error: &error];

            if( error != nil)
            {
                [self report: [NSString stringWithFormat: @"\nError reading archive\n\t%@", [error localizedFailureReason]]];
            }
            else
                [self report: [NSString stringWithFormat: @"\nNo Error\n%@", [destination description]]];
        }
        [self reportDone];
    }
}

OUTPUT

No Error
{
    "corner table" = {length = 2449440, bytes = 0x88a40000 e0970000 98c40000 48b70000 ... f3b00000 85a40000 };
    "edge table" = {length = 145152, bytes = 0x00000000 00000000 140b0000 250a0000 ... 99090000 bf080000 };
    input = Parameters;
}


Note that I archived NSMutableData objects and they were unarchived with no error. The runtime doesn't send any warnings when unarchiving.