iOSのアプリでサーバーサイドと連携するにも関わらず明示的なユーザー登録を必要としないアプリがある。例えばnanapiのアンサーなどがそれにあたる。

ひとつ端末でしか使わないという世界観でアプリを作ればユーザーに明示的にログインさせる必要はないのでユーザー体験もよくなる。

そのようなアプリケーションを実装するときの1アイデアを試してみたのでここに書き残しておく。

端末固有のID

ユーザーを特定するためには固有のIDが必要になる。しかし、端末固有のUDIDはiOS6から使えなくなったし、本当にハードウェア固有のIDを使ってしまうと端末を変更したときの移行処理がややこしくなってしまう気がする。

なお、固有識別子の話はこちらに詳しい。

iOSでの端末固有識別子の話。 - なるようになるといいねiOSでの端末固有識別子の話。 - なるようになるといいねはてなブックマーク - iOSでの端末固有識別子の話。 - なるようになるといいね

↑の投稿にもあるように、UUIDを使うのがいい気がする。初回起動時の自動ユーザー登録の際にUUIDを発行して何らかの形で保存。次回以降の起動したときは保存しておいたUUIDでログイン、といった流れになる。

Keychain

では生成したUUIDはどこに保存するのがよいか。すぐに思い浮かぶのはNSUserDefaultsだが、これでは2つ問題がある。

  • セキュリティ面で問題がある(かもしれない)
  • データが永続化できない

1つ目はパスワードなどの機密情報を平文で保存することになるので望ましくないという、まあよくある話だし重要な点。

2つ目はデータの永続化。NSUserDefaultsはアプリをアンインストールしたときに一緒に消えてしまうため、再インストールしたときにその情報は消えてしまう。こうなってしまうと、これまでと同じユーザーとしてログインすることはできなくなってしまう。

Keychainがこれらの問題を解決してくれる。セキュリティ的にも良いし、アプリが消えてもデータを残せる。Keychainについてはこちらが詳しい。が、、、

Cocoaの日々: [iOS] Keychain Services とは

いかんせんCベースの生APIを叩くのは骨が折れる。幸い、ラッパーライブラリがいくつかあるのでそれを使えば良い。見た中ではNSUserDefaultsライクにこれが一番よさそうかな。

KeyChain のデータを操作するラッパークラス UICKeyChainStore を書きました。 - 24/7 twenty-four sevenKeyChain のデータを操作するラッパークラス UICKeyChainStore を書きました。 - 24/7 twenty-four sevenはてなブックマーク - KeyChain のデータを操作するラッパークラス UICKeyChainStore を書きました。 - 24/7 twenty-four seven

サンプル

ユーザー管理にはParse.comを使ってみることにした。データストアの検証も兼ねてね。Parse.com自体のユーザー登録やセットアップについては割愛するし、サンプルなのでネットワークがないなどのエラー処理は書いていない。省エネ。

 1// AppDelegate.m
 2
 3#import "User.h"
 4#import <Parse/Parse.h>
 5
 6@implementation AppDelegate
 7
 8- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
 9{
10    // Override point for customization after application launch.
11    [Parse setApplicationId:@"YOUR_ID"
12                  clientKey:@"YOUR_KEY"];
13
14    [PFAnalytics trackAppOpenedWithLaunchOptions:launchOptions];
15
16    [User signIn]; // ← これ!!!
17    return YES;
18}

起動時に[User signIn]とやらを呼んでいて、このUserクラスは自前のクラスとなっている。

 1// User.m
 2#import "User.h"
 3#import <Parse/Parse.h>
 4#import <UICKeyChainStore.h>
 5
 6NSString *kKeyForName = @"UserName";
 7NSString *kKeyForPassword = @"UserPassword";
 8
 9@implementation User
10
11+ (void)signUp
12{
13    PFUser *user = [PFUser user];
14    user.username = [[NSUUID UUID] UUIDString];
15    user.password = [[NSUUID UUID] UUIDString];
16
17    [user signUpInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
18        if (!error) {
19            [UICKeyChainStore setString:user.username forKey:kKeyForName];
20            [UICKeyChainStore setString:user.password forKey:kKeyForPassword];
21        } else {
22        }
23    }];
24}
25
26+ (void)signIn
27{
28    NSString *userName = [UICKeyChainStore stringForKey:kKeyForName];
29    NSString *userPassword = [UICKeyChainStore stringForKey:kKeyForPassword];
30    if(userName && userPassword){
31        [PFUser logInWithUsernameInBackground:userName password:userPassword];
32    }else{
33        [self signUp];
34    }
35}

signInではKeychainに値を取りに行き、ユーザー名とパスワードがあればその値を使ってPFUserを作ってログインし、なければsignUpを呼ぶ。

signUpでは[[NSUUID UUID] UUIDString]でユーザー名とパスワードを生成、サインアップが成功したらKeychainに値を保存しておく、という流れになる。

このような方法を取ることで、起動した時点でユーザー登録が完了してログインした状態にできるし、アプリを一旦削除しても入れなおせば再び使い始められるアプリができた。

実装の仕方はこれだけではないかもしれないけれど、まあまあの線じゃないかなと個人的には思っている。

あとは機種変更時の移行を実現できれば、スマートフォンのみを使ったログインなしで使えるサービスにできそうだ。