1

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

1 回答 1

1

我不熟悉 MagicalRecords

当上下文持有无故障对象(对象存根)但数据库中的实际对象不存在(已删除或从未保存)时,会发生“无法完成故障”错误。

我的第一个建议:
如果您在多线程环境中工作,请尝试保存有故障的对象。
使用-existingObjectWithId:error:和获取请求:

[fetchRequest setReturnsObjectsAsFaults:NO];
[fetchRequest setIncludesPropertyValues:YES];
[fetchRequest setRelationshipKeyPathsForPrefetching:/*relationships you can afford to prefetch*/];

我的第二个建议(调试您的问题):在每次保存到商店之前
打印您deletedObjects设置的内容,以查看导致故障的上下文。

我的第三个建议:
合并对主要上下文的更改(我的猜测是 MagicalRecords 会为你做这件事)。

注意 1:可能会隐含删除(例如,您不会deleteObject:通过在级联/拒绝模式下设置关系来明确使用)

注意 2:在多线程环境(AFAIK)中,您无法避免此异常,除非您通过主上下文(使用parentContext)或始终使用预取对象(不直接使用关系)传递所有保存。

于 2013-04-25T05:04:58.380 回答