アセット (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]];
}