Сохранение PFObject NSCoding
Моя проблема: saveInBackground
не работает.
Причина Не работает: Я сохраняю PFObjects
, хранящийся в NSArray, в файл с помощью NSKeyedArchiving
. То, как я это делаю, - это реализовать NSCoding
через эту библиотеку. По какой-то причине мне неизвестно, добавлено несколько других полей и установлено значение NULL. У меня такое чувство, что это вызывает API-вызов saveInBackground
. Когда я вызываю 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
Ответы
Ответ 1
Я создал очень простой обходной путь, который не требует изменений выше NSCoding
Библиотеки:
PFObject *tempRelationship = [PFObject objectWithoutDataWithClassName:@"relationship" objectId:messageRelationship.objectId];
[tempRelationship setObject:[NSNumber numberWithBool:YES] forKey:@"read"];
[tempRelationship saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded)
NSLog(@"Success");
else
NSLog(@"Error");
}];
То, что мы делаем здесь, это создание временного объекта с тем же objectId и сохранение его. Это рабочее решение, которое не создает дубликат объекта на сервере. Спасибо всем, кто помог.
Ответ 2
Причина, по которой вы получаете все записи "<null>"
после NSArchiving, объясняется тем, как используемая вами библиотека NSCoding обрабатывает свойства nil Parse. В частности, в фиксации 18 февраля произошло несколько изменений в обработке 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 предложил альтернативную библиотеку в своем ответе 22 апреля.
ОБНОВЛЕНО:
Так как в альтернативной библиотеке отсутствует поддержка PFFile, ниже приведена категория реализации изменений, необходимых для реализации NSCoding для PFFile. Просто скомпилируйте и добавьте 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 PFObject/PFACL, заменяя className
на parseClassName
plus Martin Rybak 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];
}
} ];
}
Ответ 3
PFObject
не реализует NSCoding
, и похоже, что библиотека, которую вы используете, не кодирует объект должным образом, поэтому ваш текущий подход не будет работать.
Подход, рекомендуемый 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.
}
}];
(Код из Документация кэширования запросов.)
Затем ваше приложение будет загружаться из кеша. Перейдите на kPFCachePolicyCacheElseNetwork
, если вы хотите сначала попробовать кеш диска (быстрее, но, возможно, устаревший).
Свойство объекта запроса maxCacheAge
устанавливает, как долго что-то останется на диске до истечения срока его действия.
В качестве альтернативы здесь существует категория PFObject от Florent, которая добавляет поддержку NSCoder в PFObject. Это отличается от реализации в библиотеке, к которой вы привязались, но я не уверен, насколько она надежна. Возможно, стоит поэкспериментировать.
Ответ 4
Как вы сказали в своем вопросе, нулевые поля должны закручивать вызовы saveInBackground
.
Странно то, что parseClassName также равно null, в то время как это, вероятно, потребуется Parse для его сохранения. Установлен ли он перед сохранением NSArray
в файле?
Итак, я вижу два решения:
- реализовать себя
NSCoding
без нулевых полей, но если объект уже сохранен на сервере, он полезен (даже необходимо) для сохранения его объектов, созданныхAt, updatedAt полей и т.д.
- сохраните каждый
PFObject
в Parse перед сохранением NSArray
в файле, чтобы эти поля не были пустыми.