11

私はメディアDBアプリケーションに取り組んでいます。データストレージを備えたカスタムモデルがあり、それをCoreDataに書き換えることを考えています。私が特に興味を持っているユースケースの1つは、映画の保存です。ムービーファイルをDBに保存しますが、メディアフレームワークはファイルからのみムービーを読み取ることができます(データは読み取ることができません)。

Core Dataは、「外部バイナリストレージ」と呼ばれる便利な機能を提供します。この機能では、エンティティデータはDBではなく、外部ファイルに保存されます。これは、CoreDataAPIユーザーには透過的です。私の質問は、外部ファイルへのパスを取得して、Core Dataを使用してムービーを保存し、Core Data外部ファイルから簡単にロードできるようにすることはできますか?

4

2 に答える 2

8

はい、外部ストレージに保存されているファイルにアクセスできます。少しハッキングが必要で、Apple の App Store と完全に一致しているわけではないかもしれませんが、かなり簡単に実行できます。

Core Data Editor で「データ」プロパティが「外部ストレージを許可」に設定されている NSManagedObject サブクラス「メディア」があるとします。

//  Media.h
//  Examples
//
//  Created by Garrett Shearer on 11/21/12.
//  Copyright (c) 2012 Garrett Shearer. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>


@interface CRMMedia : NSManagedObject

@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSData * data;

@end

そして便利な NSString カテゴリ:

//  NSString+Parse.m
//  Examples
//
//  Created by Garrett Shearer on 11/21/12.
//  Copyright (c) 2012 Garrett Shearer. All rights reserved.
//

#import "NSString+Parse.h"

@implementation NSString (Parse)

- (NSString*)returnBetweenString:(NSString *)inString1
                       andString:(NSString *)inString2
{
    NSRange substringRange = [self rangeBetweenString:inString1
                                            andString:inString2];
    logger(@"substringRange: (%d, %d)",substringRange.location,substringRange.length);
    logger(@"string (self): %@",self);
    return [self substringWithRange:substringRange];
}


/*
 Return the range of a substring, searching between a starting and ending delimeters
 Original Source: <http://cocoa.karelia.com/Foundation_Categories/NSString/Return_the_range_of.m>
 (See copyright notice at <http://cocoa.karelia.com>)
 */

/*" Find a string between the two given strings with the default options; the delimeter strings are not included in the result.
 "*/

- (NSRange) rangeBetweenString:(NSString *)inString1 andString:(NSString *)inString2
{
    return [self rangeBetweenString:inString1 andString:inString2 options:0];
}

/*" Find a string between the two given strings with the given options inMask; the delimeter strings are not included in the result.  The inMask parameter is the same as is passed to [NSString rangeOfString:options:range:].
 "*/

- (NSRange) rangeBetweenString:(NSString *)inString1 andString:(NSString *)inString2
                       options:(unsigned)inMask
{
    return [self rangeBetweenString:inString1 andString:inString2
                            options:inMask
                              range:NSMakeRange(0,[self length])];
}

/*" Find a string between the two given strings with the given options inMask and the given substring range inSearchRange; the delimeter strings are not included in the result.  The inMask parameter is the same as is passed to [NSString rangeOfString:options:range:].
 "*/

- (NSRange) rangeBetweenString:(NSString *)inString1 andString:(NSString *)inString2
                       options:(unsigned)inMask range:(NSRange)inSearchRange
{
    NSRange result;
    unsigned int foundLocation = inSearchRange.location;    // if no start string, start here
    NSRange stringEnd = NSMakeRange(NSMaxRange(inSearchRange),0); // if no end string, end here
    NSRange endSearchRange;
    if (nil != inString1)
    {
        // Find the range of the list start
        NSRange stringStart = [self rangeOfString:inString1 options:inMask range:inSearchRange];
        if (NSNotFound == stringStart.location)
        {
            return stringStart; // not found
        }
        foundLocation = NSMaxRange(stringStart);
    }
    endSearchRange = NSMakeRange( foundLocation, NSMaxRange(inSearchRange) - foundLocation );
    if (nil != inString2)
    {
        stringEnd = [self rangeOfString:inString2 options:inMask range:endSearchRange];
        if (NSNotFound == stringEnd.location)
        {
            return stringEnd;   // not found
        }
    }
    result = NSMakeRange( foundLocation, stringEnd.location - foundLocation );
    return result;
}


@end

さあ、魔法の時間です.... [data description] 文字列からファイル名を解析する Category メソッドを作成します。Media サブクラスのインスタンスを操作する場合、「データ」は実際には「外部ストレージ参照」であり、NSData オブジェクトではありません。実際のデータのファイル名は、説明文字列に格納されます。

//  Media+ExternalData.m
//  Examples
//
//  Created by Garrett Shearer on 11/21/12.
//  Copyright (c) 2012 Garrett Shearer. All rights reserved.
//

#import "Media+ExternalData.h"
#import "NSString+Parse.h"

@implementation Media (ExternalData)

- (NSString*)filePathString
{
    // Parse out the filename
    NSString *description = [self.data description];
    NSString *filename = [description returnBetweenString:@"path = " andString:@" ;"];
    // Determine the name of the store
    NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator;
    NSPersistentStore *ps = [psc.persistentStores objectAtIndex:0];
    NSURL *storeURL = [psc URLForPersistentStore:ps];
    NSString *storeNameWithExt = [storeURL lastPathComponent];
    NSString *storeName = [storeNameWithExt stringByDeletingPathExtension];
    // Generate path to the 'external data' directory
    NSString *documentsPath = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
                                                                       inDomains:NSUserDomainMask] lastObject] path];
    NSString *pathComponentToExternalStorage = [NSString stringWithFormat:@".%@_SUPPORT/_EXTERNAL_DATA",storeName];
    NSString *pathToExternalStorage = [documentsPath stringByAppendingPathComponent:pathComponentToExternalStorage];
    // Generate path to the media file
    NSString *pathToMedia = [pathToExternalStorage stringByAppendingPathComponent:filename];
    logger(@"pathToMedia: %@",pathToMedia);
    return pathToMedia;
}

- (NSURL*)filePathUrl
{
    NSURL *urlToMedia = [NSURL fileURLWithPath:[self filePathString]];
    return urlToMedia;
}

@end

これで、ファイルへの NSString パスと NSURL パスができました。喜び!!!

注意すべきことは、この方法で映画をロードする際に問題が発生したことです...しかし、回避策も思いつきました。MPMoviePlayer はこのディレクトリ内のファイルにアクセスしないようです。解決策は、一時的にドキュメント ディレクトリにファイルをコピーして再生することでした。次に、ビューをアンロードするときに一時コピーを削除します。

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self copyTmpFile];
}

- (void)viewDidUnload
{
    logger(@"viewDidUnload");
    [_moviePlayer stop];
    [_moviePlayer.view removeFromSuperview];
    [self cleanupTmpFile];
    [super viewDidUnload];
}

- (NSString*)tmpFilePath
{
    NSString *documentsPath = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
                                                                       inDomains:NSUserDomainMask] lastObject] path];
    NSString *tmpFilePath = [documentsPath stringByAppendingPathComponent:@"temp_video.m4v"];
    return tmpFilePath;
}

- (void)copyTmpFile
{
    NSString *tmpFilePath = [self tmpFilePath];
    NSFileManager *mgr = [NSFileManager defaultManager];
    NSError *err = nil;
    if([mgr fileExistsAtPath:tmpFilePath])
    {
        [mgr removeItemAtPath:tmpFilePath error:nil];
    }

    [mgr copyItemAtPath:_media.filePathString toPath:tmpFilePath error:&err];
    if(err)
    {
        logger(@"error: %@",err.description);
    }
}

- (void)cleanupTmpFile
{
    NSString *tmpFilePath = [self tmpFilePath];
    NSFileManager *mgr = [NSFileManager defaultManager];
    if([mgr fileExistsAtPath:tmpFilePath])
    {
        [mgr removeItemAtPath:tmpFilePath error:nil];
    }
}

幸運を!

于 2012-11-21T16:45:53.463 に答える
3

データに直接(つまり、CoreDataを介してではなく)アクセスする場合は、各ファイルに名前としてUUIDを付け、その名前をデータベースに保存し、実際のファイルを自分で保存することをお勧めします。

UIManagedDocumentを使用する場合、いくつかのオプションがあります。UIManagedDocumentは実際にはファイルパッケージであるため、上記の手法を使用すると、データベースと一緒にファイルを保存できます。

または、UIManagedDocumentからサブクラス化し、「追加」ファイルの読み取り/書き込みを処理するメソッドをオーバーライドすることもできます。これにより、ファイル自体にアクセスできるようになります。CoreDataが自動的に作成するファイルへの実際のURLを取得するなど、必要なことを行うためにそこにフックすることができます。

- (id)additionalContentForURL:(NSURL *)absoluteURL error:(NSError **)error
- (BOOL)readAdditionalContentFromURL:(NSURL *)absoluteURL error:(NSError **)error
- (BOOL)writeAdditionalContent:(id)content toURL:(NSURL *)absoluteURL originalContentsURL:(NSURL *)absoluteOriginalContentsURL error:(NSError **)error
于 2012-04-19T13:03:38.447 に答える