我正在构建一个用于显示资产(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]];
}