Realm Mobile Platformがリリースされた
これまでのクライアント独立のローカルDB機能に加えて
リアルタイムの同期、その場合に発生しうるコンフリクトの解決、イベントハンドリング等が出来るようになった

Realm Mobile Platform: リアルタイムの同期と、Coreのオープンソース化

イメージ的にはFirebaseのリアルタイム同期型データベースと同じようなものっぽいけど MBaasのようなクラウドサービス提供型ではないのでサーバは自前で運用する必要がある

デモアプリを動かしてみる

まずはrealm-mobile-platformのmacOS bundleをダウンロード

Realm Mobile Platform: Getting Started

解凍したファイル中のstart-object-server.commandを実行するとrealm-object-serverが起動し
ブラウザでlocalhost:8090/#startupが表示される

初回アクセス時にemailとpassを設定する

demo/RealmTasksの中のRealmTasks.appを起動して適当に動かしてみる

最初にusernameとpasswordを聞かれるので先ほど設定したemailとpassを入力し、TODOリストを幾つか登録してみる

ここで、同じく解凍したディレクトリの中にあるRealm Browserを開く
起動したらConnect to Object Server を選択

ServerURLにrealm://127.0.0.1:9080を指定

さらに

In order to connect as admin we need an admin access token. In the terminal running the server, look for a line starting with Your admin access token is:. Copy the long token key string

realm-object-serverを走らせてるターミナル上に記載されてるアクセストークンを
設定しろと書いてあるので確認してコピペしてAdmin Access Tokenに指定する

起動すると、先ほどデモアプリで入力したデータが登録されている
これはアプリ内のrealmファイルではなくサーバ側に連携されたデータになる

さらにiOS版のアプリもコードがあるのでシミュレータで起動してみる
ちなみに、ダウンロードしたディレクトリ内のiOSサンプルファイル群にPodfileが見つからなかったので Githubから持ってきた改めてソースを取得し直した

Github : RealmTasks

が、pod installでエラーになる
とりあえずPodfileのgit@としているところをhttpsに変更したら回避できた

pod 'Realm', git: 'https://github.com/realm/realm-cocoa.git', branch: 'master', submodules: true
pod 'RealmSwift', git: 'https://github.com/realm/realm-cocoa.git', branch: 'master', submodules: true

先ほどのmac版のデモアプリと同様にログインしてアプリを起動するとmacアプリで入力したデータが表示される
さらに片方でデータを追加、削除等の操作をすると
するともう片方のアプリで即座に反映するのが確認できた

実装手順をみてみる

詳細はこちらで
The Realm Mobile Platform

まず、Realm Object Serverへの接続URLを定義する

let serverURL = NSURL(string: "http://my.realmServer.com:9080")!

次に認証情報を設定する(ドキュメントをみると独自のid / pass以外にgoogleやfacebook、icloud等の認証も使えるっぽい)

let usernameCredential = Credential.usernamePassword(username: "username", password: "password", actions: [.createAccount])
let googleCredential   = Credential.google(token: "Google token")
let facebookCredential = Credential.facebook(token: "Facebook token")
let iCloudCredential   = Credential.iCloud(token: "iCloud token")

そして認証処理を行い成功した場合userインスタンスを取得する

User.authenticate(with: credential, server: serverURL) { user, error in
  if let user = user {
    // can now open a synchronized Realm with this user
  } else if let error = error {
    // handle error
  }
}

最後に、serverURLとuserインスタンスを使ってRealmのconfigurationを作成、realmインスタンスを作成する

// Create the configuration
let syncServerURL = URL(string: "realm://localhost:9080/~/userRealm")!
let config = Realm.Configuration(syncConfiguration: (user, syncServerURL))

// Open the remote Realm
let realm = try! Realm(configuration: config)

以降はこのrealmインスタンスを使えばデータの更新時にRealm Object Serverに連携されるようになる
ざっと実装方法を見ると単独で使用していた時とほとんど変わらずサーバ同期が簡単に可能になるようだった

その他雑感

コンフリクトについてはデフォルトでは以下のようなルールになるらしい
さらにルールのカスタマイズも出来るっぽい

Conflict Resolution

  • Deletes always wins
  • Last update wins
  • Inserts in lists are ordered by time
  • 一方で削除された場合は常に削除が優先される
  • 双方で更新がされた場合は最終更新が優先される
  • 同じ位置にデータが登録された場合は時間順になる

あとは、大規模なサービスになった時にどの程度のサーバスペックが必要なのかとかスケールアウトはどうするのか
ちなみに、↓を見るとproxy moduleはNginx等に置き換えることも可能なようだった Realm Object Server : Proxy
他にも気になるところは色々あるけど今回はとりあえずこの辺で。。。


iOSでstaticライブラリを開発していると画像等を同梱したい場合がある。
通常だとaファイルの他にbundleを作成する必要があるが、
これをaファイルの中に同梱する方法を調べた。

やり方としては、画像などのバイナリデータを一旦16進数情報に変換、
ライブラリのソース内にchar配列として保持し、
それを実行時にNSDataで読み込みUIImageに変換する感じになる。

バイナリデータを16進数に変換する

xxdコマンドでバイナリデータを16進数に変換できる。
例えばあるpngファイルをxxdコマンドにかけると以下のような出力になる。

$ xxd original.png

00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR
00000010: 0000 02ee 0000 0536 0802 0000 0045 b5a4  .......6.....E..
00000020: 5100 0000 0173 5247 4200 aece 1ce9 0000  Q....sRGB.......
00000030: 4000 4944 4154 7801 ecbd 0794 1dc7 79ef  @.IDATx.......y.
00000040: 79f3 e4c1 2067 2232 8101 cc20 0966 5282  y... g"2... .fR.
00000050: a8b0 b244 59d2 932c 796d e959 b2fd deca  ...DY..,ym.Y....
00000060: a215 8eed b59f c33b 4eeb f5f3 8ab6 f73c  .......;N......<
00000070: 9f77 1cf6 59b6 ec5d db92 455a 6612 730e  .w..Y..]..EZf.s.
00000080: 2041 82c8 3963 1027 cfdc b4bf 3b05 140b   A..9c.'....;...
00000090: dd7d efdc 7bfb cef4 0cf0 ef83 33a8 aeae  .}..{.......3...

さらに、pオプションをつけるとプレーンな16進数出力になる。

$ xxd -p original.png

89504e470d0a1a0a0000000d49484452000002ee00000536080200000045
b5a451000000017352474200aece1ce900004000494441547801ecbd0794
1dc779ef79f3e4c1206722328101cc2009665282a8b0b24459d2932c796d
e959b2fddecaa2158eedb59fc33b4eebf5f38ab6f73c9f771cf659b6ec5d
db92455a6612730e204182c839631027cfdcb4bf3b05140bdd7defdc7bfb
cef40cf0ef8333a8aeaefaaaead7b7bbfefd557575fc81071e88c7e3b132
5bb15834474863c3c4d8b027be8c9980e80a8506a46e5c94ad79e34cca92
08888008888008348c4054fd63c31a50bb218f9630bb2e8742a19048245c
c36e6f9e62c7ee938ed46e660e99ddd1542559e31e65d7e6750ba81cf658
a89cb8e147a32dbde1cd914111100111100111983c042aab82725db02797

逆に、16進数出力結果をバイナリ形式に変換することもできる。
その場合はrオプションをつける。

xxd original.png > original.hex
xxd -r original.hex > revert.png

オプション無しの出力結果の他に、-pの出力結果もrevertできる。
その場合はpオプションを付与する。

xxd -r -p original.hex > revert.png

画像データをCのchar配列形式で出力

ここからが本題で、xxdコマンドにiオプションを付与すると以下のように画像データをCのchar配列形式で出力できる。

$ xxd -i original.png

unsigned char original_png[] = {
  0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
  0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x02, 0xee, 0x00, 0x00, 0x05, 0x36,
 // 中略
  0x80, 0x10, 0x10, 0x02, 0x42, 0x20, 0x4f, 0x08, 0xfc, 0xff, 0x2c, 0x24,
  0xa4, 0xb5, 0x8f, 0x74, 0x05, 0x08, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45,
  0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};
unsigned int original_png_len = 954870;

これをNSDataに食わせて、それをUIImageとして扱うことでソースから画像を生成することができる。

+ (UIImage *)revertHexImage
{
  unsigned char original_png[] = {
    0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
   // 中略
    0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
  };
  unsigned int original_png_len = 954870;

  // 画像データからNSDataを生成し、それを元にUIImageを生成する
  NSData *data = [NSData dataWithBytes:original_png
                                length:original_png_len];
  return [UIImage imageWithData:data];
}

今回は説明簡単にするためにメソッドの中にベタ書きしたけど、
実際には別ファイルに書き出してincludeするとかしたほうが良さそう。


1万人限定に反応してアカウントを作ってしまったので、
とりあえずbotを動かしてみるところまでやってみた

今回のbot環境を構築するにあたってSSL対応と固定IPが必要という2点が悩みどころ

現状トライアルなので、できるだけ安く、できれば無料で環境構築をしたい
ということで、以下の二つを試してみた

AWS Lambda + API Gateway

API Gatewayはデフォルトでhttpsなので
SSLの点では良さそうと最初に思い試してみた
詳細な実装方法はこちらの記事が参考になる
AWS Lambda + API GatewayでLINE BOT APIを叩いてみた

ただし、API GatewayはIPを固定できないので時間が経つと変わってしまう
別途プロキシをEC2なりで立てる必要が出てくるので今回は諦めた

Heroku + fixie

最終的にこの形に落ち着いた
HerokuはSSLにも対応している
さらに、fixieというアドオンを使ってプロキシを挟むことでIPの固定化もできる
しかも最小構成なら無料という素晴らしさ

awsの世界でサーバーレスっていうのも捨てがたいけど、
herokuの場合はnodeとかpyton以外も使える自由度もあるし、
これが今のところ一番良さそうかなと思っている

以下、ruby(sinatra)+HerokuでのLine Bot環境構築手順のメモ

heroku環境を構築する

herokuを利用するのが初めての場合は以下からアカウントを作成
https://www.heroku.com/

続いてherokuをコマンドラインから扱うためのheroku toolbeltをインストール
https://toolbelt.heroku.com/

上記リンクのgetting startに従ってログインから試してみる

$ heroku login
=> 途中でemailとpasswordを要求され、ログインする

とりあえずアプリをデプロイしてみる

$ mkdir firstsample && cd $_
$ heroku create

成功するとgitのURLとサイトURLが出力されるのでサイトURLへブラウザからアクセスしてみる

Heroku | Welcome to your new app!

みたいな文言が出るページが表示される
これでheroku上にアプリを公開する準備が整った

sinatraアプリを作成する

Gemfileを生成

bundle init

Gemfileに書きを追加

gem 'sinatra'

sinatraをインストール

bundle install --path vendor/bundle

シンプルなアプリをapp.rbに記述

# coding: utf-8

require 'sinatra'

get '/' do
  'hello!'
end

ローカルで起動してみる

bundle exec ruby app.rb

ブラウザでアクセス、localhost:4567にアクセス、helloが表示されればOK

アプリをHerokuに公開する

herokuでsinatraを起動するためにProcfileを追加

# Procfile
web: bundle exec ruby app.rb -p $PORT

gitを設定

git init

herokuのリモートリポジトリを設定(heroku createした時に出力されたgitのURL)

heroku git:remote -a xxxxx
# 確認
git remote -v
=> set git remote heroku to https://git.heroku.com/xxxxx.git

gitignore設定

.DS_STORE
*.swp
/Gemfile.lock
/vendor/
/.bundle

作成したファイルをgitにコミット

git add
git commit -m "initial commit"

herokuのリモートリポジトリにpush

git push heroku master

成功すると以下のようなメッセージが表示される

~ 中略 ~
remote:        https://xxxxx.herokuapp.com/ deployed to Heroku
~ 中略 ~

表示されたURLにアクセスして先ほどのhelloが表示されればデプロイ完了

fixieで固定IP対応(プロキシ)

herokuのアドオンを使用する場合は無料のアドオンしか使わなくても最初にクレジットカードの登録が必要になる

こんなエラーメッセージが出る

Please verify your account to install this add-on plan (please enter a credit card) For more information, see https://devcenter.heroku.com/categories/billing Verify now at https://heroku.com/verify

カード情報を登録後以下のコマンドを実行するとアドオンが追加されてIPアドレスが表示される

$ heroku addons:create fixie:tricycle
=> Your IP addresses are xx.xx.xx.xx, xx.xx.xx.xx

表示されたIPアドレスをLINE DevelopersのServer IP Whitelistに追加する

LINE BOTを実装する

最初にLine developersのbotのcallbackURL
herokuで作成したアプリのURLを設定する
サンプルはとりあえずオウム返し

# coding: utf-8

require 'sinatra'
require 'json'
require 'base64'
require 'net/https'
require 'uri'

post '/' do

  channel_secret = "自分のChannel Secret"
  http_request_body = request.body.read
  hash = OpenSSL::HMAC::digest(OpenSSL::Digest::SHA256.new, channel_secret, http_request_body)
  signature = Base64.strict_encode64(hash)
  # requestヘッダを取得
  http_headers = request.env.select { |k, v| k.start_with?('HTTP_')}

  if signature != http_headers["X-LINE-CHANNELSIGNATURE"]
    {status: :error}.to_json
  end

  params = JSON.parse http_request_body

  from = params["result"][0]["content"]["from"]
  text = params["result"][0]["content"]["text"]

  # リクエストヘッダ
  request_headers = {
      "Content-Type" => "application/json",
      "X-Line-ChannelID" => "自分のChannelID",
      "X-Line-ChannelSecret" => channel_secret,
      "X-Line-Trusted-User-With-ACL" => "自分のMID"
  }

  # リクエストボディ
  request_params = {
      "to" => [from],
      "toChannel" => 1383378250, # 固定値
      "eventType" => "138311608800106203", # 固定値
      "content" => {
          "contentType" => 1,
          "toType" => 1,
          "text" => text
      }
  }

  # message send endpoint
  uri = "https://trialbot-api.line.me/v1/events"
  uri_parsed = URI.parse uri

  # ref : https://devcenter.heroku.com/articles/fixie
  _, username, password, host, port = ENV["FIXIE_URL"].gsub(/(:|\/|@)/,' ').squeeze(' ').split
  # fixieのproxy情報とline botのendpoint情報をセット
  http = Net::HTTP.new(uri_parsed.host, uri_parsed.port, host, port, username, password)

  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  http.post(uri_parsed.path, request_params.to_json, request_headers)

  data = {status: 'OK'}
  data.to_json

end

LINE Developers のBot Channelsページの下の方にある
QRを使ってbotをLINEで友達追加してbotに話しかけてみる
botからレスがくれば無事成功

気づいたらLine Botの説明というよりHerokuの使い方説明になってしまった。。。


swiftは2016/4現在、3系に向けて開発が進んでいて、
develompemtバージョンはどんどん最新のスナップショットが公開されている

swift package managerを試したりする場合はdevelopmentバージョンが必要だし
swiftのバージョンを簡単に切り替えられるswiftenvを導入してみた
(OSXではXcode7.3でtoolchainsから切り替えられるようになった)

swiftenvのインストール

Linux(Ubuntu)とOSXそれぞれで導入を試してみた

ubuntuの場合

今回はvagrantでubuntu環境を構築してその上でswiftを入れてみる

現在Ubuntuだと15.10と14.04向けのスナップショットがある
https://swift.org/download/#latest-development-snapshots

Ubuntu14.04のvagrant box
https://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box

Vagrantfileはこんな感じ

$ grep -v -e '\#' -e "^$" Vagrantfile

Vagrant.configure(2) do |config|
  config.vm.box = "ubuntu1410"
  config.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box"
end

パッケージの最新化

sudo apt-get update

swiftenvインストールするために必要なgitを先にインストール

sudo apt-get install git

SwiftのSystem Requirementsに従って以下をインストール
(これ入れておかないとswiftは入るけどビルドで失敗する)
https://github.com/apple/swift/blob/master/README.md

sudo apt-get install git cmake ninja-build clang python uuid-dev libicu-dev icu-devtools libbsd-dev libedit-dev libxml2-dev libsqlite3-dev swig libpython-dev libncurses5-dev pkg-config

swiftenv導入
https://github.com/kylef/swiftenv

git clone https://github.com/kylef/swiftenv.git ~/.swiftenv

echo 'export SWIFTENV_ROOT="$HOME/.swiftenv"' >> ~/.bash_profile
echo 'export PATH="$SWIFTENV_ROOT/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(swiftenv init -)"' >> ~/.bash_profile
source .bash_profile

OSXの場合

homebrewを使ってインストール

brew install kylef/formulae/swiftenv
echo 'eval "$(swiftenv init -)"' >> ~/.bash_profile

swiftのインストール

swiftenvのインストールが完了したら続いてswift自体のインストールをおこなう

swiftenv install swift-DEVELOPMENT-SNAPSHOT-2016-03-24-a

引数はsnapshotのURLを直接渡してもOK

swiftenv install https://swift.org/builds/swift-2.2-release/ubuntu1404/swift-2.2-RELEASE/swift-2.2-RELEASE-ubuntu14.04.tar.gz

installコマンドに渡すswiftスナップショットの情報はここで確認出来る
https://swift.org/download/#snapshots

swiftのバージョンを切り替えてみる

複数のswiftバージョンを無事導入できたら切り替えも試してみる

まず、導入済みのswiftバージョン一覧を確認する

$ swiftenv versions

* 2.2-RELEASE (set by /home/vagrant/.swiftenv/version)
  DEVELOPMENT-SNAPSHOT-2016-03-24-a

swiftのバージョンを確認

$ swift -version
Swift version 2.2 (swift-2.2-RELEASE)

swiftのバージョンをdevelopment-snapshotに変更する

$ swiftenv global DEVELOPMENT-SNAPSHOT-2016-03-24-a

swiftのバージョンを確認

$ swift -version
Swift version 3.0-dev (LLVM b010debd0e, Clang 3e4d01d89b, Swift 7182c58cb2)

無事切り替わった

ちなみに、ヘルプを見ると以下のようになっており

$ swiftenv -h

version   Displays the current active Swift version
versions  Lists all installed Swift versions
global    Sets the global version of Swift
local     Sets the local application-specific version of Swift
install   Installs a version of Swift
uninstall Uninstalls a specific Swift version
rehash    Installs shims for Swift binaries

localオプションを使用すると特定のディレクトリ配下のswiftバージョンのみ変更できる

swiftenv local xxxx

実行したディレクトリ直下に.swift-versionファイルが生成され、
DEVELOPMENT-SNAPSHOT-2016-03-24-aなどの
snapshotバージョンが記録され、そのバージョンのswiftが動作することになる


try! swift 2016に参加してきました

http://www.tryswiftconf.com/

3/2-4の3日間、500人超の参加者が集まった
国内のswift関連としては非常に大規模なイベントだった
せっかくの貴重な体験だったので自分なりに感じたことをまとめてみた

全体的な感想

全体的な感想としては、各セッションのバリエーションが豊富で
swift自体(POPとか)、テスト周り、デザイナーとの協業、
アイコンのライブデザイン、tvOS、swiftコミュニティへの貢献について等々、
様々な分野の話が聞けて非常に有益なカンファレンスだった

POP(Protocol-Oriented Programing)については
かなり多くのセッションで扱われていたし、
抽象化、モジュール化を推し進めていく中でstoryboardは使わないという
選択をしている人も結構いるんだなぁという発見もあった
逆にデザイナーと協業していく上ではstoryboardは非常に有効という意見もあって
いろいろ考えさせられるところも多かった
どういうアプリを作るのか、どういう体制で開発するのか、
状況に合わせるべきって話だとは思うけど

本筋とずれるけど、同時通訳が素晴らしすぎた
技術系の翻訳って素人目にも難しいと思うのに、
ほとんどのセッションでちゃんと同時通訳された

サーバーサイドswift

個人的に一番興味があったのは
swiftのオープンソース化による他分野への取り組みというところだったが、
サーバサイドswift関連ではwebアプリケーションを動かす フレームワークもIBMのKituraPerfect等、既にいくつかリリースされているし
Caesar Wirthのセッションにあったvaporという、
rubyでいうところのsinatraのような軽量フレームワークも存在しているようだった

セッションで使ったデモコードが既に公開されていた https://github.com/cjwirth/trySwiftServer

swiftで機械学習

Diana Zmudaのセッションでは
swiftで機械学習プログラミングを行う試みをしていてとても興味深かった
swiftはコンパイル言語で高速なことや再帰処理や抽象化が行いやすいことから
現状の機械学習実装のファーストチョイス(python、C++等)候補の中に
割って入るだけのポテンシャルはあるのではないかという話だった

その他

自分は都合が合わず参加できなかったが最終日の打ち上げパーティは、
海外からの参加者と日本人参加者とのコミュニケーションを図る試みとして バイリンガルな人を間にいれてより深い交流をする取り組みなどもしていたようで、
そういった意味でも素晴らしいカンファレンスだったのではないかと思う
あと、海外の人ポケモン好きすぎ

まとめ

自分自身、swiftの理解度が低い状態での参加だったので、
理解が追いつかないものもあったけど、
swiftの面白さとか可能性を体感できたのはよかった

来年の開催は現状未定みたいだけどぜひ開催してもらいたいし、
その時はまた参加したい