【发布时间】:2013-04-24 18:29:00
【问题描述】:
我正在构建一个用于显示资产(PDF、视频等)的应用程序。
首先下载一个 JSON 并将其解析为核心数据对象
这些对象是一组层次结构的节点,它们在我的模型中设置了关系。每个节点可以是 FILE 或 FOLDER。
然后我在我的 NSManagedObject 子类中内置了实例方法,这些方法将下载与该对象关联的文件(即 PDF)。然后设置
self.isAvailable = [NSNumber numberWithBool:YES];
同时,我有一个显示资产列表的 UITableView。最终它将实时更新,但现在这是我遇到问题的地方。我首先让视图控制器保留一个指向 CoreData 对象的指针,该对象代表它显示的文件夹,但似乎如果上下文得到更新,则指针变得无效(即故障失败)。
核心数据并没有非常具体地说明问题是什么,甚至问题发生在哪里,但是当我设置 isAvailable 时它似乎崩溃了
*** Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x1d5f9e50 <x-coredata://EDE66B97-B142-4E87-B445-76CAB965B676/Node/p58>''
我觉得问题在于我不应该只保留对核心数据对象的强引用作为我的模型。有没有更好(不那么崩溃)的方法来做到这一点?
我已经开始使用 NSFetchedResultsController 并改用 objectID,但我还没有得到任何结果。
- (void)populateChildren {
NSString * urlString = [NSString stringWithFormat:@"%@/%@", [CMPConstants hostURLString], self.SBUCode];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:self.downloadQueue completionHandler:^(NSURLResponse * response, NSData * data, NSError * error) {
if (data) {
NSDictionary * dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
[self processParsedObject:dict];
} else {
NSLog(@"%@", urlString);
}
}];
}
#pragma mark - Parse JSON into NSManagedObjects
- (void)processParsedObject:(id)object {
[self processParsedObject:object depth:0 parent:nil key:nil];
[[NSManagedObjectContext MR_contextForCurrentThread] MR_saveToPersistentStoreAndWait];
}
- (void)processParsedObject:(id)object depth:(int)depth parent:(Node *)parent key:(NSString*)key {
if ([object isKindOfClass:[NSDictionary class]]) {
if (depth == 0) {
// Grab content node if depth is 0;
object = [object valueForKey:@"content"];
}
// FIXME: Change this to a real primary key once we get one.
static NSString * primaryKey = @"name";
// Look for existing object
Node * testNode = [Node MR_findFirstByAttribute:primaryKey withValue:[object valueForKey:primaryKey]];
// Create new node pointer
Node * newNode;
if (testNode) {
// Update existing Node
newNode = testNode;
} else {
// Build a new Node Object
newNode = [Node MR_createEntity];
newNode.isAvailable = [NSNumber numberWithBool:NO];
}
// Get keys
NSArray * keys = @[@"name",
@"type",
@"index",
@"size",
@"videoDemensions",
@"videoId",
@"fileName",
@"fileType",
@"path"];
if ([[object valueForKey:@"type"] isEqual:[NSNull null]]) {
NSLog(@"%@", object);
}
// Loop to set value for keys.
for (NSString * key in keys) {
id value = [object valueForKey:key];
if (![[object valueForKey:key] isKindOfClass:[NSNull class]]) {
[newNode setValue:value forKey:key];
}
}
// Set calculated properties.
[newNode setSbu:[self SBUCode]];
[newNode setParent:parent];
[[NSManagedObjectContext MR_contextForCurrentThread] MR_saveToPersistentStoreAndWait];
// Sync local file.
if (!newNode.isAvailable.boolValue) {
[newNode aquireFileInQueue:self.downloadQueue];
}
// Process children
for(NSString * newKey in [object allKeys]) {
id child = [object objectForKey:newKey];
[self processParsedObject:child depth:depth+1 parent:newNode key:newKey];
}
} else if ([object isKindOfClass:[NSArray class]]) {
for(id child in object) {
[self processParsedObject:child depth:depth+1 parent:parent key:nil];
}
} else {
// Nothing here, this processes each field.
}
}
这个方法是Node类的一个实例方法。
- (void)aquireFileInQueue:(NSOperationQueue *)queue {
if ([self.type isEqualToString:@"VIDEO"]) {
// Videos are available, but not downloaded.
self.isAvailableValue = YES;
return;
}
if (self.path == nil || self.fileName == nil) {
NSLog(@"Path or Filename for %@ was nil", self.name);
return;
}
// Build the download URL !! MAKE SURE TO ADD PERCENT ESCAPES, this will protect against spaces in the file name
// Also make sure to slash-separate the path and fileName
NSURL * downloadURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@/%@",
[self.path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
[self.fileName stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
// Build the download request
NSURLRequest * downloadRequest = [NSURLRequest requestWithURL:downloadURL];
// FIXME: Authentication Code for JSON service
// Show network activity indicator
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
// Send Asynchronus Request for fileData
[NSURLConnection sendAsynchronousRequest:(NSURLRequest *)downloadRequest queue:queue completionHandler:^(NSURLResponse * response, NSData * data, NSError * error) {
// Hide network activity indicatior
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
// Cast URL Response to HTTPURLResponse
NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse *)response;
// If statusCode is 200 (successful) and data is not nil, save data
if (httpResponse.statusCode == 200 && data) {
[data writeToURL:[self fileURL] atomically:NO];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self setIsAvailable:[NSNumber numberWithBool:YES]];
}];
}
}];
}
- (void)prepareForDeletion {
// Remove file from Filesystem
[[NSFileManager defaultManager] removeItemAtURL:[self fileURL] error:nil];
}
- (NSURL *)fileURL {
// Return local file URL
return [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/%@", [Node applicationDocumentsDirectory], self.fileName]];
}
【问题讨论】:
-
上下文是如何更新的?您是否正在合并来自 did-save 通知的更改?还有什么?
-
听起来像是线程问题?您是否跨线程共享 NSManagedObject?如果是这样,那么根据 Apple 的说法,这是一个禁忌。您应该使用
objectID,就像您开始使用一样。 NSFetchedResultsController 是一个很好的方法,如果你想确保你的表视图在不同的上下文/线程中发生的变化保持最新。 -
NSManagedObject 具体类有一个名为 -(void)aquireFile 的方法。此方法将文件下载到单独的队列中,然后分派回 mainQueue 以设置 isAvailable。我的理解是这不应该导致任何队列或上下文问题。我会错过什么吗?
-
我添加了一些代码。对不起,它不像我最初写的那么优雅,因为我正在尝试追踪这个错误。
标签: ios objective-c cocoa-touch core-data magicalrecord