Ios 奇怪的对象行为
我遇到了奇怪的CoreData问题。Ios 奇怪的对象行为,ios,objective-c,core-data,Ios,Objective C,Core Data,我遇到了奇怪的CoreData问题。 首先,在我的项目中,我使用了很多框架,所以有很多问题的根源——所以我考虑创建一个重复我的问题的最小项目。您可以一步一步地克隆并重复我的测试。 因此,问题是: NSManagedObject与它的NSManagedObjectID绑定,它不允许从NSManagedObjectContext中正确删除对象 因此,复制步骤: 在AppDelegate中,我像往常一样设置CoreData堆栈。AppDelegate具有managedObjectContext属性,可
首先,在我的项目中,我使用了很多框架,所以有很多问题的根源——所以我考虑创建一个重复我的问题的最小项目。您可以一步一步地克隆并重复我的测试。
因此,问题是:
NSManagedObject与它的NSManagedObjectID绑定,它不允许从NSManagedObjectContext中正确删除对象
因此,复制步骤:
在AppDelegate中,我像往常一样设置CoreData堆栈。AppDelegate具有
managedObjectContext
属性,可以访问该属性以获取主线程的NSManagedObjectContext。应用程序的对象图由一个实体消息
和主体
,来自
,时间戳
属性组成。
应用程序只有一个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'ed消息(空)
因此,它显示:
1) 消息实际上在数据库中不存在。无法获取它。
2) 代码的第一个版本以某种方式在上下文中保持删除消息对象(可能是因为它的对象id被保留用于块调用)。
但为什么我可以通过它的id获得删除的对象呢?我需要知道。
显然,首先,我将objectId
更改为\uu-weak
。甚至在阻塞之前就崩溃了:)
那么CoreData是在没有ARC的情况下构建的?嗯,很有趣。
嗯,我考虑过复制NSManagedObjectID。我得到了什么?
安全对象: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
简要说明-我们使用\uuuuuuuuuuunretained
变量,因此在块调用时可以释放它,因此我们必须检查它是否为有效对象。所以我们在块之前保存它的class
(它不是retain,而是assign),并通过safePointer:isMemberOfClass:
所以现在,按managedObjectId重新蚀刻对象对我来说是不可信的模式。
有人对我在这种情况下该怎么办有什么建议吗?使用uu不安全u未修复并检查?但是,此managedObjectId也可以由另一个代码保留,因此它将导致无法实现对属性访问的崩溃。还是每次通过谓词获取对象?(如果对象是由3-4个属性唯一定义的,该怎么办?保留它们以用于完成块?)。异步处理托管对象的最佳模式是什么?
抱歉进行了长时间的研究,提前谢谢。
请注意,您仍然可以重复我的步骤或使用进行自己的实验。不要使用ID:
的对象。使用ID为的现有对象:错误:
。根据文件:
。。。始终返回一个对象。持久存储中的数据
假设objectID表示的对象存在,如果不存在,则
当您访问任何属性(该属性)时,返回的对象引发异常
是,当故障被触发时)。这种行为的好处是
允许您创建和使用故障,然后创建基础数据
稍后或在单独的上下文中
这正是你所看到的。您得到一个对象,因为核心数据认为您必须要一个具有该ID的对象,即使它没有ID。当您尝试存储到它时,在没有创建实际对象的情况下,它不知道该做什么,您会得到异常
existingObject…
仅当存在对象时才会返回该对象。效果很好!我几乎确定CoreData中应该有一个简单的解决方案,我只是不知道它=)谢谢,我的应用程序的下一次构建将使用existingObjectWithID:error:
=)我根据@Tommy的回答更新了repo以包含正确的实现
...
__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;
}
...
#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