能登 要

札幌在住のiOSアプリ開発者。SwiftUI により分割されたデバイス間を縦横にやりとりできる考え方に転換しています。

iOSアプリ開発者。2009年のiPhoneアプリ開発開始時期から活動。開発言語のアップデートの中でSwiftUIおよび周辺技術に着目中。

Apple Archive - Apple 謹製のアーカイブフレームワーク

端的に言うと

Apple謹製のアーカイブフレームワークApple Archiveの説明。WWDC2020のセッションにもならないほどのシンプルさ。iOS14以降対応で2020年に話題となったAppleSiliconに最適化されている。Appleの殿堂(Apple Archive)というわけではない。

追記 2020年12月23日

Xcode12.3 でAppleArchive のベンチマークを取るテストアプリを作成、ビルドしたところiPhone SimulatorではApple Archiveフレームワークの定義が存在しない(importはできるがフレームワーク内のクラスにアクセスできない)扱いとなった。

Intel版iPhone Simulatorだけの話かと思われたが、M1(arm64)版のiPhone SimulatorでもApple Archiveフレームワークの定義が存在しないのでアーキテクチャに依存した問題ではなくiPhone Simulatorに対するApple Archiveの扱いが特殊なものと考えられる。

Developer Forumsでは問い合わせはあるが解決にまでは至っていないまま議論が収束していた。 Apple Archive: Cannot find 'Archiv… | Apple Developer Forums

1) Apple謹製のアーカイブフレームワーク

iOS14からApple謹製のアーカイブ機能フレームワークApple Archiveが利用できるようになっている。

1-1) アーカイブ機能小史

アーカイブ機能はコンピューターの歴史上、a. 1つ以上のデータの集合を1まとめとし、b. データサイズを縮小させる役割を担っており、データのやり取りにおいては送り手から送信先へのデータ送信量を減らす上で今でも欠かせない機能となっている(データ総量が減ると時間も短縮する)。

1-2) アーカイブ種類

LZ4, LZFSE, LZMA, zlib を選択できる。

アーカイブ種類の中でもオープンソースとして公開されたLZFSE形式が最適なパフォーマンスを出せるようにマルチスレッド対応、Arm64への最適化、APFS対応とAppleが出しているデバイス向けにチューニングされている点がドキュメント上から見て撮ることができる。

実際サンプルコードは単一ファイル、階層化されたフォルダ、文字列の圧縮の3パターンのいずれもLZFSEを採用している。

アーカイブ用機能としてもまとまっていて、ファイルシステムへの入出力、オンメモリへの展開、ストリーミングサポート、階層化されたフォルダ内のignore処理など、従来のアーカイブ用ライブラリであればC/C++で書かれたオリジナルライブラリを直接使用するかミドルウェアを導入を考えるところでApple謹製のフレームワークが選択肢に入ったのは大きい。

1-2) マルチプラットフォーム

Apple Archiveはマルチプラットフォームなので、iOS/iPadOS/macOS(Big Sur 以降)で利用可能となる。

2) 開発への影響は?

アーカイブを使った機能実装のお手軽感が増す。

iPhone/iPad/(Apple Silicon以後の)macOS でエネルギー効率と圧縮展開速度が速い圧縮形式(lzfse)をApple謹製のフレームワークで手軽に使える事で適用できる用途が増えている。たとえばローカルファイルの各種データのスナップショットを取りバックアップとして使用する、Apple機器同士のデータ転送の際に圧縮した形式で送信、転送時間を短縮するなどが考えられる。

個人的にはクリエイター向けツールのファイル形式にDocument Package(コンテキストメニューからパッケージの内容を表示 が選べるファイル)のアーカイブされた形式をサポートするのにいいかもしれない。Document PackageはOS上でファイルのように見せかけているフォルダであり、Apple製デバイスのFile Syste,およびiCloud以外に配置するとただのフォルダになってしまう。ファイルとフォルダでは使い勝手が変わるのでDocument Packageのファイルを圧縮した形式を提供、編集時はテンポラリフォルダに展開、保存後に圧縮し元ファイルに書き込むような用途が可能となる。

3) アーカイブ機能の構造

ArchiveByteStreamから入出力ストリーム、compless/decomplessストリームをそれぞれ生成、ArchiveByteStream.process() を呼び出して処理を実行する。File System上の階層ディレクトリの圧縮時にignore処理を加えることも可能。

処理実行時に各種ストリームの組み合わせが想定されるが、compless/decomplessストリームは、常にアーカイブしたいorされている対象を必要とすると整理しやすい。

圧縮時 complessストリームを生成する。complessストリームはアーカイブする対象を必要としている。

展開時 decomplessストリームを生成する。decomplessストリームはアーカイブされた対象を必要としている。

アーカイブしたいorされている対象は入出力ストリームのインスタンス生成するタイミングでFile Systemだったり連続したメモリ空間だったり、inoutストリームなどを指定できる組み合わせの自由度がある。

入出力ストリームの先にcomplessストリームを作らなければならないのは回り道をしているように見えるが、圧縮結果を複数の入出力ストリームに流すような用途を想定しているかもしれない。

サンプルのアーカイブしたいorされている対象がFile Systemベースなのは、モバイルアプリだとデータ送信時に遮断される可能性があるため送信途中の状態をレジュームする用途としてはテンポラリにアーカイブ作業対象があることを一般的としてサンプルを提示しているところがある(実際のところは不明。なにしろApple Archiveの用途に関する資料はApple公式含め乏しい)。

単体のファイルを圧縮/展開する際にはArchiveByteStream のクラスメソッドを呼び出すだけで圧縮/展開機能を利用できる。

Figure.1 基本の依存関係 Basic Dependency Relationship

Figure.2 圧縮用途での依存関係 Compressing Dependency Relationship

Figure.3 展開用途での依存関係 Decompressed Dependency Relationship

3-1) ArchiveStream

File System上の単体のファイルを扱う際は意識しないがcomplessストリームへの書き込みを行うArchiveStreamが存在している。ArchiveStreamはヘッダー情報とボディ情報を書き込みことができる。

ヘッダー/ボディそれぞれにキー値経由でアクセスする。キー値そのものはArchiveHeader.FieldKeySet で指定される。

ArchiveByteStream は1対1の圧縮/展開以外に使用される。具体的にはFileSystem 上の改装フォルダを圧縮する。文字列などを書き込む際に使用する。

guard let encodeStream = ArchiveStream.encodeStream(writingTo: compressStream) else {
    return
}
defer {
    try? encodeStream.close()
}

ArchiveStreamには以下の機能が用意されている。

  1. Blobデータの書き込み/読み込み
  2. ヘッダーの取得/ヘッダーの設定
  3. File Systemの階層化フォルダの書き込み
  4. ストリームへの処理をキャンセルする
  5. ストリームへの処理をクローズする

3のFile Systemの階層化フォルダの書き込みはignore指定も可能なのでフォルダ中の余計なファイルをアーカイブせずに済む。

ArchiveStreamの基本機能はArchiveStreamProtocolに定義されている。

ArchiveStream Class Diagram

ArchiveByteStreamとArchiveStreamと似通った名称であるがArchiveByteStreamはオフセットを指定したシーケンシャルなアクセスも可能なのに対してArchiveStreamがストリーム入出力のメソッドをサポートしている。

ArchiveByteStreamがシーケンシャルアクセス可能、ArchiveStreamがストリームのみサポートしている特徴がそのまま用途として組み合わせに直結している。

ArchiveByteStreamはファイルやメモリイメージをソースもしくはアーカイブとして指定できる。ArchiveStreamはシーケンシャルな条件を満たしていない対象をソースとする場合に使用する。

用途とソースとデスティネーションの組み合わせは多様となるが実行処理の

  1. 入力先、出力先が同じクラスから生成された場合はクラスの静的メソッドを呼び出す
  2. 入力出力のどちらかがFileSystemの階層フォルダの場合はArchiveStreamの静的メソッドを呼び出す
  3. 入力出力のどちらかがBlodデータ書き込みを使用する際はArchiveStreamの動的メソッドを呼び出す

となる。

1は単一ファイル、2はFileSystem上の階層フォルダ、3は文字列を対象としたアーカイブ/展開 処理が該当する。目的に応じて実行処理メソッドを調べた上で必要なオブジェクトの生成方法を調べた方が効率が良いかもしれない。

3-2) オブジェクト生成

ArchiveByteStream、compless/decomplessストリーム生成はUIKitで馴染みのあるFactoryパターンで実装されている。ArchiveByteStreamのクラスメソッドにArchiveByteStream、compless/decomplessストリーム生成機能が用意されている。

// 入出力ストリームの生成
let readFileStream = ArchiveByteStream.fileStream()
// complessストリームの生成
let compressStream = ArchiveByteStream.compressionStream()

4) 性能を比較する

別Blogで投稿予定。

別ブログとして投稿しました。

Apple M1 ChipとIntel Chipの性能を比較する

5) わからない点

アーカイブ時に指定するヘッダーのキー情報(ArchiveHeader.FieldKeySet)についての言及が少ない。ArchiveHeader.FieldKeySetは圧縮時にFile Systemから抜け落ちるであろうファイル属性を補足するものは確かだがキーセットとファイル属性との相対表といったものは用意されていない。手がかりとなるのはAppleArchive Essentialsより、圧縮に関わる項目に記述されている内容となる。Compressing File System Directoriesで有用なキーセットの一覧、 Compressing and Saving a String to the File System にて、PAT、TYP,DATの使用方法が示されている。

キーセット 種類 捕捉
PAT ファイル名
TYP ファイル種類 レギュラー以外のファイルもFile System 上には存在するので明示的に指定する必要あり
DAT 圧縮前のデータサイズ

単体ファイルの圧縮は圧縮元のファイル属性を引き継ぐので特に言及は必要ない、KetSetが出てくるのはFile System上の階層化フォルダの圧縮、文字列などメモリ空間上に存在する要素に対する圧縮は明示的に指定する必要がある。

6) まとめ

Apple Archiveについてのドキュメント及びサンプルコードは不足していはいるが公式ページのTopicsを把握するだけでも十分かもしれない。Big SurからmacOSでも利用可能となりAppleSilicon搭載デバイスが登場すれば性能面で他の圧縮形式を選ぶことはなくなるかもしれない。

参照