{"id":16322253,"url":"https://github.com/p-x9/appcontainer","last_synced_at":"2025-03-20T22:31:21.528Z","repository":{"id":59830701,"uuid":"534216032","full_name":"p-x9/AppContainer","owner":"p-x9","description":"🧳 Library that makes it easy to create multiple environments within a single app. You can switch environments without deleting the application.","archived":false,"fork":false,"pushed_at":"2023-10-29T14:49:35.000Z","size":446,"stargazers_count":30,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-10-11T22:50:28.355Z","etag":null,"topics":["container","environment","ios","swift","swiftpackage","swiftui"],"latest_commit_sha":null,"homepage":"https://p-x9.github.io/AppContainer/documentation/appcontainer","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/p-x9.png","metadata":{"files":{"readme":"README.ja.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-09-08T13:06:54.000Z","updated_at":"2024-07-16T01:40:14.000Z","dependencies_parsed_at":"2023-02-09T04:01:20.891Z","dependency_job_id":"377e1a90-45c7-4ad5-bc69-0783e5b1c8b9","html_url":"https://github.com/p-x9/AppContainer","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/p-x9%2FAppContainer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/p-x9%2FAppContainer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/p-x9%2FAppContainer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/p-x9%2FAppContainer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/p-x9","download_url":"https://codeload.github.com/p-x9/AppContainer/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244703835,"owners_count":20496187,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["container","environment","ios","swift","swiftpackage","swiftui"],"created_at":"2024-10-10T22:50:24.796Z","updated_at":"2025-03-20T22:31:21.240Z","avatar_url":"https://github.com/p-x9.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# AppContainer\n\nまるでコンテナを載せ替えるかのように、一つのアプリで複数の環境を作成・管理することのできるライブラリです。\n\n\u003c!-- # Badges --\u003e\n\n[![Github issues](https://img.shields.io/github/issues/p-x9/AppContainer)](https://github.com/p-x9/AppContainer/issues)\n[![Github forks](https://img.shields.io/github/forks/p-x9/AppContainer)](https://github.com/p-x9/AppContainer/network/members)\n[![Github stars](https://img.shields.io/github/stars/p-x9/AppContainer)](https://github.com/p-x9/AppContainer/stargazers)\n[![Github top language](https://img.shields.io/github/languages/top/p-x9/AppContainer)](https://github.com/p-x9/AppContainer/)\n\n## コンセプト\n通常１つのアプリに対して、１つの環境(ディレクトリ, UserDefaults、Cookie, Cache, …)が存在しています。\nDebugのためや複数のアカウントを扱うために複数の環境を用意するには、複数の同一アプリをインストールする必要があります。(bundle idの異なる)\nDebugにおいては、アカウントのログインとログアウトを繰り返しての確認が必要となるケースもあるかもしれません。\n\u003c/br\u003e\nそこで、同一アプリ内に複数の環境を作成し、簡単に切り替えることができないかと考えました。\nそれで作成したのが、`AppContainer`というこのライブラリです。\n\n## デモ\n|  Default  |  Debug1  |\n| ---- | ---- |\n|  ![Default](https://user-images.githubusercontent.com/50244599/195981131-c0a3938c-2ea9-48cc-a0f5-eafd7b6ea283.PNG)  |  ![Debug1](https://user-images.githubusercontent.com/50244599/195981134-bbd94cac-6cd2-4ea9-acbc-f20d3832fef6.PNG)  |\n\n|  コンテナ選択  |  コンテナリスト  |  コンテナ情報  |\n| ---- | ---- | ---- |\n|  ![Select](https://user-images.githubusercontent.com/50244599/195981135-240d3201-66e1-4845-b437-b8e28474a946.PNG)  |  ![List](https://user-images.githubusercontent.com/50244599/195981140-6ae77d07-6a7a-495a-812b-6bf2c4b81ce1.PNG)  |  ![Info](https://user-images.githubusercontent.com/50244599/195981142-21ac932a-d82e-41ce-a30d-deebd5773fdb.PNG)  |\n\n## 原理\n### ディレクトリ\nアプリが書き込み可能な領域は、ホームディレクトリ配下にあります。\nUserDefaultsもCoreDataもCookieも、アプリが生成するデータは全てここに保存されています。\nこのディレクトリをコンテナごとに載せ替えることで複数の環境を作成しています。\nコンテナは、Library配下に特別なディレクトリを用意してそこに退避させるように実装しています。\n```\n// UserDefaults\nLibrary/Preferences/XXXXX.plist\n\n// CoreData\nLibrary/Application Support/YOU_APP_NAME\n\n// Cookie\nLibrary/Cookies\n```\n\n### UserDefaults/CFPreferences\n`UserDefaults`やその上位実装である`CFPreferences`はsetされたデータを、別プロセスである`cfprefsd`というものによってキャッシングをおこなっています。\nこれらはsetされたデータをplistファイルに保存し永続化をおこなっていますが、上記のキャッシングにより、plist内のデータと`UserDefaults`/`CFPreferences`から取得できるデータは常に等しくなるわけではありません。（非同期で読み書きが行われる。）\nこれはアプリの再起動を行っても同期されるとは限りません。\nよってコンテナの有効化処理を行う処理で、同期を行う処理をおこなっています。\n\n### HTTPCookieStorage\nHTTPCookieStorageもキャッシングされており、非同期でファイル(Library/Cookies)への書き込みが行われています。\n予期せぬタイミングで書き込みが行われてしまうと、コンテナ内でデータの不整合が起こってしまいます。\n特に同一ドメイン宛のCookieを複数コンテナで扱っている場合には、セッションが引き継げなくなってしまう問題が起きます。\nそのため、コンテナの切り替え時に、保存とキャッシュの解放を行なっています。\n\n## ドキュメント\n### AppGroup\n```swift\nextension AppContainer {\n    static let group = .init(groupIdentifier: \"YOUR APP GROUP IDENTIFIER\")\n}\n```\n### メソッド\n#### コンテナの作成\n ```swift\n let container = try AppContainer.standard.createNewContainer(name: \"Debug1\")\n ```\n\n#### コンテナのリスト\n元のコンテナは`DEFAULT`という名前で、UUIDは`00000000-0000-0000-0000-000000000000`となっています。\n`isDefault`というプロパティで確認できます。\n```swift\nlet containers: [Container] = AppContainer.standard.containers\n```\n\n#### 現在使用されているコンテナ\n```swift\nlet activeContainer: Container? = AppContainer.standard.activeContainer\n```\n\n#### コンテナの有効化\nこのメソッドを呼んだ後は、アプリを再起動することをお勧めします。\n```swift\ntry AppContainer.standard.activate(container: container)\n```\n```swift\ntry AppContainer.standard.activateContainer(uuid: uuid)\n```\n\n#### コンテナの削除\nもし削除しようとしているコンテナが使用中の場合、Defaultコンテナを有効化してから削除します。\n```swift\ntry AppContainer.standard.delete(container: container)\n```\n```swift\ntry AppContainer.standard.deleteContainer(uuid: uuid)\n```\n\n#### コンテナの中身を初期化\n```swift\ntry AppContainer.standard.clean(container: container)\n```\n```swift\ntry AppContainer.standard.cleanContainer(uuid: uuid)\n```\n\n#### リセット\nこのライブラリを使用する前の状態に戻します。\n具体的には、DEFAULTコンテナを有効にして、その他のAppContainer関連のファイルは全て削除されます。\n```swift\ntry AppContainer.standard.reset()\n```\n\n### 通知(Notification)\nコンテナ切り替え時に通知を受け取ることができます。\n厳密に、切り替え前および切り替え後に行いたい処理を追加する場合は、後述するdelegateを使用してください。\n\n- containerWillChangeNotification\nコンテナ切り替え前\n- containerDidChangeNotification\nコンテナ切り替え後\n### 委譲(Delegate)\nDelegateを使用して、コンテナの切り替え時に、任意の処理を追加することができます。\n以下の順で処置が行われます。\n\n``` swift\n// `activate`メソッドが呼び出される\n\n// ↓↓↓↓↓↓↓↓↓↓\n\n\nfunc appContainer(_ appContainer: AppContainer, willChangeTo toContainer: Container, from fromContainer: Container?) // Delegate(コンテナ切り替え前)\n\n// ↓↓↓↓↓↓↓↓↓↓\n\n// コンテナの切り替え処理(ライブラリ)\n\n// ↓↓↓↓↓↓↓↓↓↓\n\nfunc appContainer(_ appContainer: AppContainer, didChangeTo toContainer: Container, from fromContainer: Container?) // Delegate(コンテナ切り替え後)\n```\n\nこのライブラリでは複数のdelegateを設定できるようになっています。\n以下のように追加します。\n```swift\nAppContainer.standard.delegates.add(self) // selfがAppContainerDelegateに準拠している場合\n```\n弱参照で保持されており、オブジェクトが解放された場合は自動で解除されます。\nもし、delegateの設定を解除したい場合は以下のように書きます。\n```swift\nAppContainer.standard.delegates.remove(self) // selfがAppContainerDelegateに準拠している場合\n```\n\n### コンテナ切り替え時に移動しないファイルを設定する\nコンテナ切り替え時には、一部のシステムファイルを除くほぼ全てのファイルが、コンテナディレクトリへ退避そして復元されます。\nこれらの移動対象から除外するファイルを設定することができます。\n\n例えば、以下はUserDefaultを全てのコンテナで共通で利用したいときの例です。\nこのファイルは、コンテナ切り替え時に、退避も復元もされません。\n```swift\nappcontainer.customExcludeFiles = [\n    \"Library/Preferences/\u003cBundle Identifier\u003e.plist\"\n]\n```\n\nファイルパスのうち、最後がcustomExcludeFilesの内容に一致するものが全て移動対象から除外されます。\n例えば、以下のように設定した場合、全てのディレクトリ配下の`XXX.yy`というファイルが移動対象から除外されます。\n```swift\nappcontainer.customExcludeFiles = [\n    \"XXX.yy\"\n]\n```\n\n### AppContainerUI\nAppContainerを扱うためのUIを提供しています。\nSwiftUIおよびUIKitに対応しています。\n#### SwiftUI\n```swift\nimport AppContainerUI\n\n// コンテナのリストを表示\nContainerListView(appContainer: .standard, title: String = \"Containers\")\n\n// コンテナ情報を表示\nContainerInfoView(appContainer: .standard, container: container)\n```\n#### UIKit\n```swift\nimport AppContainerUI\n\n// コンテナのリストを表示\nContainerListViewController(appContainer: .standard, title: String = \"Containers\")\n\n// コンテナ情報を表示\nContainerInfoViewController(appContainer: .standard, container: container)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fp-x9%2Fappcontainer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fp-x9%2Fappcontainer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fp-x9%2Fappcontainer/lists"}