Saving PFObject NSCoding

后端 未结 4 2093
清歌不尽
清歌不尽 2021-02-14 07:45

My Problem: saveInBackground isn\'t working.

The Reason It\'s not working: I\'m saving PFObjects stored in an

4条回答
  •  陌清茗
    陌清茗 (楼主)
    2021-02-14 08:12

    The reason you are getting all the "" entries after NSArchiving is because of the way the NSCoding library you used handles nil Parse properties. In particular, in a commit on 18th Feb, several changes occurred to the handling of nil, including removal of several tests to see if a property was nil plus addition of the following code inside the decode:

        //Deserialize each nil Parse property with NSNull
        //This is to prevent an NSInternalConsistencyException when trying to access them in the future
        for (NSString* key in [self dynamicProperties]) {
            if (![allKeys containsObject:key]) {
                self[key] = [NSNull null];
            }
        }
    

    I suggest you use an alternative NSCoding library.

    @AaronBrager suggested an alternative library in his answer on 22nd Apr.

    UPDATED:

    Since the alternative library is missing support for PFFile, below is a category implementation of the changes you need to implement NSCoding for PFFile. Simply compile and add PFFile+NSCoding.m to your project. This implementation is from the original NSCoding library you used.

    PFFile+NSCoding.h

    //
    //  PFFile+NSCoding.h
    //  UpdateZen
    //
    //  Created by Martin Rybak on 2/3/14.
    //  Copyright (c) 2014 UpdateZen. All rights reserved.
    //
    
    #import 
    
    @interface PFFile (NSCoding)
    
    - (void)encodeWithCoder:(NSCoder*)encoder;
    - (id)initWithCoder:(NSCoder*)aDecoder;
    
    @end
    

    PFFile+NSCoding.m

    //
    //  PFFile+NSCoding.m
    //  UpdateZen
    //
    //  Created by Martin Rybak on 2/3/14.
    //  Copyright (c) 2014 UpdateZen. All rights reserved.
    //
    
    #import "PFFile+NSCoding.h"
    #import 
    
    #define kPFFileName @"_name"
    #define kPFFileIvars @"ivars"
    #define kPFFileData @"data"
    
    @implementation PFFile (NSCoding)
    
    - (void)encodeWithCoder:(NSCoder*)encoder
    {
        [encoder encodeObject:self.name forKey:kPFFileName];
        [encoder encodeObject:[self ivars] forKey:kPFFileIvars];
        if (self.isDataAvailable) {
            [encoder encodeObject:[self getData] forKey:kPFFileData];
        }
    }
    
    - (id)initWithCoder:(NSCoder*)aDecoder
    {
        NSString* name = [aDecoder decodeObjectForKey:kPFFileName];
        NSDictionary* ivars = [aDecoder decodeObjectForKey:kPFFileIvars];
        NSData* data = [aDecoder decodeObjectForKey:kPFFileData];
    
        self = [PFFile fileWithName:name data:data];
        if (self) {
            for (NSString* key in [ivars allKeys]) {
                [self setValue:ivars[key] forKey:key];
            }
        }
        return self;
    }
    
    - (NSDictionary *)ivars
    {
        NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
        unsigned int outCount;
    
        Ivar* ivars = class_copyIvarList([self class], &outCount);
        for (int i = 0; i < outCount; i++){
            Ivar ivar = ivars[i];
            NSString* ivarNameString = [NSString stringWithUTF8String:ivar_getName(ivar)];
            NSValue* value = [self valueForKey:ivarNameString];
            if (value) {
                [dict setValue:value forKey:ivarNameString];
            }
        }
    
        free(ivars);
        return dict;
    }
    
    @end
    

    SECOND UPDATE:

    The updated solution I have described (using the combination of Florent's PFObject / PFACL encoders replacing className with parseClassName plus Martin Rybak's PFFile encoder) DOES work - in the test harness below (see code below) the second call to saveInBackground call does work after a restore from NSKeyedUnarchiver.

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        PFObject *testObject = [PFObject objectWithClassName:@"TestObject"];
        testObject[@"foo1"] = @"bar1";
        [testObject saveInBackground];
    
        BOOL success = [NSKeyedArchiver archiveRootObject:testObject toFile:[self returnFilePathForType:@"testObject"]];
        NSLog(@"Test object after archive (%@): %@", (success ? @"succeeded" : @"failed"), testObject);
    
        testObject = [NSKeyedUnarchiver unarchiveObjectWithFile:[self returnFilePathForType:@"testObject"]];
        NSLog(@"Test object after restore: %@", testObject);
    
        // Change the object
        testObject[@"foo1"] = @"bar2";
        [testObject saveInBackground];
    }
    
    - (NSString *)returnFilePathForType:(NSString *)param {
        NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
        NSString *filePath = [docDir stringByAppendingPathComponent:[param stringByAppendingString:@".dat"]];
    
        return filePath;
    }
    

    However, looking at the Parse server, the second call to saveInBackground has created new version of the object.

    Even though this is beyond the scope of the original question, I'll look to see if it is possible to encourage the Parse server to re-save the original object. Meanwhile please up vote and / or accept the answer given it solves the question of using saveInBackground after NSKeyedArchiving.

    FINAL UPDATE:

    This issue turned out to just be a timing issue - the first saveInBackground had not completed when the NSKeyedArchiver occurred - hence the objectId was still nil at the time of archiving and hence was still a new object at the time of the second saveInBackground. Using a block (similar to below) to detect when the save is complete and it is ok to call NSKeyedArchiver would also work

    The following version does not cause a second copy to be saved:

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        __block PFObject *testObject = [PFObject objectWithClassName:@"TestObject"];
        testObject[@"foo1"] = @"bar1";
        [testObject saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
            if (succeeded) {
                BOOL success = [NSKeyedArchiver archiveRootObject:testObject toFile:[self returnFilePathForType:@"testObject"]];
                NSLog(@"Test object after archive (%@): %@", (success ? @"succeeded" : @"failed"), testObject);
    
                testObject = [NSKeyedUnarchiver unarchiveObjectWithFile:[self returnFilePathForType:@"testObject"]];
                NSLog(@"Test object after restore: %@", testObject);
    
                // Change the object
                testObject[@"foo1"] = @"bar2";
                [testObject saveInBackground];
            }
        } ];
    
    }
    

提交回复
热议问题