【iOS】ストレージの空き容量を取得する方法 & 容量不足のデバッグ

# はじめに

この記事はand factory.inc Advent Calendar 2021 18日目の記事です。

昨日は @ichikawa7ss さんの「[アプリ・Webサービス開発者に必要なUIデザインの知識 - UIデザインの教科書をまとめた -](https://qiita.com/ichikawa7ss/items/5eb4d465aa550b346e0f)」でした。

こんにちは、and factory iOSエンジニアのykkdです！

さて今回は、

- iOS端末の空き容量を取得する方法
- デバッグ目的で実機、simulatorそれぞれにおいて、なるべく簡単に容量不足を再現する方法

について書きます。

# 端末の空き容量を取得する方法

## 実装

```swift
    let url = URL(fileURLWithPath: NSHomeDirectory())
    do {
        let result = try url.resourceValues(
            forKeys: [
                .volumeTotalCapacityKey,
                .volumeAvailableCapacityKey,
                .volumeAvailableCapacityForImportantUsageKey,
                .volumeAvailableCapacityForOpportunisticUsageKey,
            ]
        )
    } catch {
        print(error)
    }
```

`resourceValues`のKeyを指定することで、意図した値を取得することができます。

上記では、[Volume Capacity Values](https://developer.apple.com/documentation/foundation/urlresourcevalues) にリストアップされている4つのKeyを指定してみました。

## 各Keyから読み取れる値の説明

### volumeTotalCapacityKey

ストレージの総容量をバイト単位で表したもの

### volumeAvailableCapacityKey

ストレージの利用可能な容量をバイト単位で示すKey。

このKeyに紐つく値がストレージの空き容量になります。

後述の`volumeAvailableCapacityForImportantUsageKey`,

`volumeAvailableCapacityForOpportunisticUsageKey`とは異なり、

単純な空き容量を表現しているようです。

### volumeAvailableCapacityForImportantUsageKey

重要なリソースを保存する際の、ストレージの利用可能な容量をバイト単位で示すKey。

### volumeAvailableCapacityForOpportunisticUsageKey

必須ではないリソースを保存するための、ボリュームの利用可能な容量をバイト単位で示すKey。

## 十分な容量の有無を判定するには

ストレージの空き容量を表現するKeyが複数存在するため、どのKeyを用いるべきか迷うところですが、

[公式](https://developer.apple.com/documentation/foundation/nsurlresourcekey/checking_volume_storage_capacity)によると以下の例が記載されています。

- ユーザーのリクエストや、アプリが正常に機能するために必要なリソース（ユーザーがこれから見るビデオや、ゲームの次のレベルに必要なリソースなど）に基づいてデータを保存する場合は、NSURLVolumeAvailableCapacityForImportantUsageKeyを参照すると良い

- より予測的な方法でデータをダウンロードする場合（たとえば、ユーザーが最近見ていたテレビシリーズの新しいエピソードをダウンロードする場合）は、NSURLVolumeAvailableCapacityForOpportunisticUsageKeyを参照すると良い

ユーザーが欲しいコンテンツが取得可能か確認する場合は前者を確認し、
アプリ側の都合で可能な限り裏で事前に用意しておきたい場合は後者という感じでしょうか。

(補足)
今回調査した背景では前者のケースに当てはまるため、`NSURLVolumeAvailableCapacityForImportantUsageKey`を見ていくことにしました。

# 容量不足をなるべく簡単に再現する

外部サーバーからコンテンツをダウンロードしたり、データを保存する場合、
事前に端末に充分な空き容量があるかチェックすることで、無駄な通信を減らすことができ、親切な体験になると思います。
容量不足に陥った場合を再現しやすくするために、なるべく簡単に空き容量をコントロールする方法を調べてみました。

## 実機編

### 実装

```swift
DispatchQueue.main.async {
    // 1GB
    let d = Data.init(repeating: 1, count: 1073741824)
    do {
        let documentPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        let destinationPath = documentPath.appendingPathComponent("\(Date())")
        try d.write(to: destinationPath)
    } catch {
        print("error: \(error)")
    }
}
```

byte数を指定してDataを生成し、指定のディレクトリに書き込んでいます。

上記例では

- サイズ: 1GB
- ファイル名: データ生成時の日時

のデータファイルをドキュメントディレクトリに保存しています。

デバッグ用の実機端末の空き容量に合わせて、必要なサイズを指定することで、

データを書き込むのに時間がかかりますが、コードで空き容量を減らすことができます。

※指定サイズが大きいとクラッシュする場合があるため、for文で回した方が安全です。

## シミュレーター編

シミュレーターでも上記実装でDisk容量を圧迫することは可能ですが、

- PC全体のパフォーマンスに影響する
- PCのスペック次第では数百GBの書き込みを行うことになるため時間がかかる

という問題があります。

そのため今回はシミュレーターの保存可能容量を引き下げることにします。

シミュレーター用に軽量のDisk Imageを作成し、指定フォルダと差し替えることで、より簡単に容量不足を再現することが可能です。

### ディスクイメージを作成する

```bash
# hdiutil create -size [容量] -fs HFS+ [名前].dmg
hdiutil create -size 1m -fs HFS+ storage-for-simulator.dmg
```

`hdutil create`コマンドを実行することでDisk Imageを作成することができます。

サイズ1メガバイトの`storage-for-simulator.dmg`というディスクイメージが作成されていれば成功です。

### シミュレーターにディスクイメージをマウントする

```bash
hdiutil attach storage-for-simulator.dmg -mountpoint マウント先Directory
```

`hdutil attach` コマンドを実行する際に、ディスクイメージとマウント先を指定することで、シミュレーターの保存可能容量を変更することができます。

マウント先にはシミュレーター内部のフォルダを指定します。

実行中のシミュレーターのフォルダパスを取得する際は[Simulator Manager](https://github.com/tue-savvy/SimulatorManager)が便利です。


下記2点の添付画像は、後述するデモ用のプロジェクトをシミュレーターで立ち上げた際のスクリーンショットです。

利用しているPCの空き容量は数百GBあり（１枚目）、通常ですとシミュレーターの空き容量も同値になります。

しかし今回は、保存可能容量1MBのディスクイメージを参照しているため、
シミュレーターの残容量は811KBになっています(2枚目)。

![laptop_storage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605495/dc6995ae-44d2-1511-b86d-13b835e98989.png)

![sim_storage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605495/1eb4d19e-f9dc-9e0b-bcfe-28a328f470aa.png)


### マウントを解除する
なお、マウント状態を解除する際は

`diskutil detach [identifier]`コマンドを実行します。

identifierを確認するには

`diskutil list`コマンドで一覧を確認することができます。

# おわりに
ここまで読んでいただき有難うございました。

コマンドラインスクリプトを除き、
記事中で紹介したソースコードは、[Push](https://github.com/ykkd/CheckDeviceStorage)してあります。

Advent Calendar来週25日まで毎日更新されますので、お楽しみに！

# 参考

- [Checking Volume Storage Capacity](https://developer.apple.com/documentation/foundation/nsurlresourcekey/checking_volume_storage_capacity?language=swift)
- [URLResourceValues](https://developer.apple.com/documentation/foundation/urlresourcevalues)
- [No space left on device: Testing low storage scenarios](https://www.avanderlee.com/debugging/no-space-left-on-device/)
- [How does iOS and apps deal with low to no disk storage?](https://apple.stackexchange.com/questions/396309/how-does-ios-and-apps-deal-with-low-to-no-disk-storage)
- [Simulator Manager](https://github.com/tue-savvy/SimulatorManager)