保存PFObject NSCoding

时间:2014-04-16 20:43:10

标签: ios objective-c parse-platform nskeyedarchiver nscoding

我的问题: saveInBackground无法正常工作。

原因不起作用:我使用PFObjects将存储在NSArray中的NSKeyedArchiving保存到文件中。我这样做的方法是通过此library实施NSCoding。由于某些我不知道的原因,正在添加其他几个字段并将其设置为NULL。我觉得这会搞砸到saveInBackground的API调用。当我在第一组对象上调用saveInBackground时(在NSKeyedArchiving之前)saveInBackground工作正常。但是,当我在第二个对象上调用它时(在NSKeyedArchiving之后)它不会保存。这是为什么?

保存

[NSKeyedArchiver archiveRootObject:_myArray toFile:[self returnFilePathForType:@"myArray"]];

检索

_myArray = (NSMutableArray *)[NSKeyedUnarchiver unarchiveObjectWithFile:
                                             [self returnFilePathForType:@"myArray"]];

NSArchiving之前的对象

2014-04-16 16:34:56.267 myApp[339:60b]
<UserToMessage:bXHfPM8sDs:(null)> {
    from = "<PFUser:sdjfa;lfj>";
    messageText = "<MessageText:asdffafs>";
    read = 0;
    to = "<PFUser:asdfadfd>";
}
2014-04-16 16:34:56.841 myApp[339:60b]
<UserToMessage:bXHsdafdfs:(null)> {
    from = "<PFUser:eIasdffoF3gi>";
    messageText = "<MessageText:asdffafs>";
    read = 1;
    to = "<PFUser:63sdafdf5>";
}

NSArchiving后的对象

<UserToMessage:92GGasdffVQLa:(null)> {
    ACL = "<null>";
    createdAt = "<null>";
    from = "<PFUser:eIQsadffF3gi>";
    localId = "<null>";
    messageText = "<MessageText:EudsaffdHpc>";
    objectId = "<null>";
    parseClassName = "<null>";
    read = 0;
    saveDelegate = "<null>";
    to = "<PFUser:63spasdfsxNp5>";
    updatedAt = "<null>";
}

2014-04-16 16:37:46.527 myApp[352:60b]
<UserToMessage:92GadfQLa:(null)> {
    ACL = "<null>";
    createdAt = "<null>";
    from = "<PFUser:eIQsadffF3gi>";
    localId = "<null>";
    messageText = "<MessageText:EuTndasHpc>";
    objectId = "<null>";
    parseClassName = "<null>";
    read = 1;
    saveDelegate = "<null>";
    to = "<PFUser:63spPsadffp5>";
    updatedAt = "<null>";
}

使用Florent的PFObject类别更新

PFObject+MyPFObject_NSCoding.h

#import <Parse/Parse.h>

@interface PFObject (MyPFObject_NSCoding)

-(void) encodeWithCoder:(NSCoder *) encoder;
-(id) initWithCoder:(NSCoder *) aDecoder;
@end

@interface PFACL (extensions)
-(void) encodeWithCoder:(NSCoder *) encoder;
-(id) initWithCoder:(NSCoder *) aDecoder;
@end


 PFObject+MyPFObject_NSCoding.m

#import "PFObject+MyPFObject_NSCoding.h"
@implementation PFObject (MyPFObject_NSCoding)
#pragma mark - NSCoding compliance
#define kPFObjectAllKeys @"___PFObjectAllKeys"
#define kPFObjectClassName @"___PFObjectClassName"
#define kPFObjectObjectId @"___PFObjectId"
#define kPFACLPermissions @"permissionsById"
-(void) encodeWithCoder:(NSCoder *) encoder{

    // Encode first className, objectId and All Keys
    [encoder encodeObject:[self className] forKey:kPFObjectClassName];
    [encoder encodeObject:[self objectId] forKey:kPFObjectObjectId];
    [encoder encodeObject:[self allKeys] forKey:kPFObjectAllKeys];
    for (NSString * key in [self allKeys]) {
        [encoder  encodeObject:self[key] forKey:key];
    }


}
-(id) initWithCoder:(NSCoder *) aDecoder{

    // Decode the className and objectId
    NSString * aClassName  = [aDecoder decodeObjectForKey:kPFObjectClassName];
    NSString * anObjectId = [aDecoder decodeObjectForKey:kPFObjectObjectId];


    // Init the object
    self = [PFObject objectWithoutDataWithClassName:aClassName objectId:anObjectId];

    if (self) {
        NSArray * allKeys = [aDecoder decodeObjectForKey:kPFObjectAllKeys];
        for (NSString * key in allKeys) {
            id obj = [aDecoder decodeObjectForKey:key];
            if (obj) {
                self[key] = obj;
            }

        }
    }
    return self;
}
@end

4 个答案:

答案 0 :(得分:3)

在NSArchiving之后获得所有"<null>"条目的原因是因为您使用的NSCoding库处理nil Parse属性的方式。特别是,在2月18日的提交中,处理nil发生了一些变化,包括删除几个测试以查看属性是否为nil以及在解码中添加以下代码:

    //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];
        }
    }

我建议你使用另一种NSCoding库。

@AaronBrager在4月22日的回答中提出了一个替代图书馆。

<强>更新:

由于替代库缺少对PFFile的支持,下面是为PFFile实现NSCoding所需的更改的类别实现。只需编译并将PFFile+NSCoding.m添加到您的项目中即可。 此实现来自您使用的原始NSCoding库。

PFFile+NSCoding.h

//
//  PFFile+NSCoding.h
//  UpdateZen
//
//  Created by Martin Rybak on 2/3/14.
//  Copyright (c) 2014 UpdateZen. All rights reserved.
//

#import <Parse/Parse.h>

@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 <objc/runtime.h>

#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

第二次更新:

我所描述的更新解决方案(使用Florent's PFObject / PFACL encoders替换classNameparseClassNameMartin Rybak's PFFile encoder的组合)可以工作 - 在下面的测试工具中(请参阅下面的代码) )从saveInBackground恢复后,对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;
}

但是,查看Parse服务器,第二次调用saveInBackground已创建该对象的新版本。

即使这超出了原始问题的范围,我也会查看是否可以鼓励Parse服务器重新保存原始对象。同时请注意投票和/或接受答案,因为它解决了在saveInBackground之后使用NSKeyedArchiving的问题。

最终更新:

这个问题结果只是一个计时问题 - 第一个saveInBackground在NSKeyedArchiver发生时没有完成 - 因此在归档时objectId仍然是零,因此在第二个saveInBackground时仍然是一个新对象。使用块(类似于下面)来检测保存何时完成并且可以调用NSKeyedArchiver也可以使用

以下版本不会导致保存第二个副本:

- (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];
        }
    } ];

}

答案 1 :(得分:0)

PFObject没有实现NSCoding,看起来你正在使用的库并没有正确编码对象,因此你当前的方法赢得了&#39;工作。

Parse推荐的方法是通过设置PFQuery属性将cachePolicy对象缓存到磁盘:

PFQuery *query = [PFQuery queryWithClassName:@"GameScore"];
query.cachePolicy = kPFCachePolicyNetworkElseCache;
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
  if (!error) {
    // Results were successfully found, looking first on the
    // network and then on disk.
  } else {
    // The network was inaccessible and we have no cached data for
    // this query.
  }
}];

(来自Caching Queries documentation的代码。)

然后您的应用将从缓存中加载。如果您想先尝试磁盘缓存(更快,但可能已过期),请切换到kPFCachePolicyCacheElseNetwork

您的查询对象的maxCacheAge属性设置了某些内容在到期之前保留在磁盘上的时间。


或者,there's a PFObject category by Florent here可以为PFObject添加NSCoder支持。它与您链接的库中的实现不同,但我不确定它的可靠性。可能值得尝试。

答案 2 :(得分:0)

我创建了一个非常简单的解决方法,无需更改上述{​​{1}}库:

NSCoding

我们在这里做的是创建一个具有相同objectId的临时对象,并保存它。这是一个工作解决方案,不会在服务器上创建对象的副本。感谢所有帮助过的人。

答案 3 :(得分:-1)

正如您在问题中所说,空字段必须搞砸saveInBackground次来电。

奇怪的是,parseClassName也是null,而Parse可能必须保存它才能保存它。是否在将NSArray保存到文件中之前设置了它?

所以我看到两个解决方案:

  • 在没有空字段的情况下实现自己NSCoding,但如果对象已经保存在服务器上,那么保存其objectIds,createdAt,updatedAt字段等是有用的(甚至是必要的)......
  • 在将PFObject保存到文件中之前,将每个NSArray保存在Parse上,这样这些字段就不会为空。