Hatena::ブログ(Diary)

Over&Out その後 このページをアンテナに追加 RSSフィード

2017-07-25

Core ML vs MPSCNN vs BNNS #fincwwdc

昨日FiNCさんのオフィスで開催された「WWDC2017振り返り勉強会」で『Core ML vs MPSCNN vs BNNS』というタイトルでLTしてきました。



iOS 11で追加されたCore MLが非常に注目を集めていますが、「既存の機械学習フレームワークを使って学習させたモデル(のパラメータ)をiOS側に持ってきて推論を実行する」ということ自体はiOS 10からあって、そこに不便さがあったので広まらず、Core MLでやっと使われるようになった、という側面はもちろんありつつも、いややはりそれでも単にそういうことがiOS 10できるようになったということ自体が知られていなかっただけなのではと。




確かに自分も Metal Performance Shaders のCNN APIを用いた機能を実装しようとしたときに、情報があまりに少なく、何ができて何ができないのか、どうやるのかがよくわからなかった、ということがありました。


で、そのへんをシンプルに説明したら、もっと興味をもつ人も出てくるんじゃないかなと思い、実装手順を3ステップで解説してみました。


  • Step 1: モデルをつくる

f:id:shu223:20170725070344j:image:w600


  • Step 2: ネットワークを実装する

f:id:shu223:20170725070345p:image:w600

f:id:shu223:20170725070347p:image:w600


  • Step 3: 推論の実行処理を書く

f:id:shu223:20170725070346p:image:w600



意外と簡単そう/使えそうではないでしょうか?



ところが・・・


f:id:shu223:20170725070348p:image:w600


f:id:shu223:20170725070349p:image:w600


っていうつらさがあり、他にも色々と面倒な点があり、やっぱりCore ML & Visionのおかげで各段に便利になった、という話でした。



最後にAccelerateフレームワークのBNNSの使いどころについてWWDC17のMetal Labで聞いた話が出てきます。


f:id:shu223:20170725073011p:image:w600



(登壇の様子 by yucovin さん)


なぜこの話をしたのか

上の説明だけを読むとまるでMPSCNNの普及活動をしている人みたいですが、動機はそこではなくて、来月登壇する アメリカのiOSカンファレンス で、"Deep Learning on iOS" というタイトルで発表することが決まっていて、


f:id:shu223:20170725070350p:image:w600


で、これってWWDC17開催前にCfPを出して通ったやつなので、応募当時はMPSCNNの話をするつもりだったのに、Core MLが出てしまって事情が変わってしまった、どうしよう、という。。


じゃあCore MLの話をすればいいじゃん、と思うかもしれません。その通りかもしれません。でも僕自身がまだあまり試せてないのと、Core MLの場合はiOS側が便利になりすぎてむしろ機械学習フレームワーク側(Kerasとか)がメインになるけどそっちは初心者とすらいえないレベルだし、ってことでMPSCNNの方に解説を寄せよう、という試行錯誤の中で「こんな切り口はどうだろう」と考えたのが昨日の発表なのでした。


実際に話してみて、正直なところコレジャナイ感はちょっとありました。もうちょっとワクワクする感じにならないか、実例とかデモとかを増やす感じでブラッシュアップしてみようと思ってます。(来月のカンファレンスは発表時間が45分もあるので、個々の解説ももうちょっと丁寧にやる予定)


おわりに

WWDCには参加したものの、そのままアメリカにいたのでこういう振り返り勉強会に参加できず(※例年勉強会発表ドリブンで新APIを勉強していた)、あっちではこういうLTで気軽に登壇できる勉強会もあまりないので、1ヶ月後というわりとWWDC勉強会としては珍しいタイミングで開催された本イベントは大変ありがたかったです。


LTですが非常に中身の濃い発表が多く、勉強になりました。懇親会で出てくる料理もさすがFiNCさん、ヘルシーで美味しいものばかりでした。どうもありがとうございました!


追記:BNNSについていただいたコメント

Facebookでsonsonさんからコメントいただきました。


f:id:shu223:20170725124035p:image:w400


BNNSとMPSCNNの使い分けは,難しいけど,電力と計算スピードのバランスかなぁと思います.


あと,GPUのメモリとCPUのメモリ間の転送に時間がかかるので,オーバーヘッドをカバーできるくらい,データや計算量大きくないと,GPUは意味ないでしょう.

BNNSは,SIMDなので,これも当然CPUのコンテキストスイッチのためのオーバーヘッド(ノーマルモードとSIMDモードの切り替え)があるのですが,GPUほどではないので,小さいネットワークだとGPUよりBNNSの方が速いというのはありそうです.


まぁ,なんで,電力と速度の限界に挑戦する場合は,ベンチマークとって極限を目指そうって感じですかねw


なるほど、単に「CNNの計算はGPUが向いてるでしょ」とか「Appleの人も言ってた」とかってだけでBNNSのことは忘れようとか言ってちゃいけないですね。確かにGPU↔CPU間の転送速度のボトルネックとGPUによる高速化がどれぐらい見込まれるかのバランスによる、というのは非常に納得です。また「SIMDモードへの切り替えのオーバーヘッド」(はあるがGPUとの転送ほどではない)というあたりもまったく考慮できてなかったところです。


CPU、GPUの負荷がそれぞれどれぐらいか、というのはXcodeで簡単に見れますが、GPU↔CPU間の転送状況を見る方法もあるのでしょうか?GPUまわりの計測・デバッグ手法はもうちょっと勉強したいところです。


ちなみにsonsonさんは例の(私も参加させていただく)クラウドファンディング本で「Core ML」のパートを担当されます。


7/28まで買えるみたいなのでまだの方はぜひご検討を!


2016-11-13

iOS/MetalのシェーダをWebGL/GLSLから移植する

Metalでグラフィック処理を行うにしろ並列演算を行うにしろ、GPUに処理をさせるためのシェーダを書かないといけないわけですが、これがまだ情報が少なくて、「こういうシェーダを書きたいんだけど、誰かもう書いてないかな・・・」というときに参考になる近いものとかはそうそう都合よく出てこないわけです。


ただ、WebGL/GLSLの情報はググると山ほどあって、GLSL Sandbox という、Web上で編集できてプレビューできてシェアできるサイトもあり、何がどうなってそうなるのか理解できない難しそうなものから、ただの円といったシンプルなものまで、既に偉大な先人たちのサンプルがたくさんアップされています


f:id:shu223:20161113192400j:image:w600



Metalのシェーダというのは正しくは Metal Shading Language といいまして、C++をベースとする独自言語なのですが、まー概ねGLSLと一緒です。


実際にやってみたところ、GLSL -> Metal Shader の移植はほとんど単純置き換えで済み、Swift 2をSwift 3に直すのよりも簡単という感覚でした。


f:id:shu223:20161113192821j:image


いずれも画像等のリソースは使用しておらず、iOSデバイス上で、GPUによってリアルタイム計算されたものです。


実際のところ自分はゲーム開発やVJをやったりしているわけではないので、こういう派手なエフェクトではなく、線とか円とかグラデーションとかのもっと単純なものをMetalで動的描画したいだけだったりするのですが *1、移植が簡単に行えることがわかっていれば、GLSLを参考にMetalシェーダを書けるというのはもちろんのこと、GLSL Sandboxで動作確認しつつシェーダを書いて、できたものをiOSに移植する、ということもできるので、個人的にはMetalシェーダを書く敷居がグッと下がりました。


というわけで、以下GLSLのコードをMetalに移植する際のメモです。


雛形となるプロジェクトを作成する

プロジェクトテンプレートに "OpenGL ES" はあっても "Metal" というのはないのですが、"Game" というのがあり、次の画面で [Game Technology] の欄で Metal を選択すると、シンプルなMetalのプロジェクトが生成されます。


f:id:shu223:20161113194605j:image:w500


これをベースに、GLSLのコードを移植して来やすいように次のように手を入れました。(現時点ではGLSL SandboxのコードをiOSで動かしてみるべく、フラグメントシェーダだけに手を入れています)


1.「画面の解像度」をフラグメントシェーダに渡す

多くのGLSLのサンプルでは、xy平面における座標を 0.0〜1.0 に正規化した状態で取り扱っています。ピクセルベースの座標値をシェーダ側で正規化できるよう、画面の解像度をシェーダに渡すよう修正します。


まずはシェーダ側。下記のように引数に float2型の "resolution" を追加します。

fragment float4 practiceFragment(VertexInOut     inFrag      [[stage_in]],
                                   constant float2 &resolution [[buffer(0)]])

次にSwift側。下記のようにバッファを用意して、

var resolutionBuffer: MTLBuffer! = nil
let screenSize = UIScreen.main.nativeBounds.size
let resolutionData = [Float(screenSize.width), Float(screenSize.height)]
let resolutionSize = resolutionData.count * MemoryLayout<Float>.size
resolutionBuffer = device.makeBuffer(bytes: resolutionData, length: resolutionSize, options: [])

フラグメントシェーダ関数の引数にバッファをセットします。

renderEncoder.setFragmentBuffer(resolutionBuffer, offset: 0, at: 0)

こんな感じで正規化した座標値を算出します。

float p_x = inFrag.position.x / resolution.x;
float p_y = inFrag.position.y / resolution.x;
float2 p = float2(p_x, p_y);

GLSL Sandboxはスクリーンが必ず正方形なのですが、iOSデバイスはそうではないので、比率が変わらないようどちらもx方向の解像度(つまり幅)で割っています。


2. 「経過時間」をフラグメントシェーダに渡す

ほとんど同様です。シェーダ側では、引数に float型の "time" を追加します。

fragment float4 practiceFragment(VertexInOut     inFrag      [[stage_in]],
                                   constant float2 &resolution [[buffer(0)]],
                                   constant float  &time       [[buffer(1)]])

Swift側。下記のようにバッファを用意して、

var timeBuffer: MTLBuffer! = nil
timeBuffer = device.makeBuffer(length: MemoryLayout<Float>.size, options: [])
timeBuffer.label = "time"

フラグメントシェーダ関数の引数にバッファをセットします。インデックスが変わる点に注意。

renderEncoder.setFragmentBuffer(timeBuffer, offset: 0, at: 1)

時刻の更新時にバッファを更新します。

let pTimeData = timeBuffer.contents()
let vTimeData = pTimeData.bindMemory(to: Float.self, capacity: 1 / MemoryLayout<Float>.stride)
vTimeData[0] = Float(Date().timeIntervalSince(startDate))

GLSLを移植する際の改変点

GLSL を Metal に移植してくる準備が整いました。ほとんど同じ、と書きましたが細部はやはり違います。以下、大まかな移行ポイントです。

  • GLSLのフラグメントシェーダでは、最後に gl_FragColor にvec4値をセットすることで出力とするが、return で float4 なり half4 なりを返す

(例)GLSLの場合

gl_FragColor = vec4(color, 1.0 );

Metalの場合

return float4(color, 1.0);
  • 関数はプロトタイプ宣言が必要
    • これがないと、ビルド時に "No previous prototype for function 〜" というwarningが出る
  • vec2, vec3, vec4 -> float2, float3, float4
  • ivec2, ivec3, ivec4 -> int2, int3, int4
  • mat2, mat3, mat4 -> float2x2, float3x3, float4x4
    • コンストラクタも微妙に違う(float2x2ならfloat2を2つ渡す。公式ドキュメントp15あたり)
  • const -> constant
  • mouse.x, mouse.y -> 適当に0.5とか

また出てきたら追記します。


GLSL Sandboxから移植してみる

GLSL Sandboxでいくつかピックアップして上記手順でMetalに移植し、iOSで動かしてみました。それぞれの移植にかかった時間は5分ぐらいです。ほとんど単純置き換えで済みました。


http://glslsandbox.com/e#36694.0

f:id:shu223:20161113193424g:image


http://glslsandbox.com/e#36538.3

f:id:shu223:20161113193507g:image

※デフォルトのITERATIONS 128では3fpsぐらいしか出なかったので、ITERATIONS 64に変更


http://glslsandbox.com/e#36614.0

f:id:shu223:20161113194708g:image


まとめ

GLSLをMetalに移植する手順について書きました。


上にも書きましたが、Metalをさわりはじめたときはシンプルなものすら書き方がわからなくて四苦八苦したので、「GLSLのコードも参考になるんだ!」と気付いたときはMetalの敷居がグッと下がった気がしました。


MetalはiOS 10からニューラルネットワーク計算のライブラリも追加されたこともあり、自分的に今一番熱い分野です。引き続きシェーダの具体的な書き方や、SceneKitを併用して3D空間内のノード上にMetalでテクスチャを動的描画する方法、デバッグツールの使い方等、色々と書いていきたいと思います。



*1:線とか円とかの単純なものでも、カメラプレビューで動的かつリアルタイムに、かつ他の重い画像処理と一緒に、といった場合、そして描画数が多かったり毎フレームの更新が必要な場合、やはりUIKitやCoreGraphicsでは厳しい場面が出てきます。

2016-07-17

【書評】初学者はもちろん、中級者にもオススメのAuto Layout解説書

著者の川邉さん(@jeffsuke)および出版社のリックテレコム様より『よくわかるAuto Layout - iOSレスポンシブデザインをマスター』をご献本いただきました。


f:id:shu223:20160717140912j:image:h600


タイトルの通りAuto Layoutの解説書で、豊富な図やスクリーンショットを用いて、非常にわかりやすく書かれています。前書きによると、本書の対象読者は

過去一度はXcodeを用いてiOSアプリをつくったことがあるが、Auto Layoutとサイズクラスを用いたAdaptive Layoutと言われると尻込みしてしまう開発者

とのこと。


なんかもうベストセラーになったりしてるらしく、


AutoLayoutの必要性は今更僕が説くことでもないですし、監修は名著『UIKit詳解リファレンス』の著者 所さん


ということで、これはもう

  • iOSアプリ開発に携わっている(携わろうとしている)
  • AutoLayoutがよくわかってない

という方はもう買うしかないでしょう。買いましょう!



よくわかるAuto Layout  iOSレスポンシブデザインをマスター
川邉 雄介
リックテレコム
売り上げランキング: 14,747

(Kindle版もありますが、固定レイアウトのようです。)



・・・と、Auto Layoutに自信のない初学者の方には当然のようにオススメなのですが、AutoLayoutがそれなりにわかっている(と自分では思っていた)僕が読んでも、勉強になったポイントがいくつもありました。以下で紹介していきます。


Content Hugging Priority と Content Compression Resistance Priority と Instrinsic Content Size

250とか750とかのデフォルト値が振られてるアレです。


f:id:shu223:20160718095410j:image:w250


Auto Layoutはわかっているつもりでも、このへんの理解が曖昧なままwarningが出たときになんとなく優先度をいじってみたりしている、みたいな人は実は多いのではないでしょうか。私がまさにそうでしたが、本書では詳細かつ明解に解説されていて、曖昧に理解していたところがスッキリしました。以下メモ。

  • それぞれ Instrinsic Content Size についての「大きくなりにくさ」「小さくなりにくさ」を意味している*1
  • NSLayoutConstraintの優先度の値が同値の場合は、NSLayoutConstraintによる制約が優先される

レイアウトのライフサイクル(p35〜)

1. 制約の更新、2. フレームの更新、3. レンダリングというiOSにおけるレイアウトの3つのステップについて詳細に解説されています。


恥ずかしながら `updateConstraintsIfNeeded`, `layoutIfNeeded`, `layoutSubviews`, `setNeedsDsplay` といったメソッドでそれぞれ何が行われるのか、または何が行われないのか、といったことの理解が曖昧だったのですが、順序立ててわかりやすく解説されていて非常に勉強になりました。

  1. 制約の更新
    • `updateConstraints` が呼ばれる
    • ボトムアップ(子ビューから親ビュー)に制約の計算が実行される
    • 制約の有効/無効化、優先度変更、追加/削除などをトリガに実施される
    • この制約の更新を明示的に実行したい場合には `updateConstraintsIfNeeded` もしくは `setNeedsUpdateConstraints` を呼ぶ
  2. フレームの更新
    • `layoutSubviews` が呼ばれる
    • `layoutSubviews` が呼ばれると `updateConstraintsIfNeeded` も呼ばれ、必要に応じて制約の更新も行われる
    • トップダウン(親ビューから子ビュー)に実施される
    • ビューのフレームの変更・サブビューの追加・削除等をトリガに実施される
    • 明示的に実行したい場合には `layoutIfNeeded` もしくは `setNeedsLayout` を呼ぶ
    • `layoutSubview` をオーバーライドすると、`super.layoutSubviews()` の時点で既に制約の更新が完了しているので、そのレイアウト情報を用いて何らかの処理を実行したい場合に有効(+この方法で無限スクロールを実現する例)
  3. レンダリング
    • `drawRect:` が呼ばれる
    • 明示的に実行したい場合には `setNeedsDisplay` または `setNeedsDisplayInRect:` を呼ぶ

また `viewWillLayoutSubviews` と `viewDidLayoutSubviews` についても、それぞれの段階では(レイアウトサイクルの)どのステップは完了していて何は完了していないのか、というところの理解がスッキリしました。

  • viewWillLayoutSubviews
    • この時点では、サブリューのレイアウトは決定されていないが、画面の向きは決定している
    • この後、「制約の更新」と「フレームの更新」が実施される
  • viewDidLayoutSubviews
    • layoutSubviews による「フレームの更新」が完了すると呼ばれる
    • この後にレンダリングが行われるので、viewDidAppearはこれより後に呼ばれる

UIWindowの話

直接Auto Layoutとは関係ないですが、iOSでUIを実装するにあたってUIWindowに関して知っておいた方がいいことが簡潔に解説されています。`windowLevel` による重なり順の話とか、画面サイズを取得する場合に

UIScreen.mainScreen().bounds

UIScreen.sharedAppliation().keyWindow?.bounds

はどう違うか、とか。


UIStackView

iOS 9で追加された UIStackView についても、10ページを使ってしっかり解説されています。


ちなみに

Start with Stack View, use constraints as needed

まずはStack Viewを使って、必要に応じてconstraintsを使おう


WWDCのセッションでも言われていたとか。


コードで制約を設定する & NSLayoutAnchor

僕は基本的にはIB/Storyboardで制約を貼るのですが、仕事柄多くのプロジェクトに関わるので、「IB/Storyboard使わない派」な方々のコードに触れる機会も度々ありまして、コードで制約を設定することもたまにあります。


本書では、NSLayoutConstraint のイニシャライザを使う方法、VFL (Visual Format Language)を使う方法、iOS 9 で追加されたNSLayoutAnchorを使う方法等、まるまる一章を使ってその方法が解説されています。


ちなみにそろそろiOS 9の機能を使ってもいい(iOS 8を切ってもいい)空気になってきたと個人的には感じていますが、NSLayoutAnchor を使うと超シンプルかつ直感的にレイアウト制約をコードから設定できるようになります。


(before)

NSLayoutConstraint(item: subview,
                   attribute: .Leading,
                   relatedBy: .Equal,
                   toItem: view,
                   attribute: .LeadingMargin,
                   multiplier: 1.0,
                   constant: 0.0).active = true

(after)

let margins = view.layoutMarginsGuide
subview.leadingAnchor.constraintEqualToAnchor(margins.leadingAnchor).active = true

本章末尾のコラムではAuto Layoutを簡潔に書くためのオープンソースライブラリもいくつか紹介されています。


その他、細かい話

Auto Layout のアルゴリズム

この線型方程式系を解くアルゴリズムは、Greg J. Badros と Alan Borning、Peter J. Stuckey によって2001年に発表された、制約充足問題を説く Cassowary というツールキットで用いられたアルゴリズムを使用しています。(p.15)

明日から開発に活かせる、という話ではないのですが、こんなこともサラッと書かれていて、しっかり調べて執筆されている書籍なんだなということが伺えます。


外接矩形(alignment rects)をデバッグ時に確認する

[Edit Scheme] > [Run] > [Arguments] の [Arguments Passed On Launch] に `-UIViewShowAlignmentRects` を追加してチェックを入れると、実行時に外接矩形が表示される、というTips。


NSLayoutAttributeのLeft/RightとLeading/Trailingの違い

基本的にLeading/Trailingを使ってたので意識したことなかったですが、アラビア語とかヘブライ語のように右から左へ書く言語ではLeading/TrailingがそれぞれRight/Leftと同値となるそうです。


制約の矛盾が実行時に起きたときのエラーメッセージの見方

実行時にぶわーっと出てくるエラーメッセージ、ありますよね。僕は最近だとAutoLayoutを使ったアニメーションをやろうとして何かが間違っててこれが出てきました。で、メッセージ読んでもどの制約が悪いのかわかりづらい、と思ってたんですが、制約にちゃんとidentifierふればいいみたいです。(p86)


Xcode 7 の Storyboard Reference

Xcode 7 で追加された [Refactor to Storyboard...] メニューからの Storyboard の分割方法もしっかり書かれています。


Auto LayoutのデバッグTips

UIView に `exerciseAmbiguityInLayout()` なるメソッドがあり、これを用いると曖昧な制約を持つビューオブジェクトのフレームを、制約を満たす範囲でランダムに変更してくれるそうです。


他にもオブジェクトが持つインスタンス変数名と値を吐いてくれる `_ivarDescription` メソッド*2とか、ビューデバッガーの [Show Constraints] 機能とか、知らなかったことが色々と書いてありました。


まとめ

書籍『よくわかるAuto Layout』を紹介させていただきました。Auto Layoutがよくわかってない人を対象として書かれた書籍ですが、中級者にも勉強になる部分は多いと感じました。


本をまるっと一冊通読してAuto Layoutを勉強するというのはなかなか難しい、と感じる方もいらっしゃるかもしれません。が、目次を見れば分かる通り、Auto Layoutについてかなり網羅的かつ本質的に解説されてますので、リファレンス的に手元に置いておくのもいいのでは、と思います。


気になった方はぜひ!


よくわかるAuto Layout  iOSレスポンシブデザインをマスター
川邉 雄介
リックテレコム
売り上げランキング: 14,747


*1:この訳はAppleのドキュメントにあったのか著者のオリジナルかわかりませんが、簡潔で秀逸だなと思いました

*2:※Privateです

2016-03-03

「try! Swift」1日目の復習 #tryswiftconf

iOSのカンファレンス「try! Swift」がいよいよ昨日から始まりました。恥をさらすようですが、半分ぐらいのセッションでリアルタイムについていけませんでした。。


ので既に上がっている発表資料やレポート記事を参照しつつ復習をしていこうかと。(WWDCもリアルタイムでは理解できないのでいつもスライドpdfが出てから自分のペースで手を動かしながら勉強する、というスタイルでやってます)


オーバービュー的な話、考え方的な話はありがたく参考にさせていただきつつ、ここでは主に実装面のセッションについて復習していきたいと思います。


実践的クロスプラットフォームSwift / Practical Cross-Platform Swift

Realm の JP Simard さんの話。

SwiftはApple以外のプラットフォームでも利用できるようになりました。iOSアプリ以外でも好きなようにSwiftを書けます。この講演では、CocoaやObjective-Cの機能を犠牲にせずに、クロスプラットフォームでSwiftを用いるときの実践的な書き方、テスト・デバッグ手法、について解説します。



開発環境

まず開発環境の話。できるだけ統一したいよねーというので、次の3つが挙げられていました。

  • Xcode
  • Xcode + Toolchain
  • Docker + CLI + Editor

そういう選択肢があるよねという話としてはわかるのですが、何が腹落ちしなかったのか考えてみると、

  • この3つのメリット・デメリットがよくわからない
    • たとえば、Xcode単体だと何らかのプラットフォーム向けの開発時に困るという話なのか?それとも開発環境としてXcodeやMacに縛られたくないという話なのか?
  • Toolchainがよくわかってない
    • ([Xcode] > [Toolchains] というメニューがある)
  • Docker使ってCLI+好きなエディタで開発できますという話とかは、サーバーサイド開発とかで普段そうしてる人だとピンと来る話なのかもしれない

そもそも非マルチプラットフォーム(MacでiOS開発しかやってない)、エディタとかにもこだわりがない(Xcodeで不満がない)人間なので、とりあえず何がわからなかったのかを列挙できたのでいったんokとします。。*1


Swift Package Manager

なぜSPMの話がクロスプラットフォーム開発の話で出てきたのかよくわからなかったのですが、niwatakoさんの書き起こし記事 みて、


是非オススメしたいのはSPMを使う時に自分のコードを細かいプロジェクトに分けることです。


使いまわしやすいように細かく分けてSPMで管理しよう、という話だと解釈しました。違ってたらすみません。*2


Objective-C 依存

これもリアルタイムでそもそも何についての話なのか(依存性の話だということが)理解できないまま聞いてましたが、niwatakoさんの書き起こしで理解。


#if がたくさん必要になる。 アーキテクチャチェック、AppleFrameworkを使っていないか、Objective-Cランタイムを使っていないかなど。この辺離れていくしか無い。時間がたてば軽減していくと思われる。


今のところは大変そうだ。。(JPさん荒野の開拓ありがとうございます)


Q&A: クロスプラットフォーム&オープンソースなXcode

これもniwatakoさんの記事で理解。Darwin以外でも動くXcodeはオープンソースとして出るか?という質問だったらしい。で、回答としては、Xcode自体は出ないと思うけど、SourceKitとかほとんどのIDEサポートはオープンソースになってるし、クロスプラットフォームで使えるものもあるので、実質的には別プラットフォームでIDEサポートはできると思います、と。


平常心で型を消し去る / Keep Calm and Type Erase On

型を明確にすることがSwiftらしいやり方であると気づいた時、同時に時には型を消す必要があると分かります。この講演では、型とは何か、型を消すことが何を意味するか、なぜそうしたいかについて解説します。


あとで手を動かしてコード書いてみよう、と思ってたら既に @hkato193 さんが試されてて、


@norio_nomura さんがgistにアップされてました。

ありがたや。


こんな風に書けるのか。。

class AnyPokemon<PokemonType>: Pokemon {
    let _attack: PokemonType -> Void
    required init<U: Pokemon where U.PokemonType == PokemonType>(_ pokemon: U) {
        _attack = pokemon.attack
    }
    func attack(move: PokemonType) {
        _attack(move)
    }
}

で、こんな感じで使えると。

let pika = AnyPokemon(Pikachu())
let hito = AnyPokemon(Hitokage())
pika.attack(3)
hito.attack(3.0)

勉強になりました。



  • 別の登壇者の方の実装:


  • 関連トーク:




Swiftのエラー処理についての三つの話 / Three Stories about Error Handling in Swift

エラー処理は安全なコードを書く上で重要です。私のプレゼンテーションでは、主に私の経験と考えに基いて、また `Error Handling Rationale and Proposal` と swift-evolution のメーリングリストでの議論にも触れながら、 Swift におけるエラー処理の論点を整理します。" : "Error handling is important to write safe codes. In my presentation, I will organize the issues of error handling in Swift mainly based on my experience and thinking referring to `Error Handling Rationale and Proposal` and discussions on the swift-evolution mailing list.")


後ほど復習:


プロトコルと約束の地 / Protocols and the Promised Land

Swiftの設計はジェネリクスや第一級プロトコルなど言語の機能がアプリケーション開発のカギとなることを推進しています。しかし、Objective-Cから導入されたものを含む論理的パターンの多くは期待した通りには動作しません。多くの場合、Swiftの型システムは、プロパティをクラスとプロトコルの両方に拘束したりする素直なパターンとうまく動きません。この講演ではいくつかの課題に着目し、内部に潜む原因を議論し、対応策について考えます。


後ほど復習:


文化を調和させる / Blending Cultures

Swiftでアプリケーションを書くということはObjective-Cで書かれたアプリケーションをただSwiftに書き換えるだけでなく、Swiftの特徴や哲学を受け入れる必要があります。この講演では、標準的なMVCで構成されたテーブルビューを使用したアプリケーションをSwiftに書き換えるところから始まり、よりSwiftらしいコードにするために、関数型プログラミング、オブジェクト指向プログラミング、デザインパターン、プロトコル指向プログラミングの考え方を適用していきます。


まずは niwatako さんの書き起こし を読んで理解しようと試みたのですが、


・・・わからない。コードの意味、やってることそのものはわかるのですが、一枚一枚のスライドとその間にある「その心は?」的なところがわからない。。


ただ、結局何をやっているのか、というのは最後にひとことで要約されてました。

さて、何をしましたか、いろんな簡素化しました。変更しないコードを外に追い出しました。


実際にサンプルコードを書いてくれた方がいます。


このコード内の HandVC.swift という UITableViewController サブクラスを見ると、効果が一目瞭然です。

class HandVC: UITableViewController {
    
    private var dataSource = HandDataSource()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.dataSource = dataSource
        tableView.delegate = self
        navigationItem.leftBarButtonItem = editButtonItem()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    @IBAction private func addNewCard(sender: UIBarButtonItem) {
        dataSource.addItemTo(tableView)
    }
}

なんとこれで全部。肥大化しやすい VC がこうもスッキリするのかと。ちょうどVCを肥大化させてしまった個人プロジェクトがあるので、この考え方でリファクタリングしてみようと思います。


Core Animationで作る高度なグラフィックス / Advanced Graphics with Core Animation

理解はできたので復習は省略しますが、発表に関連する話で、Core Animation を使ったライブラリを公開しています。



カスタマイズ性のためにいろいろとプロパティが多くなってしまってますが、基本的にはシンプルで、

  • CABasicAnimation で scale をアニメーションさせつつ、
  • CAKeyframeAnimation で opacity をアニメーション

させてます。また複数のパルスを発生させるために、(スライドにも出てきた) CAReplicatorLayer を利用 してます *3


我ながらAPIがいけてないのですが、実装時間3分ぐらいでそれなりにインパクトのあるアニメーションを入れられるので費用対効果抜群です。自分でも、WHILL、Moff、BONX、iOS×BLE本のサンプル、その他iOS Samplerシリーズやハッカソンでの作品、プロトタイプ案件等々、Bluetoothや位置情報を使う場合にしょっちゅう利用してます。


f:id:shu223:20141104132131g:image:left

f:id:shu223:20141104132209g:image:left



近々Swiftでつくりなおそうと思ってます。(あ、ObjCです、すいません try! Swift なのに)


スマートホームのためのコード / Code for the Smart Home

上に同じく、理解はできたので復習は省略しますが、ちらほらタイムラインで見かけた意見について。



上記記事にも書いてあるのですが、HomeKit Accessory Protocol は次のような構造になっています。


f:id:shu223:20140623070646p:image:w500


対応デバイスも海外ではちらほら出てきている *4 ので、近々記事を書こうと思います。



*1:たぶん上記疑問に答えてもらっても、「なるほどー(ポカーン)」ってなりそう

*2:JPさんの英語がハキハキしてて何とかついていけそうな気がしてこの時点では同時通訳聞いてなかったので、たぶん聞き逃しまくってたのかと

*3:@dealforest さんプルリクありがとうございました!

*4:昨年WWDCでSFに行ってきたときに買ってきて、まだ触ってない。。

2015-04-24

WatchKit もろもろ実機検証

Apple Watch をたまたま発売日当日ゲットできたので、いろいろと WatchKit アプリ開発に携わってきた 中で、「実機でやってみないと確信が持てないな。。」と思っていた諸々について検証してみました。


Xcodeからの実機インストール

Parent App をインストールして、Apple Watch アプリからインストールするのか、どうなのか・・・?


というところがよくわかってなかったのですが、やってみると何のことはない、Xcode から WatchKit App の Scheme を選び、Apple Watchとペアリングした iPhone を選択して Runするだけでした。


f:id:shu223:20150424135908j:image:w400


ちなみに親アプリを実機インストールするだけでもいけました。(ペアリング済みのWatchにアプリが自動的にインストールされる)


あと実機デバッグ時も普通にブレークポイントで止まってくれました。


親アプリからローカル通知を発行するとWatchKit Appで受けられるのか?

ドキュメント読む限りではできそうだけど、シミュレータでは通知受信時の見た目しか確認できないので、実際どうなのか・・・「親アプリとWatchアプリのどちらが通知を受け取るか」はiOSが制御する、と書いてあるし、親アプリが自分で受け取ってしまうということもありうるのか・・・!?


というわけで、次のように親アプリ側で「ボタンを押したら10秒後にローカル通知を発行する」という実装をしておいて、

UIUserNotificationType types = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert;
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
- (IBAction)notifyBtnTapped:(id)sender {
    
    UILocalNotification *localNotification = [UILocalNotification new];
    localNotification.alertBody = @"Hello!";
    localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:10.];
    localNotification.soundName = UILocalNotificationDefaultSoundName;
    [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
}

Apple Watch とペアリングした状態で試してみました。


ケース1: 親アプリがフォアグラウンド状態にある

通知発行ボタンを押して、そのまま親アプリが起動した状態にしておいた場合


Apple Watch側では通知を受け取らず


ケース2: 親アプリがバックグラウンド、iPhoneはロックされていない状態

通知ボタンを押して、ホームボタンで親アプリをバックグラウンド状態にし、iPhoneはロックせずにそのままにしておいた場合


親アプリでは通知バナーが表示されたが、Apple Watch側では通知を受け取らず


ケース3: iPhone側がロック状態にある

通知ボタンを押して、電源ボタンでiPhoneをロック状態にした場合


親アプリ、Apple Watch両方で通知を受け取った



というわけで、iPhone側ですぐに反応できない場合にウォッチ側で通知を受け取って表示する、という制御のようです。


Apple Watch 自体の BLE 仕様

Apple Watch の BLE 機能をデベロッパが制御できないのは WatchKit の API を見ればわかることですが、BLE自体は積んでるはず。アドバタイズメントパケット飛んでるのかな・・・?と見てみたところ・・・


f:id:shu223:20150424134509j:image:w375


普通にありました。


さすがに接続は拒否されるだろう、と思いつつダメ元で繋いでみたら・・・


f:id:shu223:20150424134554p:image:w240


何の問題もなく繋げました。


(繋いでみてわかる範囲で)Apple Watch の BLE 仕様をまとめてみるとこんな感じ。

  • アドバタイズメントパケット
    • Connectable
    • サービスUUIDのアドバタイズはなし
  • サービス
    • UUID: D0611E78-BBB4-4591-A5F8-487910AE4366
    • キャラクタリスティックを1つだけ保持(下記)
  • キャラクタリスティック
    • UUID: 8667556C-9A37-4C91-84ED-54EE27D90049
    • Properties: Write, Notify

Notification の Subscribe(Light Blue のUIだと Listen)も普通にできました。


また、サービス・キャラクタリスティックは他の人の Apple Watch 個体で調べてみても同じでした。


念のため参考図書です↓↓


iOS×BLE Core Bluetoothプログラミング
堤 修一 松村 礼央
ソシム
売り上げランキング: 898


【電子版】(PDF・達人出版会)

iOS×BLE Core Bluetoothプログラミング
堤 修一, 松村 礼央
ソシム
発行日: 2015-03-23
対応フォーマット: PDF


デジタルクラウンでスクロール

アップルApple Watchのアプリ開発で、できないことのまとめ」という記事に、

開発者向けツールのWatchKitでは、タッチスクリーン上で指を使ったスクロールを可能にするAPIが提供されておらず、デジタルクラウンも使えない仕様となっています。

という記述があり、「明示的にAPIはなくても、普通に WKInterfaceTable とかのスクロールはデジタルクラウンでできるのでは?」と思ってましたが、どうなのでしょうか・・・?


→ WKInterfaceTable はデジタルクラウンでスクロールできました。


フォアグラウンドにある親アプリの制御

前述のWatchKitでできないことのまとめ記事に、こんな記述もありました。

  • 離れたiPhoneのカメラへのアクセスはできない

ただし、アップルは自社のカメラアプリからの利用は可能にするようです。

これも、カメラ機能を持った親アプリをフォアグラウンドにしておけば、普通に `openParentApplication:reply:` でシャッター切れるんじゃないか、と思ったので、試してみました。


親アプリ側で下記のように実装をしておいて、


(ViewController)

<UIImagePickerControllerDelegate>
@property (nonatomic, strong) UIImagePickerController *pickerCtr;
self.pickerCtr = [[UIImagePickerController alloc] init];
self.pickerCtr.sourceType = UIImagePickerControllerSourceTypeCamera;
[self presentViewController:self.pickerCtr animated:YES completion:nil];
- (void)takePicture {
    
    [self.pickerCtr takePicture];
}

(AppDelegate)

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
    CameraViewController *vc = (CameraViewController *)self.window.rootViewController;
    [vc takePicture];
}

WatchKit Extension 側では下記のように実装をしました。

- (IBAction)takePicture:(id)sender {
    
    [WKInterfaceController openParentApplication:@{@"command": @"take_picture"}
                                           reply:^(NSDictionary *replyInfo, NSError *error) {
                                           }];
}

で、親アプリを起動しておいて、WathKit App側のボタンを押すと・・・


無事シャッターが切れました


結論:WatchKit App からフォアグラウンドにある親アプリのカメラ機能等を制御することは可能*1


iPhone側のBLE機能を利用

ウォッチ側のBLE機能はデベロッパは「直接的には」利用できないことは上に書いた通りですが、次の2通りの方法でiPhone側のBLE機能を利用することが考えられます。

  • WatchKit Extension で Core Bluetooth を利用
  • 親アプリで Core Bluetooth を利用

自分が実装したとある案件では親アプリが既にBLE利用機能を有していたので後者を選択しました。


ここではシンプルな例として、「ウォッチ側のボタンを押したら、バックグラウンドにいる親アプリが外部デバイス(ペリフェラル)との接続を確立する」ということを考えてみます。


バックグラウンドにおけるBLEのふるまいや制約については拙著に詳しく書いたのでここでは割愛するとして、WatchKit App をトリガとして Parent App にBLE関連処理を行わせる際のポイントとなるのは、BLEはスキャン、接続、サービス/キャラクタリスティック探索、Read/Write 等々、基本的には非同期的にレスポンスが返ってくる処理ばかりである、というところです。


`openParentApplication:reply:` は同期処理だし、非同期処理完了後にバックグラウンドの親アプリ側から WatchKit App を起こしたりデータを渡したりするAPIはありません。プッシュ通知やローカル通知を使う方法はありますが、プッシュ通知はタイムラグもありますし、ローカル通知は上で行った検証の通りウォッチ側で受け取れないケースが多すぎます。


で、僕はWatchKit Appからポーリングする実装にしました。


(ウォッチ側)

- (IBAction)connectBtnTapped:(WKInterfaceButton *)sender {
    
    // 接続処理開始をparentに依頼する
    [WKInterfaceController openParentApplication:@{@"command": @"connect"}
                                           reply:
     ^(NSDictionary *replyInfo, NSError *error) {

         if (error) {
             return;
         }
         
         [self.connectBtn setEnabled:NO];
         [self.connectBtn setTitle:@"Connecting..."];
         
         startTime = [[NSDate date] timeIntervalSince1970];
         
         self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5
                                                       target:self
                                                     selector:@selector(handleTimer:)
                                                     userInfo:nil
                                                      repeats:YES];
     }];
}

- (void)handleTimer:(NSTimer *)timer {
    
    NSTimeInterval interval = [[NSDate date] timeIntervalSince1970] - startTime;

    if (interval >= kTimeoutInterval) {
        
        // タイムアウト
        [self invalidateTimerIfNeeded];
        [self resetInterfaces];
    }

    // 接続状態をparentに確認する
    [WKInterfaceController openParentApplication:@{@"command": @"status"}
                                           reply:
     ^(NSDictionary *replyInfo, NSError *error) {
         
         if (error) {
             return;
         }

         BOOL isReady = [replyInfo[@"is_ready"] boolValue];
         
         if (isReady) {
             // 次の処理へ
         }
     }];
}

(親アプリ側)

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo
              reply:(void (^)(NSDictionary *))reply
{
    NSString *command = userInfo[@"command"];
    NSDictionary *replyDic;
    
    // connection 開始
    if ([command isEqualToString:@"connect"]) {
        
        replyDic = [self startConnecting];
    }
    // BLE接続ステータスを返す
    else if([command isEqualToString:@"status"]) {
        
        BOOL isReady = [[BLEManager sharedManager] isReadyForControl];
        
        replyDic = @{@"is_ready": @(isReady)};
    }
    // 中略

    reply(replyDic);
}

AppleWatchは実機がないのでシミュレータでしか試せない、BLE機能はシミュレータでは使えないというジレンマで検証できずにいたわけですが、今日手に入ったのでさっそく試してみたところ・・・


無事動作しました



しつこいようですが参考図書になります。。m(__)m

↓↓↓↓


iOS×BLE Core Bluetoothプログラミング
堤 修一 松村 礼央
ソシム
売り上げランキング: 898


【電子版】(PDF・達人出版会)

iOS×BLE Core Bluetoothプログラミング
堤 修一, 松村 礼央
ソシム
発行日: 2015-03-23
対応フォーマット: PDF




*1:PowerPointを制御できるアプリとかもあるので、予想はついていたことですが。。

2009 | 08 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2013 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2014 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2015 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2016 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 11 | 12 |
2017 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2018 | 02 |