原因它不工作:我正在使用NSKeyedArchiving将存储在NSArray中的PFObjects保存到文件中.我这样做的方式是通过这个library实现NSCoding.由于某些原因我不知道,还有其他几个字段被添加并设置为NULL.我有一种感觉,这是把API调用到saveInBackground.当我在第一组对象(NSKeyedArchiving之前)调用saveInBackground时,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
解决方法
//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替换className与parseClassName加上Martin 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服务器重新保存原始对象.同时请投票和/或接受答案,解决了在NSKeyedArchiving之后使用saveInBackground的问题.
最新更新:
这个问题原来只是一个时间问题 – 当NSKeyedArchiver发生时,第一个saveInBackground没有完成,因此在归档时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 (%@): %@",testObject); testObject = [NSKeyedUnarchiver unarchiveObjectWithFile:[self returnFilePathForType:@"testObject"]]; NSLog(@"Test object after restore: %@",testObject); // Change the object testObject[@"foo1"] = @"bar2"; [testObject saveInBackground]; } } ]; }