gpt4 book ai didi

ios - 奇怪的 NSManagedObject 行为

转载 作者:可可西里 更新时间:2023-11-01 05:55:08 24 4
gpt4 key购买 nike

我遇到了奇怪的 CoreData 问题。
首先,在我的项目中我使用了很多框架,所以有很多问题来源 - 所以我考虑创建最小的项目来重复我的问题。你可以克隆 Test project on Github并逐步重复我的测试。
所以,问题:
NSManagedObject 绑定(bind)到它的 NSManagedObjectID,它不允许从 NSManagedObjectContext 中正确删除对象
因此,重现步骤:
在我的 AppDelegate 中,我像往常一样设置 CoreData 堆栈。 AppDelegate 具有 managedObjectContext 属性,可以访问该属性以获取主线程的 NSManagedObjectContext。应用程序的对象图由一个实体 Messagebodyfromtimestamp 属性组成。应用程序只有一个 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 之前就崩溃了:)
enter image description here

所以 CoreData 是在没有 ARC 的情况下构建的?嗯,很有趣。
嗯,我考虑过 copy NSManagedObjectID。我得到了什么?
enter image description here

(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/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com