Parse.com で Push Notification with Rubymotion


RubyMotion Advent Calendar 2013、4日目。

Baas (Backend as a Service)の代表格である Parse.com を使って Push Notification を実装してみましたので、Parse.com についての印象・感想などを書いてみたいと思います。

# セットアップから初めての Push notification まで

Parse.com にアカウント登録したらまず目を通すのは [Push Quick Start Guide](https://parse.com/apps/quickstart_push) 。

Parse.com のドキュメントは総じて読みやすく、それが気に入った理由の大きなひとつ。

書かれている内容を上から順番に実施していき、ページの末尾にある Send Test Push ボタンをクリックして、アプリをインストールした実機に Push notification が届けばひとまずセットアップ完了です。

なんというかお手本のような Start Guide でした。

出来上がったソースコードは[本稿末尾](#source_code)に記載しています。

# セットアップが完了してアプリを起動しました その時 Parse.com でなにが起きたか

[Dashboard](https://parse.com/apps/) を見てみます。

左上の Select an app プルダウンで今回テストしたアプリを選び、遷移した画面の上部の Data Browser をクリックすると表示されるのは、[こんな感じの画面](https://qiita-image-store.s3.amazonaws.com/0/5004/d264db53-d7ce-91cc-f549-a0c0174f84b2.png) 

![スクリーンショット 2013-12-03 23.10.34_small.png](https://qiita-image-store.s3.amazonaws.com/0/5004/72d4f696-79ba-defe-3e80-f3b83007cdc5.png "スクリーンショット 2013-12-03 23.10.34_small.png")

この画面では Parse.com がディベロッパーに提供してくれている Key - Value 型 Data Store の状態を見ることができます。

Installations Class の Object が2つできていますね。
※今回自分は2台のデバイスにアプリをインストールしました。

iOS アプリ側で Parse.com に Object の作成を指示しているのは app_delegate.rb のこの部分。

```ruby:app_delegate.rb
  # callback of register_push_notifications
  # see https://www.parse.com/tutorials/ios-push-notifications
  def application(application, didRegisterForRemoteNotificationsWithDeviceToken:newDeviceToken)
    # Store the deviceToken in the current installation and save it to Parse.
    currentInstallation = PFInstallation.currentInstallation()
    currentInstallation.setDeviceTokenFromData(newDeviceToken)
    currentInstallation.saveInBackground()
  end
```

Parse.com に HTTP リクエストしているはずですが、Parse.com SDK に含まれる PFInstallation クラスがそれを隠蔽してくれてます。




ところで Installations Class とはなんぞや？、とドキュメントを見てみます。
Push Notification Guide に installatiions という小見出しがありました。

[Push Developer Guide | Parse](https://parse.com/docs/push_guide#installations/iOS)

>Installations
>
>Every Parse application installed on a device registered for push notifications has an associated Installation object. The Installation object is where you store all the data needed to target push notifications. For example, in a baseball app, you could store the teams a user is interested in to send updates about their performance.

+ Push notiofication を送るデバイス1台に対して1つの installation object が作成される
+ installation object はアプリの仕様に応じて拡張可能で、Push notification を送信する対象デバイスを決定するのに必要な情報をすべてもたせることができる。

とのこと。

このドキュメントもとても見やすいですね。
いま読んでいる箇所を左側のナビゲーションがハイライトしてくれるので、自分がいま何を読んでいるのかが直観的にわかります。

いま読んでる箇所にはナビゲーションからいつでも戻れるので、少し他の気になるセクションをつまみ読みしてみます。

例えばこちら、[Cloud Code Guide | Parse](https://parse.com/docs/cloud_code_guide#cloud_code)

大雑把にですが

+ Node.js で Parse.com 上に独自の Function を定義できる
+ 定義した Function は `https://api.parse.com/1/functions/[function 名]` をリクエストすることでコールできる
+ Function 内では、Data Store に対しての CRUD を実行できる
+ Function とは別に CRUD に対する hook も定義可能
+ Scheduler もある

といったことが読みとれました。

# Parse.com に対する印象が変わった

自分は Parse.com に対して

+ Push Notification や in App Purchase など iOS を始めとする mobile app の構築に必要な Backend Service を提供してくれるもの

という印象をもっていました。

しかし実際は

+ Parse.com が提供するのは
	+ Key-Value Store
	+ Key-Value Store を操作するための Rest API
	+ Rest API の HTTP リクエストを隠蔽してくれる 各種 SDK
+ Installation Object のような特殊な Object を作成した場合に Push Notification など特殊な動作をサポートしてくれる

というノリっぽい。

前者と後者とで何が違うの？ときかれるとやや言葉につまるのですが、

前者は

+ 定型的な処理をするのであれば楽できるが、ややイレギュラーなことをしようとすると途端に手詰まりになりそうなイメージ

なのに対し後者は

+ Parse.com が想定してない使い方もなんやかんやでできちゃいそうなイメージ

あと付け加えるとするならば、ドキュメントを読んだり SDK を触ってみたりしたところ、ユーザビリティをすごく重視しているな、と。

この場合ユーザとは、我々アプリディベロッパを指すわけですけど、ユーザに対する愛？があるな、と。

と、いったわけでちょっと本格的に利用してみようかな、と思っだ次第でした！
（なんだこのまとめ）


# 最後に

RubyMotion Advent Calendar 2013、明日5日目は [naoya@github](http://qiita.com/naoya@github) さんです！
余談ですが、[ドラゴンクエストX version 2.0 眠れる勇者と導きの盟友](http://www.dqx.jp/2nd/) は同日 10:00 に on Store!!

# 参考

+ Push Notification に関する公式ドキュメント
	+ [About Local Notifications and Push Notifications(英語)](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Introduction.html#//apple_ref/doc/uid/TP40008194)
	+ [Local および Push Notification プログラミングガイド(日本語)](https://developer.apple.com/jp/devcenter/ios/library/documentation/RemoteNotificationsPG.pdf)
+ Installation Object を元に Parse.com が何をしているか
	+ [Provider Communication with Apple Push Notification Service](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/CommunicatingWIthAPS.html)

<a name ="source_code"></a>
# ソースコード
```ruby:Gemfile
source 'https://rubygems.org'

gem 'rake'
gem 'motion-cocoapods'
gem "bubble-wrap", :require => "bubble-wrap/all"
```

```ruby:Rakefile
# -*- coding: utf-8 -*-
$:.unshift("/Library/RubyMotion/lib")
require 'motion/project/template/ios'

begin
  require 'bundler'
  Bundler.require
rescue LoadError
end

Motion::Project::App.setup do |app|
  # Use `rake config' to see complete project settings.
  app.name = 'pushtest'
  app.identifier = "自分の app indentifier"

  # Parse.com の依存ライブラリ
  # see https://parse.com/apps/quickstart_push#ios/native/existing
  app.frameworks += [
    "AudioToolbox",
    "CFNetwork",
    "CoreGraphics",
    "CoreLocation",
    "MobileCoreServices",
    "QuartzCore",
    "Security",
    "StoreKit",
    "SystemConfiguration"
  ]
  app.libs += ["/usr/lib/libz.dylib"]
  
  
  # Parse.com SDK
  app.vendor_project(
    'vendor/Parse.framework',
    :static,
    :products => ['Parse'],
    :headers_dir => 'Headers'
  )

  # Parse.com SDK が依存してる Objective-C ライブラリ
  app.pods do
    pod 'Facebook-iOS-SDK'
  end


  app.development do
    # Provisioning Profile のパス
    # Provisioning Profile の作成方法は
    # → https://www.parse.com/tutorials/ios-push-notifications
    app.provisioning_profile = ""
    
    # この行がないと 'aps-environment' error が返る
    # TODO: at release, see http://kametaro.wordpress.com/2011/09/21/appstore%E3%81%8B%E3%82%89%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%97%E3%81%9F%E3%82%A2%E3%83%97%E3%83%AA/
    app.entitlements['aps-environment'] = 'development'
  end

end
```

```ruby:app_delegate.rb
class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)

    self.register_push_notifications(application)
    self.configure_parse_service(launchOptions)
    
    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
    @window.rootViewController = HelloViewController.new
    @window.makeKeyAndVisible
    true
  end

  def application(application, didFailToRegisterForRemoteNotificationsWithError:error)
    if (error.code == 3010)
      App.alert("Push notifications don't work in the simulator!")
    else
      App.alert("didFailToRegisterForRemoteNotificationsWithError: #{error.code}, #{error.domain}, #{error.userInfo}")
    end
  end

  def register_push_notifications(application)
    application.registerForRemoteNotificationTypes(
      UIRemoteNotificationTypeBadge |
      UIRemoteNotificationTypeAlert |             
      UIRemoteNotificationTypeSound)
  end

  def configure_parse_service(launchOptions)
    # see https://parse.com/apps/pushtest--177/edit#app_keys
    Parse.setApplicationId(
      "Parse.com の Application Id",
      clientKey:"Parse.com の client key",
    )
    PFAnalytics.trackAppOpenedWithLaunchOptions(launchOptions)
  end

  # callback of register_push_notifications
  # see https://www.parse.com/tutorials/ios-push-notifications
  def application(application, didRegisterForRemoteNotificationsWithDeviceToken:newDeviceToken)
    # Store the deviceToken in the current installation and save it to Parse.
    currentInstallation = PFInstallation.currentInstallation()
    currentInstallation.setDeviceTokenFromData(newDeviceToken)
    currentInstallation.saveInBackground()
  end

  def application(application, didReceiveRemoteNotification:userInfo)
    PFPush.handlePush(userInfo)
  end

end
```

```ruby:hello_view_controller.rb
# -*- coding: utf-8 -*-
class HelloViewController < UIViewController
  
  def viewDidLoad
    super
    view.backgroundColor = UIColor.blueColor
  end

end
```