[iOS] ファイルの扱い ファイルの操作、入出力、リソースファイルなど

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

1 はじめに

今回は、iOSにおけるファイルの扱いについてまとめてみました。

具体的には以下の5項目です。

  • データの保存場所
  • シュミレーターのフォルダ
  • ファイル操作
  • ファイルの入出力
  • リソースファイルの読込み

2 データの保存場所

iOSでは、OS標準のアプリ以外は、各アプリのホームディレクトリ(/Application/<identifier>/)(サンドボックス)ごとに離隔され、ファイルの作成やダウンロードは、そのディレクトリ内に制限されています。

ホームディレクトリのパスは、NSHomeDirectory()で取得できます。

NSString *homeDirectory = NSHomeDirectory();
NSLog(homeDirectory);

シュミレーター(iPhone6s)で実行した場合のホームディレクトリのパスは、次のようになっていました。

@"/Users/username/Library/Developer/CoreSimulator/Devices/92293FBD-4EE1-4754-AADE-F69125D38F81/data/Containers/Data/Application/F58CA469-525B-4EEC-A273-A829CF44BE0A/Documents"

以下、ホームディレクトリの配下で定型的に使用されるパスとその取得方法です。

(1) /Document

文書など一般的に永続化したデータ

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
NSString *DocumentsDirPath = [paths objectAtIndex:0];

(2) /Library/Caches

再ダウンロードや、再作成可能なキャッシュを保存する

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask, YES);
NSString *cachesDirPath = [paths objectAtIndex:0];

(3) /tmp

一時ファイル アプリが停止している間に削除される可能性がある

NSString *tmpDirPath = NSTemporaryDirectory();

(4) /Library/Prefrences

NSUserDefaultsで利用されており、アプリケーションの設定を保存する

3 シュミレーターのフォルダ

シュミレーターのフォルダは、次のパスにあります。 /Library/Developer/CoreSimulator/Devices/{Identifier}/

001

各デバイスごとにフォルダに分かれていますが、どのデバイスが、どのフォルダかの対応は、 XcodeのメニューからWindow/Devicesと辿って表示されるダイアログで確認できます。

002

また、下記のコマンドでも、デバイスの列挙が可能です。

$ instruments -s devices
Known Devices:
2016-02-09 10:40:32.153 instruments[96693:1399178] WebKit Threading Violation - initial use of WebKit from a secondary thread.
hirauchi.shinichi [E564E4EE-375A-58A8-9CB8-6AADC0850C75]
iPhone6 (9.2.1) [58374b549ed26c12a3b75b053d84f66db1f71cb2]
Apple TV 1080p (9.1) [FF28257D-8591-43D0-BF4F-09B398347D18]
iPad 2 (9.2) [3029999A-F6BD-47A0-ADE0-598B25705C89]
iPad Air (9.2) [DABE28FA-3F47-485D-BEFA-76765EF5A604]
iPad Air 2 (9.2) [9401383C-A3D6-4A9C-9F5D-5C8EAFF4450E]
iPad Pro (9.2) [B3FDFE90-7A03-425C-85B7-682CBE6511D2]
iPad Retina (9.2) [5BCFDE8E-BF3C-498F-AD68-95E2E81CB37B]
iPhone 4s (9.2) [CBBC3141-95E7-498A-A09E-AE41F0CB8F4D]
iPhone 5 (9.2) [6E1D5670-F85C-40A7-8F03-6930448C3506]
iPhone 5s (9.2) [2EEECC28-AE45-467D-A3E3-4455E0D17E41]
iPhone 6 (9.2) [73B002C5-3921-43B5-8E51-A7B18A30AFF2]
iPhone 6 Plus (9.2) [9F4F166E-9665-4F86-9D18-918F14783809]
iPhone 6s (9.2) [92293FBD-4EE1-4754-AADE-F69125D38F81]
iPhone 6s (9.2) + Apple Watch - 38mm (2.1) [68BD997B-0DF3-41B7-9826-6094D3B21564]
iPhone 6s Plus (9.2) [CC711674-58FA-4973-B5A2-4D71A06D57D8]
iPhone 6s Plus (9.2) + Apple Watch - 42mm (2.1) [1B75849C-07C6-430D-9AFE-99F7D155CAC4]

iPhone6sのシュミレーターのパスは、先ほど、取得したホームディレクトリとも合致していることが確認できます。

4 ファイル操作

(1) ファイル操作の一例

ファイル操作の一例として、/Documentの下に、ダウンロードした画像ファイルを置くコードは、次のようになります。

// /Documentのパスの取得
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
// ファイル名の作成
NSString *filename = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"sample.png"];

// 画像データのダウンロード
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://cdn-ssl-devio-img.classmethod.jp/wp-content/uploads/2015/11/recruit_aws_solution_architect-960x400.png"]];
// ファイルの新規作成
result = [fm createFileAtPath:filename contents:data attributes:nil];

/Documentの実際のパスは、次のようになっていました。

@"/Users/username/Library/Developer/CoreSimulator/Devices/92293FBD-4EE1-4754-AADE-F69125D38F81/data/Containers/Data/Application/F58CA469-525B-4EEC-A273-A829CF44BE0A/Documents"

実行結果は次のとおりです。実際にフォルダ内にファイルが作成されていることを確認できます。

003

(2) ファイル・ディレクトリ操作

上記の他にも、ファイル・ディレクトリの操作には、次のようなものがあります。

<br />BOOL result;
NSError *error;
// NSFileManagerの取得
NSFileManager *fm = [NSFileManager defaultManager];

// 新規作成
result = [fm createFileAtPath:path contents:data attributes:nil];

// ファイルの存在確認(ディレクトリも同じ)
result = [fm fileExistsAtPath:path];

// ファイルの削除(ディレクトリも同じ)
result = [fm removeItemAtPath:path error:&error];

// ファイルの移動(ディレクトリも同じ)
result = [fm moveItemAtPath:src toPath:dst error:&error];

// ファイルのコピー(ディレクトリも同じ)
result = [fm copyItemAtPath: src toPath:dst error:&error];

// ディレクトリの作成
result = [fm createDirectoryAtPath:path withIntermediateDirectories:NO attributes:nil error:&error];

// ディレクトリかどうか
BOOL dir;
if ( [fm fileExistsAtPath:path isDirectory:&dir] ){
   if ( dir ){
        // path is directory.
   }
}

// ディレクトリ内のファイル列挙
for ( NSString *path in [fm contentsOfDirectoryAtPath:dst error:&error] ){
    // ....
}

// NSErrorがnilでなければエラーが発生している
NSLog(@"%@.",[err localizedDescription]);

5 ファイルの入出力

ファイルの入出力には、次の2種類の方法があります。

  • 既存のクラスの入出力メソッド
  • オブジェクトをアーカイブ

(1) 既存クラスの入出力メソッド

各NSクラスのwriteToFile:(保存)とwithContentsOfFile:(読込み)関数を使用するとファイルへの入出力が可能です。

比較的簡単に利用できそうなのは、次のようなものになると思います。

  • バイナリデータ NSData
  • 文字列データ NSString
  • 配列や辞書ファイル NSArray NSDictionary

以下は、NSStringオブジェクトをファイルに保存・読込みしているサンプルです。

// /Documentのパスの取得
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
// ファイル名の作成
NSString *filename = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"sample.txt"];
NSError *error;
// NSStringオブジェクト
NSString *sample = @"Hello, World";

// ファイルへの保存
BOOL result = [sample writeToFile:filename atomically:YES encoding:NSUTF8StringEncoding error:&error];
if(result){
    // ファルからの読込み
    NSString *content = [NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:&error];
    NSLog(content);
}

保存されたファイルも確認できます。

004

writeToFileatomicallyYESに設定しておくと、まず、別の名前でファイルを保存し、成功した場合に指定のファイル名に変更します。 既存のファイルを壊さないためにもYESにしておく方がいいでしょう。

(2) オブジェクトのアーカイブ

iOSでは、アーカイブのためにNSKeyedArchiverクラスと、アンアーカイブのためのNSKeyedUnarchiverクラスが用意されています。

// /Documentのパスの取得
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
// ファイル名の作成
NSString *filename = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"sample.db"];
NSError *error;
// NSArrayオブジェクト
NSArray *array = @[@"TEST1",@"TEST2"];

// アーカイブほ
BOOL result = [NSKeyedArchiver archiveRootObject:array toFile:filename];
if(result){
    // アンアーカイブ
    NSArray *content = [NSKeyedUnarchiver unarchiveObjectWithFile:filename];
    NSLog(content.description);
}

ログには、アンアーカイブしたNSArrayが表示されています。

006

また、保存されたファイルも確認できます。

005

(3) NSCodingプロトコル

独自のクラスをアーカイブに対応させるためには、NSCondingプロトコルを実装する必要があります。 先の例では、NSArrayが、既にこれを実装しているから問題なく利用できていたのです。

下記の例は、独自に作成したPersonクラスNSCondingプロトコルを実装した例です。 descriptionメソッドについては、ログ出力のために定義したので、必須ではありません。

#import <UIKit/UIKit.h>

@interface Person : NSObject<NSCoding>

@property NSString *name;
@property int age;

@end
#import "Person.h"

@implementation Person

- (id)initWithCoder:(NSCoder*)decoder {
    self = [super init];
    if(!self){
        return nil;
    }
    self.name = [decoder decodeObjectForKey:@"name"];
    self.age = [decoder decodeInt32ForKey:@"age"];
    return self;
}

- (void)encodeWithCoder:(NSCoder *)encoder {
    [encoder encodeObject:self.name forKey:@"name"];
    [encoder encodeInt32:self.age forKey:@"age"];
}

- (NSString*)description {
    return [NSString stringWithFormat:@"name=%@ age=%d",_name,_age];
}
@end

decode***ForKey:及び[encoder encode***:の形でプロトコルを実装していますが、***の部分を型にマッチさせる必要があります。

定義したPersonオブジェクトをアーカイブ・アンアーカイブしている例です。

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
// ファイル名の作成
NSString *filename = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"person.dat"];

// Personオブジェクトの生成
Person *person = [Person alloc];
person.name = @"Taro";
person.age = 20;

// アーカイブ
BOOL result = [NSKeyedArchiver archiveRootObject:person toFile:filename];
if(result){
    // アンアーカイブ
    Person *content = [NSKeyedUnarchiver unarchiveObjectWithFile:filename];
    NSLog(content.description); // name=Taro age=20

}

6 リソースファイルの読込み

NSBunbleは、アプリケーションのクラス、Xib、画像などの各種のリソースを管理しています。 リソースのパスが必要な場合は、このNSBunbleを使用します。

リソースファイルの格納位置は、/Applications/<identifier>/<ApplicationName>/app/になっています。

(1) pathForResource:を使用する方法

NSString *imageFile = [[NSBundle mainBundle] pathForResource:@"classmethod" ofType:@"png"];

(2) バンドルパスを基準に取得する方法

NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
NSString *imageFile = [bundlePath stringByAppendingPathComponent:@"classmethod,png"];

なお、ビルド時にリソースとして組み込まれるためには、Build PhasesCopy Bundle Resoucesに入っている必要があります。

007

7 最後に

今回は、iOSにおけるファイルの扱いについてまとめてみました。

物理的には深い階層に入っていますが、それぞれ、アクセス用のメソッドがありますので、アプリ側からは、そんなに意識する必要はないようです。