問題点
iOS8でアプリ間でデータを共有する仕組みとしてAppGroupが導入された。カスタムキーボードやAppleWatchアプリ等の拡張は別アプリとなるため、これらを開発をする際はAppGroupの利用が事実上必須である。 (参考: http://www.toyship.org/archives/1845)
AppGroupは開発者アカウントごとのデータであり、かつ、世界中で一意となる名前を持たねばならない。 個人(Individual)アカウントは複数人で共有できないため、同じAppGroupを使うことができない。企業(Company/Organization)アカウントならばAppGroupの共有が可能だが、取得には法人である必要がある。
そのため、法人でない団体(例: 大学サークル等)では、AppGroupの利用が難しい。
解決策
以下の方針で、開発を複数人で行なえるようにする。
- 開発者ごとにAppGroup名/Bundle Identifierを変更する。
- AppGroup名/Bundle Identifierを具体的に書く箇所を一箇所にすることで、容易に変更できるようにする。
ただし、AppStoreに提出できるのは誰か一人という制約は解決できないため、誰かが正式版のビルド係/AppStoreへの提出係となる必要がある。
手順
Xcode 6.3.2をもとに手順を記述する。
Config.xcconfigを作成する
以下の内容の Config.xcconfig
を作成し、プロジェクトに追加する。
// 以下の行を開発者ごとに変更すること
APP_IDENTIFIER = org.example.mzp.some_app
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) APP_IDENTIFIER=${APP_IDENTIFIER}
gitで管理している場合は以下のようにするとよい。
- 上記の内容をConfig.xcconfig.exampleに記述する
- .gitignoreでConfig.xcconfigを無視するようにするように設定する
- 開発者がcloneした直後にConfig.xcconfigをConfig.xcconfig.examlpeを元に作成するようにするようREADME等に記述する
Config.xcconfigを追加する
プロジェクト -> Info -> Configurationsで Config.xcconfig
を使うように設定する。
Info.plistの変更
各ターゲットの Info.plist
内のBundle identifierを用いている部分を $(APP_IDENTIFIER)
で置換する。
ターゲットの種類によって置き換えなければならない箇所は異なる。
AppGroupの有効化
各ターゲットのCapabilitiesからAppGroupを有効にする。
また、下の「+」ボタンで、AppGroupの作成も行なえる。
*.entitlementsの変更
<アプリ名>.entilements
が追加されるので、AppGroup名をgroup.$(APP_IDENTIFIER)
で置換する。
ラッパクラスの作成
アプリ内でAppGroupを用いた処理をできるようラッパクラスを作成する。CPPマクロを利用するため、Objective-Cでなければならない。
Objectvie-Cのため、Swiftから利用する場合はBridge Headerを適切に設定する必要がある。
AppGroup.h
#import <Foundation/Foundation.h>
// AppGroupに関係する情報のラッパ。
//
// 個人アカウントを使っているため、ユーザに応じてAppGroup名を変える必要がある。
// AppGroup名はConfig.xcconfig内でCPPマクロとして定義されているため、Swiftからは読むことができない。
// そのためObjective-Cで記述する。
@interface AppGroup : NSObject
// AppGroup名
+ (NSString *)appGroupID;
// AppGroupで共有された領域のパスを取得
+ (NSString *)pathForResource:(NSString *)subpath;
// AppGroupで共有されたNSUserDefaultsを取得
+ (NSUserDefaults*)userDefaults;
@end
AppGroup.m
#import "AppGroup.h"
#define STR(x) @#x
#define STR2(x) STR(x)
static NSString * const kAppIdentifier = STR2(APP_IDENTIFIER);
@implementation AppGroup
+ (NSString *)appGroupID
{
return [NSString stringWithFormat:@"group.%@", kAppIdentifier];
}
+ (NSString *)pathForResource:(NSString *)subpath
{
NSString *containerPath = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:[self appGroupID]].path;
return [containerPath stringByAppendingPathComponent:subpath];
}
+ (NSUserDefaults*)userDefaults
{
return [[NSUserDefaults alloc] initWithSuiteName: [self appGroupID]];
}
@end