我有一个应用程序,它使用parse将数据存储在解析的本地数据存储中,并将所述数据备份到解析云。一般来说,这非常有效。在本地和云中存储数据的关键代码如下:
- (void)store:(PFObject*) parseObject {
if (parseObject) {
[parseObject pinInBackground];
[parseObject saveEventually];
} else
NSLog(@"Err :Store was passed a nil?");
}
我有一个应用程序,其中一些用户说如果他们设置了数据然后终止应用程序很快就会丢失数据'此后。
当用户数据更新时,该函数会传递大约10个或更多项目以快速连续存储。
我通过执行以下操作测试了此方案。我让所有项目都存储起来并设置一个断点来完成。然后,我再次运行应用程序并按下主页键并轻扫应用程序终止它。它还有大约两分钟的运行时间,但关键是在任何情况下商店都已在每个对象上完成。
我确实发现数据可能会丢失。似乎只是因为这些方法已经运行并不能保证数据将被存储。为了清楚起见,我理解这些函数不存储数据,但我曾(我假设)在完成后保证存储数据的意图。
我会添加以下内容:
我的问题如下:
我期待一旦这些电话回来,任务就被锁定并且总是完成。我知道无法保证保存时间可以保留多长时间,但它总是能够完成最终的'甚至在下一次运行应用程序时。我做错了什么吗?我是否接触过这种数据丢失并需要采取进一步的预防措施?有没有人有任何经验或建议?即使只是你发现它适合你?可能是我在其他地方做过蠢事,但很难看出如何。
感谢您的时间。
答案 0 :(得分:3)
我在这里发布我自己的结果,也许他们对某人有用。在查看Parse源代码后,我了解它的工作原理,我不能保证这是最后一个词或整个故事,但它符合事实。
Parse现在是开源的,所以如果遇到问题,你至少可以去尝试确定发生了什么。
Parse保留一个任务队列,它们按照请求的顺序严格处理。无论是在前台(阻塞)还是在后台(有或没有回调)中请求任务,都是如此。此外,无论任务是查询,引脚,保存等,它们都在任务列表中排队。任务列表保存在内存中,如果应用程序暂停,然后终止或在处理完所有任务之前终止,则任务将丢失。
所以,如果解析没有做任何事情并且你请求一个引脚或保存,那么它将启动它并且很可能一切都会很好。
如果解析正忙(根据我上面的描述),你的saveEventually就在等待处理的队列的末尾。在处理此任务之前,您将面临Parse未记录saveEventually的风险。请注意,重要的是要了解通过流程我的意思是查看任务 - 不完成任务 - 只需简单地查看任务是什么,并在saveEventually的情况下记录它。
我不一定认为这是一个错误。我的问题是我将saveEventually视为数据库提交。我知道这并不意味着它还在云中,我只是假设当saveEventually返回时,将它推送到云端的请求以非易失方式存储。一旦它被处理,它就像一个提交,它最终会进入云端,但你无法确定它是否已经过编程处理(saveEventually回调是在完成时没有记录saveEventually)。
如果应用程序被核了,那么就这样,用户已经采取了一些行动,他们应该知道他们在做什么。但是,如果您声明您的应用程序具有后台任务,则可以确保在用户按下主页键或移动到另一个应用程序时仍有一个线程要执行 - 我认为他们有权这样做。我这样做了,似乎可以防止我看到的数据丢失。我基本上确保处理任务列表,并且Parse已经记录了我的所有saveEventuallys,即使应用程序进入后台也是如此。
很难确定何时终止此线程。在我的应用程序中,我做了一个pin并且最终成对保存。 Pin确实有一个回调,所以我保持了优秀的引脚数。当它达到零时,我再等一会儿然后关闭线程。这给了1分钟执行最终的saveEventually,这似乎在测试中已经足够了。如果我此时可以访问互联网,那么保存最终会在以后或下次运行应用时注意到。
我认为你必须是Parse的重度用户才能看到这个问题。我有很多小数据对象分布在不相关的不同表上。这会创建大量的备份查询和引脚,如果您有一个表或一个数据blob可以一次性固定,您将看不到我描述的问题。
根据评论请求示例代码(7Jan16编辑)
以下是我用来实现上述目标的核心代码。如果使用它,您将需要更改它。我建议在上面阅读后台任务链接。后台线程使应用程序保持足够活跃,以至于解析仍然可以处理其未完成的任务。
此代码块的意图是提供一个帮助函数来跟踪所有未解析的请求。它在调用时启动后台线程(startPinMonitor)。只启动一个监视器,这在startPinMonitor中检查。我的代码在UI线程上运行,但是否则你可能需要一些同步逻辑。
- (void)store:(PFObject*) parseObject {
if (parseObject) {
if (self.outStandingPins == 0)
[self startPinMonitor];
self.outStandingPins++;
[parseObject pinInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
self.outStandingPins--;
if(!succeeded)
NSLog(@"Err : Pin failed?");
}];
[parseObject saveEventually];
}
}
这段代码的目的是让应用程序保持运行,直到处理完所有剩余的解析操作。它首先检查监视器是否已处于活动状态。 'checkPinStatus'方法每30秒调用一次,并在处理完所有引脚后终止。未完成引脚的日志对于检测正确的操作非常有用。按住主页键后,您应该能够验证应用程序是否仍在运行。如果您注释掉'beginBackgroundTaskWithExpirationHandler'方法,则可以确定此代码尝试实现的行为差异。
-(void) startPinMonitor {
if (![self.myTimer isValid]) {
self.myTimer = [NSTimer scheduledTimerWithTimeInterval:30
target:self
selector:@selector(checkPinStatus)
userInfo:nil
repeats:YES];
self.myBackgroundTask = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:^{
NSLog(@"Background tasks stopped");
}];
}
}
-(void) checkPinStatus {
NSLog(@"Current outStandingPins=%i", self.outStandingPins);
if (self.outStandingPins == 0) {
[self.myTimer invalidate];
[[UIApplication sharedApplication]
endBackgroundTask:self.myBackgroundTask];
}
}