- android - RelativeLayout 背景可绘制重叠内容
- android - 如何链接 cpufeatures lib 以获取 native android 库?
- java - OnItemClickListener 不起作用,但 OnLongItemClickListener 在自定义 ListView 中起作用
- java - Android 文件转字符串
我遇到了奇怪的 CoreData 问题。
首先,在我的项目中我使用了很多框架,所以有很多问题来源 - 所以我考虑创建最小的项目来重复我的问题。你可以克隆 Test project on Github并逐步重复我的测试。
所以,问题:
NSManagedObject 绑定(bind)到它的 NSManagedObjectID,它不允许从 NSManagedObjectContext 中正确删除对象
因此,重现步骤:
在我的 AppDelegate 中,我像往常一样设置 CoreData 堆栈。 AppDelegate 具有 managedObjectContext
属性,可以访问该属性以获取主线程的 NSManagedObjectContext。应用程序的对象图由一个实体 Message
和 body
、from
、timestamp
属性组成。应用程序只有一个 viewController,只有一个方法 viewDidLoad。看起来是这样的:
- (void)viewDidLoad
{
[super viewDidLoad];
NSManagedObjectContext *context = ((AppDelegate*)[[UIApplication sharedApplication] delegate]).managedObjectContext;
NSEntityDescription *messageEntity = [NSEntityDescription entityForName:NSStringFromClass([Message class]) inManagedObjectContext:context];
// Here we create message object and fill it
Message *message = [[Message alloc] initWithEntity:messageEntity insertIntoManagedObjectContext:context];
message.body = @"Hello world!";
message.from = @"Petro Korienev";
NSDate *now = [NSDate date];
message.timestamp = now;
// Now imagine that we send message to some server. Server processes it, and sends back new timestamp which we should assign to message object.
// Because working with managed objects asynchronously is not safe, we save context, than we get it's objectId and refetch object in completion block
NSError *error;
[context save:&error];
if (error)
{
NSLog(@"Error saving");
return;
}
NSManagedObjectID *objectId = message.objectID;
// Now simulate server delay
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void)
{
// Refetch object
NSManagedObjectContext *context = ((AppDelegate*)[[UIApplication sharedApplication] delegate]).managedObjectContext;
Message *message = (Message*)[context objectWithID:objectId]; // here i suppose message to be nil because object is already deleted from context and context is already saved.
message.timestamp = [NSDate date]; // However, message is not nil. It's valid object with data fault. App crashes here with "Could not fulfill a fault"
NSError *error;
[context save:&error];
if (error)
{
NSLog(@"Error updating");
return;
}
});
// Accidentaly user deletes message before response from server is returned
delayInSeconds = 2.0;
popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void)
{
// Fetch desired managed object
NSManagedObjectContext *context = ((AppDelegate*)[[UIApplication sharedApplication] delegate]).managedObjectContext;
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"timestamp == %@", now];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([Message class])];
request.predicate = predicate;
NSError *error;
NSArray *results = [context executeFetchRequest:request error:&error];
if (error)
{
NSLog(@"Error fetching");
return;
}
Message *message = [results lastObject];
[context deleteObject:message];
[context save:&error];
if (error)
{
NSLog(@"Error deleting");
return;
}
});
}
好吧,我检测到应用程序崩溃,所以我尝试以其他方式获取消息
。我更改了获取代码:
...
// Now simulate server delay
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void)
{
// Refetch object
NSManagedObjectContext *context = ((AppDelegate*)[[UIApplication sharedApplication] delegate]).managedObjectContext;
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"timestamp == %@", now];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([Message class])];
request.predicate = predicate;
NSError *error;
NSArray *results = [context executeFetchRequest:request error:&error];
if (error)
{
NSLog(@"Error fetching in update");
return;
}
Message *message = [results lastObject];
NSLog(@"message %@", message);
message.timestamp = [NSDate date];
[context save:&error];
if (error)
{
NSLog(@"Error updating");
return;
}
});
...
哪个 NSLog 的 message (null)
所以,它显示:
1)消息实际上在数据库中不存在。无法获取。
2) 第一个版本的代码以某种方式在上下文中保留了已删除的 message
对象(可能是因为它的对象 ID 被保留用于 block 调用)。
但是为什么我可以通过它的 id 获取已删除的对象?我需要知道。
显然,首先,我将 objectId
更改为 __weak
。甚至在 block 之前就崩溃了:)
所以 CoreData 是在没有 ARC 的情况下构建的?嗯,很有趣。
嗯,我考虑过 copy
NSManagedObjectID。我得到了什么?
(lldb) po objectId
0xc28ed20 <x-coredata://8921D8F8-436C-4CBC-B4AB-118198988D88/Message/p4>
(lldb) po message.objectID
0xc28ed20 <x-coredata://8921D8F8-436C-4CBC-B4AB-118198988D88/Message/p4>
看看有什么问题? NSCopying
的-copy
的实现类似于NSManagedObjectID
上的return self
最后一次尝试是 __unsafe_unretained
获取 objectId。我们开始吧:
...
__unsafe_unretained NSManagedObjectID *objectId = message.objectID;
Class objectIdClass = [objectId class];
// Now simulate server delay
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void)
{
if (![NSObject safeObject:objectId isMemberOfClass:objectIdClass])
{
NSLog(@"Object for update already deleted");
return;
}
...
safeObject:isMemberOfClass: 实现:
#ifndef __has_feature
#define __has_feature(x) 0
#endif
#if __has_feature(objc_arc)
#error ARC must be disabled for this file! use -fno-objc-arc flag for compile this source
#endif
#import "NSObject+SafePointer.h"
@implementation NSObject (SafePointer)
+ (BOOL)safeObject:(id)object isMemberOfClass:(__unsafe_unretained Class)aClass
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-objc-isa-usage"
return ((NSUInteger*)object->isa == (NSUInteger*)aClass);
#pragma clang diagnostic pop
}
@end
简要说明 - 我们使用 __unsafe_unretained
变量,所以在 block 调用时它可以被释放,所以我们必须检查它是否是有效对象。所以我们在 block 之前保存它的 class
(它不是保留,它是分配)并通过 safePointer:isMemberOfClass:
在 block 中检查它所以现在,通过它的 managedObjectId 重新获取对象对我来说是UNTRUSTED模式。
有人对我在这种情况下应该怎么做有什么建议吗?要使用 __unsafe_unretained 并检查?但是,这个 managedObjectId 也可以被另一个代码保留,所以它会导致 could not fulfill
属性访问崩溃。或者每次都通过谓词获取对象? (如果对象由 3-4 个属性唯一定义怎么办?将它们全部保留用于完成 block ?)。异步处理托管对象的最佳模式是什么?
很抱歉长时间的研究,提前致谢。
附言您仍然可以重复我的步骤或使用 Test project 进行自己的实验。
最佳答案
不要使用 objectWithID:
。使用 existingObjectWithID:error:
。根据文档,the former :
... always returns an object. The data in the persistent store represented by objectID is assumed to exist—if it does not, the returned object throws an exception when you access any property (that is, when the fault is fired). The benefit of this behavior is that it allows you to create and use faults, then create the underlying data later or in a separate context.
这正是您所看到的。你得到一个对象,因为 Core Data 认为你必须想要一个具有该 ID 的对象,即使它没有。当您尝试存储到它时,如果没有在此期间创建一个实际对象,它不知道该做什么,您会得到异常。
existingObject...
将仅在对象存在时返回对象。
关于ios - 奇怪的 NSManagedObject 行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19061132/
我有这种来自 Google map 自动完成的奇怪行为(或者我可能错过了某事)...想法?奇怪的: 您在输入中输入某物,例如“伦敦” 您按 [ENTER] 你按下 [CLEAR] 按钮 你点击进入'输
这段代码与《Learning Java》(Oracle Press Books)一书中的代码完全一样,但它不起作用。我不明白为什么它不起作用,它应该起作用。我用 OpenJDK 和 Sun JDK 7
示例 1 中究竟发生了什么?这是如何解析的? # doesnt split on , [String]::Join(",",("aaaaa,aaaaa,aaaaa,aaaaa,aaaaa,aa
我需要获得方程式系统的解决方案。为此,我使用函数sgesv_()。 一切都很好,它使我感到解决方案的正确结果。 但是我得到一个奇怪的警告。 警告:从不兼容的指针类型传递'sgesv_'的参数3 我正在
我目前在制作动画时遇到一个奇怪的问题: [UIView animateWithDuration:3 delay:0
alert('works'); $(window).load(function () { alert('does not work'); });
我的代码: public class MyTest { public class StringSorter implements Comparator { public
我正在学习 JavaScript。尝试理解代码, function foo (){ var a = b = {name: 'Hai'}; document.write(a.name +''
这个问题不太可能帮助任何 future 的访问者;它只与一个小的地理区域、一个特定的时间点或一个非常狭窄的情况有关,这些情况并不普遍适用于互联网的全局受众。为了帮助使这个问题更广泛地适用,visit
这按预期工作: [dgorur@ted ~]$ env -i env [dgorur@ted ~]$ 这样做: [dgorur@ted ~]$ env -i which date which: no
struct BLA { int size_; int size()const{ return size_; } } int x; BLA b[ 2 ]; BLA * p = &b[
我有以下代码: #test img {vertical-align: middle;} div#test { border: 1px solid green; height: 150px; li
我想大多数使用过 C/C++ 的人都对预处理器的工作原理有一定的直觉(或多或少)。直到今天我也是这么认为的,但事实证明我的直觉是错误的。故事是这样的: 今天我尝试了一些东西,但我无法解释结果。首先考虑
我想为 TnSettings 做 mock,是的,如果通过以下方法编写代码,它就可以工作,问题是我们需要为每个案例编写 mock 代码,如果我们只 mock 一次然后执行多个案例,那么第二个将报告异常
我的项目中有以下两个结构 typedef volatile struct { unsigned char rx_buf[MAX_UART_BUF]; //Input buffer over U
Regex rx = new Regex(@"[+-]"); string[] substrings = rx.Split(expression); expression = "-9a3dcb
我的两个应用程序遇到了一个奇怪的问题。这是设置: 两个 tomcat/java 应用程序,在同一个网络中运行,连接到相同的 MS-SQL-Server。一个应用程序,恰好按顺序位于 DMZ 中可从互联
我目前正在与 Android Api Lvl 8 上的 OnLongClickListener 作斗争。 拿这段代码: this.webView.setOnLongClickListener(new
这个问题不太可能帮助任何 future 的访问者;它只与一个小的地理区域、一个特定的时间点或一个非常狭窄的情况相关,这些情况并不普遍适用于互联网的全局受众。为了帮助使这个问题更广泛地适用,visit
只是遇到了奇怪的事情。我有以下代码: -(void)ImageDownloadCompleat { [self performSelectorOnMainThread:@selector(up
我是一名优秀的程序员,十分优秀!