1

アセット (PDF、ビデオなど) を表示するためのアプリを構築しています。

まず、JSON をダウンロードし、それをコア データ オブジェクトに解析します <-- この部分は問題なく動作します。

これらのオブジェクトは、モデルで設定された関係を持つノードの階層セットです。各ノードは FILE または FOLDER のいずれかになります。<--問題ありません。

次に、そのオブジェクトに関連付けられたファイル (つまり PDF) をダウンロードするインスタンス メソッドを NSManagedObject サブクラスに組み込みます。次に、設定します

self.isAvailable = [NSNumber numberWithBool:YES];

一方、アセットのリストを表示する UITableView があります。最終的にはリアルタイムで更新されますが、今のところ問題が発生しているのはここです。最初にView Controllerに、表示するフォルダーを表す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*/];

私の2番目のアドバイス(問題をデバッグするため):ストアに保存するたびに設定を
印刷deletedObjectsして、どのコンテキストが障害を引き起こしたかを確認してください。

私の 3 番目のアドバイス:
変更をメイン コンテキストにマージします (私の推測では、MagicalRecords がそれを行ってくれます)。

deleteObject:注1:削除が暗示される場合があります(たとえば、カスケード/拒否モードで関係を設定して明示的に使用しないでください)

注2:マルチスレッド環境(AFAIK)でこの例外を回避することはできません。ただし、すべての保存をメインコンテキスト(を使用parentContext)を介して渡すか、常にプリフェッチされたオブジェクトを使用する(リレーションシップを直接使用しない)場合を除きます。

于 2013-04-25T05:04:58.380 に答える