Search code examples
iosobjective-cxcodenskeyedarchivernskeyedunarchiver

NSKeyedArchiver deprecated method problem


Just a little background info. I lost my ability to speak a few years ago and learned how to code specifically to make a good text to speech app for myself. Amazingly a lot of others find it useful. Apple reports around 15,000 sessions per week. It is free btw.

At the time I began studying, objective C was the current language so that's what I learned. I am now studying Swift but I am not ready to re write my app. I just don’t know enough yet.

I am trying to update the app but the NSKeyedArchiver has a deprecated method and I'm stuck trying to fix it. Following is a code snippet showing the code. This runs when the user closes the app and it is supposed to save their data:

   ...        
    persistence.field19 = [NSNumber numberWithBool:self.autoOn];
   
        persistence.field20 = [NSNumber numberWithBool:self.instructionShown];
        
        NSMutableData *data = [[NSMutableData alloc] init];
        //NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
    
        NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:false];
    
    
        
        [archiver encodeObject:persistence
                        forKey:kDataKey];
        [archiver finishEncoding];
        [data writeToFile:[self dataFilePath]
               atomically:YES];
    }

I had the line with the "initForWritingWithMutableData:data" and was told to replace with "initRequiringSecureCoding:Bool". This pleased Xcode removing the warning but the user settings and data are no longer saved. I believe it is because I used to assign the NSMutableData to the archiver but no longer do. I found in the documentation the following that might help but I don’t know how to implement:

(NSData *)archivedDataWithRootObject:(id)object 
                 requiringSecureCoding:(BOOL)requiresSecureCoding 
                                 error:(NSError * _Nullable *)error;

When the app starts up again I load in their settings and saved data with the following:

// Restore settings
NSString *filePath = [self dataFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
    NSData *data = [[NSMutableData alloc] initWithContentsOfFile:[self dataFilePath]];
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:NULL];
//initForReadingWithData:data];

The "initForReadingWithData:data" was also deprecated and I was told to use the "initForReadingFromData:error:" method in its place. I "think" that is fine.

Any help or suggestions are greatly appreciated.

Update: Thanks to TheNextman's suggestion, the archiver is working. This is the current code:

    ...
    persistence.field20 = [NSNumber numberWithBool:self.instructionShown];
    
//    NSMutableData *data = [[NSMutableData alloc] init];
//    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];

    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:false];
    [archiver encodeObject:persistence forKey:kDataKey];
        [archiver finishEncoding];

    NSData* data = [archiver encodedData];
    [data writeToFile:[self dataFilePath] atomically:YES];

    
//    [archiver encodeObject:persistence
//                    forKey:kDataKey];
//    [archiver finishEncoding];
//    [data writeToFile:[self dataFilePath]
//           atomically:YES];
}

But for this to work I have to keep using the deprecated unarchiver code.

    // Restore settings
    NSString *filePath = [self dataFilePath];
    if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
        NSData *data = [[NSMutableData alloc] initWithContentsOfFile:[self dataFilePath]];
//        NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:NULL];
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];

The "initForReadingWithData:data" again is deprecated and I'm told to use "initForReadingFromData:error:" If I use the new code, it compiles and runs fine but the user data doesn't reappear.


Solution

  • I didn't test it, but I think you want the encodedData property.

    If encoding has not yet finished, invoking this property calls finishEncoding and populates this property with the encoded data

    It returns an NSData pointer that you can write to disk

    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:false];
    [archiver encodeObject:persistence forKey:kDataKey];
    
    NSData* data = [archiver encodedData];
    [data writeToFile:[self dataFilePath] atomically:YES];
    

    I then unarchive the object like this:

    NSKeyedUnarchiver* unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:nil];
    [unarchiver setRequiresSecureCoding:NO];
    Persistance* p = [unarchiver decodeObjectForKey:kDataKey];
    

    Note that we have to explicitly tell the unarchiver we don't require secure coding (as was specified for the archiver).

    If you check the documentation (and indeed, for example the initForReadingFromData:error: function above) many of the functions take an optional "error" parameter.

    You can pass in a NSError pointer and then check what it contains if you don't get your expected result. For example:

    NSKeyedUnarchiver* unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:nil];
    //[unarchiver setRequiresSecureCoding:NO]
    NSError* err = nil;
    Persistance* p = [unarchiver decodeTopLevelObjectOfClass:[Persistance class] forKey:kDataKey error:&err];
    NSLog(@"%@ %@", p, err);
    

    In this case, you would have seen the problem:

    (null) Error Domain=NSCocoaErrorDomain Code=4864 "This decoder will only decode classes that adopt NSSecureCoding. Class 'Persistance' does not adopt it." UserInfo={NSDebugDescription=This decoder will only decode classes that adopt NSSecureCoding. Class 'Persistance' does not adopt it.}