Xamarin.iOS でのファイル システム アクセス

Xamarin.iOS と .NET 基本クラス ライブラリ (BCL)System.IO クラスを使用して、iOS ファイル システムにアクセスできます。 File クラスでは、ファイルを作成し、削除し、読み込むことができます。Directory クラスでは、ディレクトリの内容を作成し、削除し、列挙できます。 Stream サブクラスを使用することもできます。このサブクラスは、ファイル操作をより詳細に制御できます (圧縮やファイル内の位置検索など)。

iOS では、ファイル システムでアプリケーションが実行できることに制限があります。これは、アプリケーションのデータのセキュリティを維持し、悪性のアプリからユーザーを保護することが目的です。 これらの制限は、ファイル、基本設定、ネットワーク リソース、ハードウェアなどに対するアプリケーションのアクセスを制限する一連のルール、"アプリケーション サンドボックス" の一部です。アプリケーションは、自身のホーム ディレクトリ (インストールされている場所) 内のファイルの読み取りと書き込みに限定され、別のアプリケーションのファイルにはアクセスできません。

iOS にはまた、ファイル システム固有の機能もあります。特定のディレクトリでは、バックアップとアップグレードに関して特別な処理が必要です。アプリケーションは、ファイルを相互に共有したり、Files アプリ (iOS 11 以降) と共有したり、iTunes を介して共有したりすることもできます。

この記事では、iOS ファイル システムの機能と制限について説明します。また、Xamarin.iOS を使用していくつかのシンプルなファイル システム操作を実行する方法を示すサンプル アプリケーションが含まれています。

いくつかの単純なファイル システム操作を実行する iOS のサンプル

一般的なファイル アクセス

Xamarin.iOS では、iOS でのファイル システム操作に .NET System.IO クラスを使用できます。

次のコード スニペットでは、一般的なファイル操作を示しています。 この記事のサンプル アプリケーションの SampleCode.cs ファイルに、これらはすべて含まれています。

ディレクトリの使用

このコードでは、アプリケーションの実行可能ファイルの場所である現在のディレクトリ ("./" パラメーターで指定) 内のサブディレクトリを列挙します。 出力は、アプリケーションと共にデプロイされているすべてのファイルとフォルダーの一覧になります (デバッグ中はコンソール ウィンドウに表示されます)。

var directories = Directory.EnumerateDirectories("./");
foreach (var directory in directories) {
      Console.WriteLine(directory);
}

ファイルの読み取り

テキスト ファイルを読み取るために必要なコードは 1 行だけです。 次の例では、[アプリケーション出力] ウィンドウにテキスト ファイルの内容を表示します。

var text = File.ReadAllText("TestData/ReadMe.txt");
Console.WriteLine(text);

XML シリアル化

完全な System.Xml 名前空間の操作についてはこの記事の範囲外ですが、次のコード スニペットのように、StreamReader を使用して、ファイル システムから XML ドキュメントを簡単に逆シリアル化できます。

using (TextReader reader = new StreamReader("./TestData/test.xml")) {
      XmlSerializer serializer = new XmlSerializer(typeof(MyObject));
      var xml = (MyObject)serializer.Deserialize(reader);
}

詳細については、「System.Xml」と「シリアル化」のドキュメントを参照してください。 リンカーについての「Xamarin.iOS のドキュメント」を参照してください。多くの場合、シリアル化するクラスに [Preserve] 属性を追加する必要があります。

ファイルとディレクトリの作成

このサンプルでは、Environment クラスを使用して Documents フォルダーにアクセスする方法を示します。ここにはファイルとディレクトリを作成できます。

var documents =
 Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments);
var filename = Path.Combine (documents, "Write.txt");
File.WriteAllText(filename, "Write this text into a file");

ディレクトリの作成は同様のプロセスです。

var documents =
 Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments);
var directoryname = Path.Combine (documents, "NewDirectory");
Directory.CreateDirectory(directoryname);

詳細については、「システム.IO API リファレンス」を参照してください。

JSON のシリアル化

Json.NET は、Xamarin.iOS で動作し、NuGet で使用できる高パフォーマンスの JSON フレームワークです。 Visual Studio for Mac で [NuGet の追加] を使用して、NuGet パッケージをアプリケーション プロジェクトに追加します。

NuGet パッケージをアプリケーション プロジェクトに追加する

次に、シリアル化/逆シリアル化のデータ モデルとして機能するクラスを追加します (この場合 Account.cs)。

using System;
using System.Collections.Generic;
using Foundation; // for Preserve attribute, which helps serialization with Linking enabled

namespace FileSystem
{
    [Preserve]
    public class Account
    {
        public string Email { get; set; }
        public bool Active { get; set; }
        public DateTime CreatedDate { get; set; }
        public List<string> Roles { get; set; }

        public Account() {
        }
    }
}

最後に、Account クラスのインスタンスを作成し、それを json データにシリアル化し、ファイルに書き込みます。

// Create a new record
var account = new Account(){
    Email = "monkey@xamarin.com",
    Active = true,
    CreatedDate = new DateTime(2015, 5, 27, 0, 0, 0, DateTimeKind.Utc),
    Roles = new List<string> {"User", "Admin"}
};

// Serialize object
var json = JsonConvert.SerializeObject(account, Newtonsoft.Json.Formatting.Indented);

// Save to file
var documents = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments);
var filename = Path.Combine (documents, "account.json");
File.WriteAllText(filename, json);

.NET アプリケーションでの json データの操作の詳細については、Json.NET の ドキュメントを参照してください。

特別な注意事項

Xamarin.iOS と .NET のファイル操作は似ていますが、iOS と Xamarin.iOS はいくつかの重要な点で .NET とは異なります。

実行時にプロジェクト ファイルにアクセス可能にする

既定では、プロジェクトにファイルを追加する場合、最終的なアセンブリには含まれないため、アプリケーションではそのファイルを使用できません。 アセンブリにファイルを含めるには、コンテンツと呼ばれる特別なビルド アクションでマークする必要があります。

ファイルをインクルードとしてマークするには、ファイルを右クリックし、Visual Studio for Mac で [ビルド アクション] > [コンテンツ] を選択します。 ファイルの [プロパティ] シートで [ビルド アクション] を変更することもできます。

大文字小文字の区別

iOS ファイル システムでは大文字と小文字が区別されることを理解しておくことが重要です。 大文字と小文字が区別されるということは、ファイル名とディレクトリ名が正確に一致する必要があることを意味します。README.txtreadme.txt は異なるファイル名と見なされます。

これは、Windows ファイル システムに慣れている .NET 開発者にとって混乱を招く可能性があります。大文字と小文字は区別されませんFilesFILESfiles はすべて同じディレクトリを参照します。

警告

iOS シミュレーターでは、大文字と小文字は区別されません。 ファイル名の大文字と小文字の区別がファイル自体とコード内の参照とで異なる場合、コードはシミュレーターでは動作するかもしれませんが、実際のデバイスでは失敗します。 これが、iOS の開発時に、実際のデバイスを早期に頻繁にデプロイしてテストすることが重要な理由の 1 つです。

パスの区切り文字

iOS では、パス区切り記号としてスラッシュ '/' が使用されます (これは、バックスラッシュ '\' を使用する Windows とは異なります)。

この混乱を招く違いがあるため、特定のパス区切り記号をハードコーディングするのではなく、現在のプラットフォームに合わせて調整する System.IO.Path.Combine メソッドを使用することをお勧めします。 これは、コードを他のプラットフォームに移植できるようにする簡単な手順です。

アプリケーション サンドボックス

ファイル システム (およびネットワークやハードウェア機能などのその他のリソース) へのアプリケーションのアクセスは、セキュリティ上の理由から制限されています。 この制限は、"アプリケーション サンドボックス" と呼ばれます。 ファイル システムに関しては、アプリケーションではファイルとディレクトリの作成と削除がホーム ディレクトリ内に限定されます。

ホーム ディレクトリは、アプリケーションとそのすべてのデータが格納されている、ファイル システム内の一意の場所です。 アプリケーションのホーム ディレクトリの場所を選択 (または変更) することはできません。ただし、iOS と Xamarin.iOS には、その中にあるファイルとディレクトリを管理するためのプロパティとメソッドが用意されています。

アプリケーション バンドル

アプリケーション バンドルは、アプリケーションを含むフォルダーです。 ディレクトリ名に .app サフィックスを追加することで、他のフォルダーと区別されます。 アプリケーション バンドルには、実行可能ファイルと、プロジェクトに必要なすべてのコンテンツ (ファイル、イメージなど) が含まれています。

Mac OS でアプリケーション バンドルを参照すると、他のディレクトリで表示されるアイコンとは異なるアイコンが表示されます (また、.app サフィックスは非表示になっています)。ただし、オペレーティング システムで異なるように表示しているだけで、ディレクトリは通常のディレクトリです。

サンプル コードのアプリケーション バンドルを表示するには、Visual Studio for Mac 内のプロジェクトを右クリックし、[Finder で表示] を選択します。 次に、アプリケーション アイコンが表示されている bin/ ディレクトリに移動します (下のスクリーンショットのようにします)。

bin ディレクトリ内を移動して、このスクリーンショットのようなアプリケーション アイコンを見つけます

このアイコンを右クリックし、[パッケージ コンテンツの表示] を選択して、アプリケーション バンドル ディレクトリの内容を参照します。 次に示すように、通常のディレクトリの内容と同様に、内容が表示されます。

アプリ バンドルの内容

アプリケーション バンドルは、テスト中にシミュレーターまたはデバイスにインストールされるものであり、最終的には App Store に含めるために Apple に送信されるものです。

アプリケーション ディレクトリ

アプリケーションがデバイスにインストールされると、オペレーティング システムによってアプリケーションのホーム ディレクトリが作成され、アプリケーション ルート ディレクトリ内に多数の使用できるディレクトリが作成されます。 iOS 8 以降、ユーザーがアクセス可能なディレクトリはアプリケーション ルート内に存在しません。そのため、ユーザー ディレクトリからアプリケーション バンドルのパスを派生させる (またはその逆) ことはできません。

これらのディレクトリ、パスを決定する方法、およびその目的を次に示します。

 

ディレクトリ 説明
[ApplicationName].app/ iOS 7 以前では、これはアプリケーションの実行可能ファイルが格納されている ApplicationBundle ディレクトリです。 アプリで作成するディレクトリ構造は、このディレクトリに存在します (たとえば、Visual Studio for Mac プロジェクトで Resources としてマークしたイメージやその他のファイルの種類)。

アプリケーション バンドル内のコンテンツ ファイルにアクセスする必要がある場合は、このディレクトリへのパスを NSBundle.MainBundle.BundlePath プロパティを介して使用できます。
Documents/ このディレクトリを使用して、ユーザー ドキュメントとアプリケーション データ ファイルを格納します。

このディレクトリの内容は、iTunes ファイル共有を通じてユーザーが使用できるようになります (ただし、既定では無効になっています)。 Info.plist ファイルに UIFileSharingEnabled ブール型キーを追加すると、ユーザーがこれらのファイルにアクセスできるようになります。

アプリケーションがファイル共有をすぐに有効にしない場合でも、このディレクトリにユーザーから非表示にする必要があるファイル (データベース ファイルなど。ただし、共有する予定がある場合はこの限りでない) を配置しないようにする必要があります。 機密ファイルが非表示のままになっている限り、ファイル共有が将来のバージョンで有効になっても、これらのファイルは公開されません (また、iTunes によって移動、変更、削除される可能性があります)。

Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments) メソッドを使用して、アプリケーション用に Documents ディレクトリへのパスを取得できます。

このディレクトリの内容は iTunes によってバックアップされます。
Library/ Library ディレクトリは、データベースや他のアプリケーションによって生成されたファイルなど、ユーザーが直接作成しないファイルを格納するのに適した場所です。 このディレクトリの内容は、iTunes 経由でユーザーに公開されることはありません。

Library には独自のサブディレクトリを作成できます。ただし、システムによって作成されたディレクトリには、Preferences や Caches など、注意が必要なディレクトリが既にあります。

このディレクトリの内容 (Caches サブディレクトリを除く) は iTunes によってバックアップされます。 Library に作成したカスタム ディレクトリはバックアップされます。
Library/Preferences/ アプリケーション固有の基本設定ファイルは、このディレクトリに格納されます。 これらのファイルを直接作成しないでください。 代わりに NSUserDefaults クラスを使用します。

このディレクトリの内容は iTunes によってバックアップされます。
Library/Caches/ Caches ディレクトリは、アプリケーションの実行に役立つものの、簡単に再作成される可能性のあるデータ ファイルを格納するのに適した場所です。 アプリケーションでは、必要に応じてこれらのファイルを作成したり削除したり、必要であれば再作成できたりする必要があります。 iOS 5 では (ストレージが少ない状況では) これらのファイルが削除されることもありますが、アプリケーションの実行中に削除が行われることはありません。

このディレクトリの内容は iTunes によってバックアップされません。つまり、ユーザーがデバイスを復元した場合には存在することはなく、更新されたバージョンのアプリケーションがインストールされた後には存在しない可能性があります。

たとえば、アプリケーションがネットワークに接続できない場合に、Caches ディレクトリを使用してデータまたはファイルを格納し、優れたオフライン エクスペリエンスを提供できます。 アプリケーションでは、ネットワーク応答の待機中にこのデータをすばやく保存して取得できますが、バックアップする必要はなく、復元やバージョンの更新後に簡単に回復したり再作成したりできます。
tmp/ アプリケーションでは、短期間だけ必要な一時ファイルをこのディレクトリに格納できます。 領域を節約するため、不要になったファイルは削除するべきです。 オペレーティング システムでは、アプリケーションが実行されていないときに、このディレクトリからファイルを削除することもあります。

このディレクトリの内容は iTunes によってバックアップされません。

たとえば、tmp ディレクトリを使用して、ユーザーに表示するためにダウンロードされた一時ファイル (Twitter アバターや電子メールの添付ファイルなど) を保存できますが、表示が済むと削除される可能性があります (そして、将来必要になった場合は再ダウンロードされます)。

次のスクリーンショットは、Finder ウィンドウのディレクトリ構造を示しています。

次のスクリーンショットは、Finder ウィンドウのディレクトリ構造を示しています

プログラムによる他のディレクトリへのアクセス

ここまでのディレクトリとファイルの例では、Documents ディレクトリにアクセスしました。 別のディレクトリに書き込むには、次に示すように ".." 構文を使用してパスを作成する必要があります。

var documents = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments);
var library = Path.Combine (documents, "..", "Library");
var filename = Path.Combine (library, "WriteToLibrary.txt");
File.WriteAllText(filename, "Write this text into a file in Library");

ディレクトリの作成は同様です。

var documents = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments);
var library = Path.Combine (documents, "..", "Library");
var directoryname = Path.Combine (library, "NewLibraryDirectory");
Directory.CreateDirectory(directoryname);

Caches ディレクトリと tmp ディレクトリへのパスは、次のようにして構築できます。

var documents = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments);
var cache = Path.Combine (documents, "..", "Library", "Caches");
var tmp = Path.Combine (documents, "..", "tmp");

Files アプリでの作業の開始

iOS 11 では Files アプリが導入されました。これは、ユーザーが iCloud にある自分のファイルや、これをサポートする任意のアプリケーションによって保存されたファイルを表示したり操作したりできる iOS 用のファイル ブラウザーです。 ユーザーがアプリ内のファイルに直接アクセスできるようにするには、Info.plist ファイル内の新しいブール型キー LSSupportsOpeningDocumentsInPlace を作成し、true に設定します。次に例を示します。

Info.plist で LSSupportsOpeningDocumentsInPlace を設定する

これで、アプリの Documents ディレクトリが Files アプリで参照できるようになります。 Files アプリで On My iPhone に移動すると、共有ファイルのある各アプリが表示されます。 次のスクリーンショットは、サンプル アプリがどのようになるかを示しています。

iOS 11 Files アプリ iPhone ファイルを参照する サンプル アプリ ファイル

iTunes を経由したユーザーとのファイルの共有

ユーザーは、Info.plist を編集し、Source ビューで iTunes 共有をサポートするアプリケーション (UIFileSharingEnabled) のエントリを作成することで、アプリケーションの Documents ディレクトリ内のファイルにアクセスできます。次に例を示します。

アプリケーションの追加では、iTunes 共有プロパティがサポートされます

これらのファイルは、デバイスが接続されていて、ユーザーが Apps タブを選択すると、iTunesでアクセスできます。たとえば、次のスクリーンショットでは、iTunes 経由で共有されている選択したアプリ内のファイルを示しています。

このスクリーンショットは、iTunes 経由で共有されている選択したアプリ内のファイルを示しています

ユーザーは、iTunes 経由で、このディレクトリ内の最上位項目にのみアクセスできます。 サブディレクトリの内容は表示できません (ただし、自身のコンピューターにコピーしたり、削除したりすることはできます)。 たとえば、GoodReader を使用すると、PDF ファイルと EPUB ファイルをアプリケーションと共有できます。そのため、ユーザーはそれらを自分の iOS デバイスで読み取ることができます。

Documents フォルダーの内容をユーザーが変更することは、問題が発生する可能性があるため、注意が必要です。 アプリケーションでは、この点を考慮して、Documents フォルダーを破壊するような更新に対する回復性を備える必要があります。

この記事のサンプル コードでは、ファイルとフォルダーの両方を (SampleCode.cs の) Documents フォルダーに作成し、Info.plist ファイルで、ファイル共有ができるようにします。 次のスクリーンショットでは、iTunes でどのように表示されるかを示しています。

このスクリーンショットは、iTunes でファイルがどのように表示されるかを示しています

アプリケーションのアイコンを設定する方法についての情報、および作成する任意のカスタム ドキュメントについては、「イメージの処理」の記事を参照してください。

UIFileSharingEnabled キーが false の場合、または存在しない場合、ファイル共有は既定で無効であり、ユーザーは Documents ディレクトリの操作ができなくなります。

バックアップと復元

デバイスが iTunes によってバックアップされると、アプリケーションのホーム ディレクトリに作成されたすべてのディレクトリが保存されますが、次のディレクトリは例外となります。

  • [ApplicationName].app – このディレクトリには書き込まないでください。このディレクトリは署名されているため、インストール後は変更しない状態にしておかなければなりません。 コードからアクセスするリソースが含まれている場合がありますが、バックアップの必要はありません。これらはアプリを再ダウンロードすることで復元されます。
  • Library/Caches – キャッシュ ディレクトリは、バックアップする必要のない作業ファイルを対象としています。
  • tmp – このディレクトリは、作成されたうえで不要になれば削除される一時ファイル、または、iOS が領域が必要と判断したときに iOS によって削除されるファイルに使用されます。

データが大量になると、バックアップには時間がかかる可能性があります。 特定のドキュメントやデータをバックアップする必要があると判断される場合は、アプリケーションでは Documents フォルダーと Library フォルダーを使用する必要があります。 ネットワークから簡単に取得できる一時的なデータやファイルについては、Caches ディレクトリまたは tmp ディレクトリを使用します。

Note

iOS では、デバイスのディスク領域が非常に少なくなった場合には、ファイル システムを "クリーン" します。 このプロセスでは、現在実行されていないアプリケーションのe Library/Caches フォルダーと tmp フォルダーからすべてのファイルが削除されます。

iOS 5 iCloud バックアップの制限への準拠

Note

このポリシーは最初に iOS 5 で導入されました (ずいぶん前のことのようです) が、ガイダンスは現在もアプリに関わっています。

Apple は iOS 5 で iCloud Backup 機能を導入しました。 iCloud Backup を有効にすると、アプリケーションのホーム ディレクトリ内のすべてのファイル (ただし、アプリ バンドル、Cachestmp などの、通常はバックアップされないディレクトリを除く) が iCloud サーバーにバックアップされます。 この機能は、デバイスの紛失、盗難、破損などの場合に備えて、完全なバックアップをユーザーに提供します。

iCloud では各ユーザーに 5 Gb の空き領域を提供するのみであり、帯域幅を不必要に使用しないようにするため、Apple ではアプリケーションが重要なユーザー生成データのみをバックアップすることを想定しています。 iOS データ ストレージ ガイドラインに準拠するには、次の項目に従ってバックアップされるデータの量を制限する必要があります。

  • ユーザーが生成するデータ、または他の方法では再作成できないデータのみを Documents ディレクトリ (バックアップ対象) に格納する。
  • 簡単に再作成や再ダウンロードできるその他のデータは Library/Cachestmp (バックアップ対象外でありクリーニング対象となる可能性がある) に格納する。
  • Library/Caches フォルダーまたは tmp フォルダーへの格納が適していると考えられるがクリーニング対象とはしないファイルがある場合は、他の場所 (たとえば Library/YourData) に格納し、"バックアップしない" 属性を適用して、ファイルが iCloud バックアップの帯域幅と記憶域を使わないようにする。 ただし、このデータはデバイスの領域を使うため、慎重に管理し、可能なタイミングて削除するべきである。

"バックアップしない" 属性は NSFileManager クラスを使用して設定されます。 クラスが using Foundation であり、次のように SetSkipBackupAttribute 呼び出していることを確認します。

var documents = Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments);
var filename = Path.Combine (documents, "LocalOnly.txt");
File.WriteAllText(filename, "This file will never get backed-up. It would need to be re-created after a restore or re-install");
NSFileManager.SetSkipBackupAttribute (filename, true); // backup will be skipped for this file

SetSkipBackupAttributetrue の場合、格納されているディレクトリに関係なく (Documents ディレクトリであっても)、ファイルはバックアップされません。 GetSkipBackupAttribute メソッドを使用して属性のクエリを実行できます。続いて、SetSkipBackupAttribute メソッドを呼び出して属性を false にリセットできます。次に例を示します。

NSFileManager.SetSkipBackupAttribute (filename, false); // file will be backed-up

iOS アプリとアプリ拡張機能の間でのデータ共有

アプリ拡張機能はホスト アプリケーションの一部として実行される (自身を含んでいるアプリではない) ため、データの共有は自動的には含まれません。追加の作業が必要です。 アプリ グループは、さまざまなアプリがデータを共有できるようにするために iOS で使用されているメカニズムです。 アプリケーションが適切なエンタイトルメントとプロビジョニングで適切に構成されている場合は、通常の iOS サンドボックスの外部にある共有ディレクトリにアクセスできます。

アプリ グループの構成

共有の場所は、アプリ グループを使用して構成します。アプリ グループは、iOS Dev Center の [証明書、識別子 & プロファイル] セクションで構成します。 また、この値は、各プロジェクトの各 Entitlements.plist で参照する必要があります。

アプリ グループの作成と構成については、「アプリ グループ機能」ガイドを参照してください。

Files

iOS アプリと拡張機能も、共通のファイル パスを使用してファイルを共有できます (適切なエンタイトルメントとプロビジョニングで適切に構成されている場合)。

var FileManager = new NSFileManager ();
var appGroupContainer =FileManager.GetContainerUrl ("group.com.xamarin.WatchSettings");
var appGroupContainerPath = appGroupContainer.Path

Console.WriteLine ("Group Path: " + appGroupContainerPath);

// use the path to create and update files
...

重要

返されるグループ パスが null の場合は、エンタイトルメントとプロビジョニング プロファイルの構成をチェックし、適切であることを確認します。

アプリケーション バージョンの更新

アプリケーションの新しいバージョンがダウンロードされると、iOS によって新しいホーム ディレクトリが作成され、新しいアプリケーション バンドルがそこに格納されます。iOS では、次のフォルダーを以前のバージョンのアプリケーション バンドルから新しいホーム ディレクトリに移動します。

  • ドキュメント
  • Library

他のディレクトリもコピーされ、新しいホーム ディレクトリの下に配置される場合がありますが、コピーされるという保証はありません。そのため、アプリケーションではこのシステム動作に依存するべきではありません。

まとめ

この記事では、Xamarin.iOS でのファイル システム操作が他の .NET アプリケーションと類似していることを示しました。 また、アプリケーション サンドボックスを導入し、それによって起こるセキュリティへの影響を調べました。 次に、アプリケーション バンドルの概念について説明しました。 最後に、アプリケーションで使用できる特化したディレクトリを列挙し、アプリケーションのアップグレードとバックアップ中のそれらの役割について説明しました。