はじめに
大工が日曜日に大工をすれば日曜大工と呼ばれるように、日々、命を削りつつ働いているプログラマも安息の時間である土日にプログラミングをすれば日曜プログラマです。
「平日はプログラミングをして稼いでいるので、土日まで働くほどがっつきたくない。…だけれどやっぱりプログラミングは好き」
という輩がこのように土日もプログラミングに勤しむわけですが、私を含めてこのような創作系の人達は「誰かが使うのを前提に」作りません。あくまで自分が使うのを前提に作ります。(じつはそれが拘っている分、一番良い物が作れるんですが)
それで何が起きるかっていうと、例えばアプリを作った時にいちいちAppleにそれを報告する煩わしさ…があり、報告をしなくなります。
例えば…。
こんな感じにiPadとかiPhoneに転送できればいいや的な。
そんな人達が「Push通知」という壁を前にして、どうすればいいねん?なんでこないなことせなあかんねん?!という挫折をするので(実際自分もしたので)ここに手順だけでなく、なぜそれをするのかの説明も踏まえて書き残そうと思います。
Push通知で何ができるのか?
Push通知という言葉を見ると通知をPushするので「メッセージを送るもの」だと思われがちですね。
まぁまさにそうですが、Twitterなどでリプライやイイネがあると送られてくるアレです。メールを受け取った時に送られてくるアレです。
しかし「自分しか使わないアプリを作ってきた皆さん」にとっては
「メッセージを送るだけなら何もわざわざアプリ作ってPush通知で送らなくてもSlackで送っときゃえぇやん」( •́⍛•̀)
と思うでしょう。
下の図を御覧ください。
Providerが皆さんが普段愛用している「自宅サーバ」です。
APNsがPush通知を各デバイスに送信するApple様のサーバです。
ClientAppが日曜プログラミングの産物ですね。
この線はメッセージだけではありません…。
メッセージでもあり、データでもあり、指示でもあり…そう、つまり、皆さんにとってPush通知最大の利点…それは「サーバ側からクライアントに対してメッセージを送った後に、クライアント側から処理を行わせることができる」ということです。
例えばあなたが日曜プログラミングで産みだした産物のデータがサーバと同期とらなきゃいけないっていう時、あなたはクソ面倒臭がりながらもiPadを使って「同期」機能を使って同期していたかもしれません…。もしサーバ側で同期が必要だと判断した時、いつの間にか勝手にクライアント側も同期されていたら…?
あなたは「同期する行為」の「時間」を消費しなくていいのです。
例えるのなら、あなたは暑いと思った時にクーラーのスイッチを入れずとも、あなたにとって暑いと思う温度に達する前にクーラーは既に部屋を冷ましているわけです。
これはIoTを始めとして、これから「快適な生活を送るためのコンピュータ」を使う上で重要な考え方です。
Push通知云々の前に知っておくべきこと
1. Appleはアプリを認識しなければならない
「いや、そこはお前、Apple様なんだからえぇ感じにやれや」( •́⍛•̀)
そう思うかも知れませんが、えぇ感じにやって限界が今のPush通知申請機構なんでしょう。犯罪者(クラッカー)が悪さをしないギリギリのラインです。Push通知を行うのがAppleのサーバなので当然ながらApple側からはアプリを把握しなければなりません。誰かよくわからん奴がAppleのPush通知サーバにアクセスしてきたら大変な事になります。日本みたいに申請書書いて捺印して提出しなかっただけありがたいと思いましょう。
2. Appleはあなたを信用していない
「AppleIDもあるし、iOS Developerなんちゃらで年間1万円ぐらい払ってるんやから、そこはえぇ感じにせぇや…面倒クセェな」( •́⍛•̀)
そう思うかもしれませんが、えぇ感じにやって限界が今のPush通知申請機構なんでしょう。犯罪者(クラッカー)が悪さをしないギリギリのラインです。年間1万円払って悪さをする輩がいたからこんな事になってるんでしょう。
悪さをした輩にこの怒りを向けましょう。
3. Appleはあなたが使うMacを信用していない
「お前、さっきからえぇかげんにせぇや。人を見たら犯罪者と思えとか本気で信じてるんか?C#使えたらなりすましで犯罪メール送ってるとか本気で思ってそうやな、マジでオツムの病院に行ってその統失治したほうがえぇで。家の周りの奴ら全員ストーカーだと思うようになるでホンマ…」( •́⍛•̀)
残念ながら、もうAppleは何も信じてません。
産まれたばかりの子鹿のようにガタガタと足を震わせながらビビってます。
仮に信じてるとしたら「あなたの使ってるMacが悪さをしてくる可能性がある」ってことぐらいです。
App申請もPush通知申請も、重要なのはAppleに「あなた」を認めさせる事である
つまりは、そういうことです。
だからこれらの行為は難しいのです。プログラマなのにプログラミングと関係ないことをしなきゃいけないからです。
そしてiOSアプリを普段からAppleStoreに公開してコメント欄でボロクソに叩かれてる開発者達が難なくこれらをやってのけてる…という理由は、普段からAppleに認めさせる行為をしているからですね。
誰にも認められずとも「自分しか使わないから」という利点でアプリを作ってきた人達にとってはこれら申請行為は寝耳にミミズな出来事なわけであります。
DeveloperなんちゃらのCertificates, Identiなんちゃら
まず申請作業をする前に、主なサイトの説明から行います。
意味や目的もわからずに淡々と手順をおって作業するのと、意味や目的を理解した上で作業をするのとでは出来上がるものが違うと某CMでも言われていますよね👍
- Developerなんちゃらにブラウザ(Safari必須)でアクセス
https://developer.apple.com - アカウントを選択
ログイン等をします。 - 左側Certificates, Identifers & Profilesを選択
Certificates は日本語で「証明」「免許証」など。
Identifers は「識別」。
あなたをあなたであるとAppleに証明し、Appleに識別してもらう機能です。
1つずつ見ていきましょう。
Certificates (証明書)
一番上のグループがCertificates:証明書です。
これらはオンライン上に格納されてますが、この状態では何の証明も成しません。これをMacにダウンロードして初めて証明が行われます。
キーチェーンアクセスというアプリがMacには標準で入っています。
証明書を管理するアプリです。
みてもらうと今回のDeveloperなんちゃら以外に沢山、自分が気づかないところで証明書が発行されて管理されているのがお分かりいただけると思います。
オンライン上に格納されている証明書をダウンロードして開くと自動的にキーチェーンアクセス内に登録済み一覧として表示されますが、これらはダウンロードして開いて初めて証明書として成す事に気をつけてください。
「わい、新しいMac買ったったわwwwwそっちにも証明書ダウンロードしてインストールしたろ!うっひょ!簡単簡単!!!!wwwwwww」
と思ってたら大間違いです。
イメージ的には捺印されてない証明書をダウンロードして、開いたら自動的に捺印されてMac内に保存される…というのが正しいです。他のMacで同じことをしたら「そのMacの捺印」がされた証明書が保存されます。
これを同一の証明書とするか?
答えは「No」です。
悪意のある誰かがあなたのDeveloperなんちゃらにログインして、勝手に証明書をダウンロードしてもApple様は気付くようにしているわけですね。
新しいMacを買ったら古いMacにある証明書をそのまま書き出しましょう。
これについては手順はここでは省略します。
Identifiers (識別)
ここでAppleから識別させる為に、今までは「Xcode: Wildcard AppID」としかなかったと思いますが、今回開発するアプリを登録します。注意点として、
Developerなんちゃらに登録されるAppIDとXCode上のBundle Identifierは合わせましょう。これでAppleからアプリを識別させることになります。
また、慣習に従いIDはcom.system.applicationという感じにURLをひっくり返したような文字列にしましょう。
例えば自分の場合はsystemの部分がシステム大枠の名前にして、applicationがクライアント名となってます。システムに他のクライアントが新たに増えた場合はcom.system.application2とかになります。
Devicesについてはみたまんまなので省略します。
Provisioning Profiles
AppIDが実態の無い識別とアプリ情報であることに対して、Provisioning Profilesはプロファイルという実態(実際にダウンロードする)があります。
ダウンロードして実行するとXCode上にプロファイルが登録されます。もちろんアプリのBuild Settingsで設定してあげる必要があります。その後、コンパイル・リンク・実機転送でアプリと一緒にプロファイルも転送されます。中身はAppIDで設定したこととほぼ同じで、アプリのIDとそのアプリがどのようなiOSの機能を使うのか?などがあります。
今回はPush通知を使う、という状態で転送されますね。
開発者証明書の取得
さて、ようやくここから「信用(申請)」行為です。
事前にDeveloperなんちゃらに年間1万円を払うアレをやっておきましょう。
おおまかには以下の作業を行います。
- CSRを作成
- CSRを用いてiOS Certificatesを作成
- 上記をダウンロード
- インストールする
最終的に開発者証明書「iOS Certificates」を取得できます。これは実機にアプリを転送したりする時に必要なのでアプリ転送の準備と言えます。
CSR (Certificate Signing Request - 証明書署名要求)を作成
どのマシンから実機へ転送するのかが必要なのでマシン上でCSR (CertificatesSigningRequest)を取得します。マシンから実機への転送は1on1の関係になっている為、
「Mac Miniで普段は転送してるんだけど、スタバでドヤリングしてる時はもちろんスタイリッシュなMac Book Airで実機に転送したいんだよ」
という希望がある場合は最終的に取得されたiOS Certificatesを複製する必要があります。それをやらずにコンパイル・リンクを行うとエラーになります。
- キーチェーンアクセスを起動します
- メニュー「キーチェーンアクセス」から「証明書アシスタント」の「認証局に証明書を要求」
- メールアドレス、通称(ニックネーム)を入力し、続いて「ディスクに保存」「鍵ペア情報を指定」にチェックをつけて続けるを選択
- 適当な場所に保存します
この次に鍵ペア情報を設定するダイアログが表示されます。
上記のように設定してください。
iOS Certificatesを作成
- CertificatesのDevelopmentを選択(Productionにも後で同様に実施します)
- 「+」ボタンで追加
- どのタイプの証明書が必要ですか?とありますので、iOS App Developmentを選択(本番リリースの場合に必要なのはApp Store and AdHocです)
- 最下部のContinueボタンを押します
自分しか使わないアプリですからAppStoreへ公開はありませんが、AdHocを選択することで実機に転送できます。まぁ、ズボラな皆さんの事ですからDevelopmentの状態で使い続けることが多いでしょうが…( •́⍛•̀;)
- ここで英語で色々書いてありますが、これはキーチェーンアクセスでCSRファイルを作成する手順が書かれてあります。先程既に作成しましたね。
- というわけで、そのままContinueを押します
- Choose Fileを押します
- ファイルを選択するダイアログが表示されますので、先ほど作成して保存してるCSRファイルを選択します
- Continueを押します
- これでCertificateが作成されましたので、Downloadを選択してMacにダウンロードし、そのダウンロードしたファイルを開いてください。キーチェーンアクセスに証明書がインストールされます。
アプリの登録
- App IDsを選択し「+」ボタンを押します
- App ID Descriptionにアプリの名称を入力します
- App ID Prefixを選択します
- App ID Suffixは「Explicit App ID」を選択します
- Bundle IDにこれから作るアプリのIDを登録します
- App ServicesではPush Notificationsを選択します
- 最後にContinueを押します
Bundle IDにはネーミングにルールがあります。
以下のようにURLのサーバの部分がひっくり返ったような名前です。
org.toire.unchi
自分の場合は1つめのワードはorg固定です。
2つめのワードは自宅で管理しているシステムの名前になります。
3つめのワードでようやくアプリケーション名がでてきます。
Confirmの画面がでてきます。
Game CenterやIn−App Purchaseなどは勝手に選択されていたのでEnableになりますね。大事なのはPush Notificationsです。おや、黄色(Configurable)になっていますね?これは後で追加設定が必要な事を示しています。
問題無ければRegisterを押してください。
いよいよ、自分しか使わないアプリがAppleに識別されました。
Push Notifications用の認証ファイル準備
先ほどPush Notificationsが黄色(Configurable)となっていたはずです。ここで設定を行い、アプリの識別を完了させましょう。これを実施しない状態でプロヴィなんとか(Provisioning Profiles)を作ってしまうとPush Notificationsが失敗します。
- 引き続きAppIDsで先ほど作成したAppIDを選択しEditボタンを押します
- 画面のずっと下の方にスクロールするとPush Notificationsがあり、認証証がまだ何も作られていないことがわかります。
- Create Certificateで認証証を作成します
- ここからの手順は先程「開発者証明書」を作ったものと同じです。几帳面なあなたなら既にCSRファイルがどこかにちゃんと保存されていると思いますので、それを用いて認証証を作り、ダウンロード&開いてキーチェーンに登録しましょう。
プロヴィ…なんとかの作成
AppIDの登録でAppleはあなたのAPを認識しましたが、iPadやiPhone側では認識されていません。これらの紐付けを行うのがプロヴィなんとか(Provisioning Profiles)です。
- Provisioning ProfilesのAllを選択
- +ボタンを押します
- DevelopmentのiOS App Developmentを選択しContinueボタンを押します
本番環境へのリリース(自分しか使わないアプリを作り続けてきた皆さんに本番環境というのがあるのなら)はDistributionのAd Hocです。
- 先ほど作成したAppIDがドロップボックス内の一覧にあるので選択します
- Continueボタンを押します
- 開発者認証を選択する画面では普通はあなた自身の認証1つしかないはずですが、それを選択します
- Continueボタンを押します
- 転送先のデバイスを選択します
- おや?どうやら私は5台持ちのようですね(΄◞ิ౪◟ิ‵)
- Continueボタンを押します
- Profile NameにProvisioning Profilesの名前をつけます
- Continueボタンを押します
アプリケーション新規作成〜実機転送
プロジェクトの設定
ここからは経験ありという前提でお話します。
まず、BundleIdentifierは先程AppIDと同じに合わせましょう。
CapabilitiesのBackGroundModesをオンにします。また、ただ通知をするだけではSlackでやれという話なので「Background fetch」もオンにします。
これだけで終わればいいんですが…クソ面倒臭いBuild Settingsの設定をしなければなりません…。
Code Signing IdentityのDebug側AnyiOSSDKに認証証の開発側をセット。
Code Signing IdentityのRelease側AnyiOSSDKに認証証の本番側をセット。
ダウンロードして開いたProvisioning Profileをセット。
AppDelegate.mにコードを記述
まずdidFinishLaunchingWithOptionsにアプリ起動時に出る「このアプリはプッシュ通知を行います」のメッセージダイアログを表示させます。ここで「はい」を選択すると以降、プッシュ通知を受け取れます。自分しか使わないんだから「はい」しか押さねぇよと思われるかもしれませんが、まぁ我慢してください。
-(BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSString *strSystemVersion = nil;
strSystemVersion = [[UIDevice currentDevice] systemVersion];
if ([strSystemVersion compare:@"8.0"
options:NSNumericSearch] == NSOrderedAscending) {
// iOS7以前
[application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
} else {
// iOS8以降
UIUserNotificationType types = UIUserNotificationTypeBadge |
UIUserNotificationTypeSound | UIUserNotificationTypeAlert;
UIUserNotificationSettings *mySettings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
[application registerUserNotificationSettings:mySettings];
}
// Override point for customization after application launch.
return YES;
}
iOS8以降の場合は「はい」を押した後に以下のメッセージも飛んできます。
そこで「[application registerForRemoteNotifications];」を実行します。
-(void)application:(UIApplication *)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
[application registerForRemoteNotifications];
}
iOS7以前は以下のメッセージがいきなり飛んできますが、iOS8以降は👆の[application registerForRemoteNotifications]を実行すると以下にメッセージが飛んできます。
didRegisterForRemoteNotificationsWithDeviceTokenでは「デバイストークン」が受信できます。これをメモっておいてサーバ側からプッシュ通知を送る時に利用します。
え?メモっておくとか原始的やなぁ…(΄◞ิ౪◟ิ‵)
確かに原始的です。北京原人級に原始的ですね。
本来なら取得したデバイストークンをPOSTとかでサーバに送りサーバ側ではデータベースに顧客テーブルがあってそこに格納して顧客アカウントとデバイストークンを管理しておくべきでしょう。それらは暗号化されてたりバックアップされてたり…ちょっと待てよ、
自分しか使わないアプリを作ってきた開発者様達が1デバイスあたり1個しか取得されないデバイストークンの為にそんな処理をわざわざ作るのはありえないっしょ…(΄◞ิ౪◟ิ‵;)
というわけで、メモです。
メモしてください。
-(void)application:(UIApplication*)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSLog(@"Device Token = %@", deviceToken);
}
もし👆のが失敗した時は以下が呼ばれます。
アプリケーション起動時にプッシュ通知しますけどいいですかダイアログは1回しまでませんが、これらは初回だろうが2回目だろうが、毎回呼ばれます。
-(void)application:(UIApplication*)application
didFailToRegisterForRemoteNotificationsWithError:(NSError*)error {
NSLog(@"error: %@", error);
}
さて、以下からはいよいよPush通知そのもののコードです。
didReceiveRemoteNotificationはその名前の通り、Push通知を受け取ると起動されます。
ただしBackgroundFetchをオンにしている場合は呼ばれず、didReceiveRemoteNotification:fetchCompletionHandlerのほうが呼ばれます。
userInfoの中からメッセージなどの情報を受け取ることができます。(Push通知の最大長が決まっているので…当然ながら)送るメッセージ以外にバンバン送るのはやめたほうがいいです。
-(void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo {
NSDictionary *aps = [userInfo objectForKey:@"aps"];
NSString *alertMessage = [aps objectForKey:@"alert"];
NSLog(@"Alert = %@", alertMessage);
}
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
if (userInfo) {
NSDictionary *aps = [userInfo objectForKey:@"aps"];
NSString *PushMessage = [aps objectForKey:@"alert"];
NSLog(@"Alert = %@", PushMessage);
UIApplicationState appState = application.applicationState;
NSString *appStateString = @"unknown";
if (appState == UIApplicationStateActive) {
appStateString = @"active";
} else if (appState == UIApplicationStateInactive) {
appStateString = @"inactive";
} else if (appState == UIApplicationStateBackground) {
appStateString = @"background";
}
NSLog(@"Receive remote notification. State:%@", appStateString);
}
// completionHandlerはダウンロードのような時間がかかる処理では非同期に呼ぶ。
// 同期処理でも呼ばないとログにWarning出力されるので注意。
completionHandler(UIBackgroundFetchResultNoData);
}
サーバからアプリケーション側にPush通知を送り、受け取った時に受け取ったメッセージに応じて様々な処理を行えばサーバとクライアントの間で「あなた」の手を煩わせる事は無くなるでしょう。
Push通知は必ず呼ばれるのか?
iOSで他のアプリを使用してた人はわかると思いますが、Push通知は必ず呼ばれるわけではありません。例えばTwitterで面白いポストをしたのに依然として「イイネ!」が来ない…おかしい…と思ってTwitterを起動すると「イイネ!」がついていた…という経験はありますか?
その時は大抵の場合、Twitterはバックグラウンドにあるわけじゃなく、何かの理由によりメモリが無くなって最初から起動されると思います。(Homeボタンをダブルクリックして表示される起動アプリ一覧にあっても、メモリ不足によりバックグラウンドに存在しないことになってる可能性はあります)
このようにPush通知はアプリケーションが起動されていて、バックグラウンドかアクティブの状態にあり、さらにメモリ内にちゃんと存在している場合に呼ばれます。
こればっかりはiOSの仕様なので諦めましょう。
まぁ、あなたが頻繁に使うであろう日曜プログラミングで作成したアプリが「メモリから追い出されてるような状態」に陥ることなどないと思います。
それでもPush通知を受け取った時に行う処理が必須であるのなら、アプリが起動されている最中に定期的にサーバ側にアクセスして処理を行うような仕様にしましょう。