【書評】Androidを支える技術(I)
Androidを支える技術(I)
なにかと話題の書籍、「Androidを支える技術(I)」をようやく読み終えたのでちょっとしたレビューでも。
結論からいいますと、 ここ数年読んだAndroid書籍では最高に面白かったです
TL;DR
レビュー
冒頭にも書いたとおりAndroidの内部構造、一巻は特にイベントと描画周りに焦点をしばった内容です。これがディープなことディープなこと。
「はじめに」からすでにおかしくて、
「執筆中にNougatが出て、バイトコード実行環境が変わり加筆修正を余儀なくされた」
とか他の本ではありえないことが書かれています。この時点でいい意味でやばい本を買ったという予感と期待に胸が膨らみました。
(逆に上記文言で特になにも感じないなら、おそらくこの本は合いません)
第1章はAndroid全体の概要。Androidアプリを開発したこたがない組み込み系エンジニア向けの解説というところです。ここからすでに「何らのGUIシステムでコーディングしたことがある人」前提で話が進んでいきます。UIスレッドとか当然のように出てきます。
そして2章でいきなりLinuxカーネル。Linuxカーネルからのイベントを受け取り、Windowに配布する部分の解説ですからんね…カーネル周りの知識はほとんどないですがざっくり概要ぐらいは掴めたのと同時に「こんな仕組みだったのか!」という驚きを感じました。
ここはAndroidエンジニア以外の人の方が楽しいかもしれませんね。少なくともLinuxの知識があったほうが楽しめるのではないかと思います。
中盤がレイアウトや描画周りの解説。ここは(OpenGLを除き)Javaコード部分の解説が主体です。Viewツリーの構築と描画・イベント処理ですね。
ここについてはそこそこコードを追ったことがあるので、把握してた知識で問題なかったことを確認したぐらいです。dispatchTouch/dispatchDraw/onMeasureなどですね。このレベルぐらいなら自分のように、内部構造をちょっと覗いたことがあるという人は多いのではないでしょうか?
もちろん自分が追ってた範囲よりは深い部分の解説があって面白かったですが、アプリを作るぶんに困らない範囲の知識はもっていたのでそこまで新しい知識というわけでもなかったです。
逆に上記のメソッドをみてピンとこないなら 今すぐ注文していいと思います。より自作Viewが作りやすくなるはずです。
そして一番面白かったのが最終章「バイトコード実行環境」
なぜdexがレジスタ型なのかという考察、いかにネイティブコンパイルするか、さらにそれをAndroidバージョンごとにどのように変わっていったか、およびその理由の考察…
本当の読んでて興奮しました。個人的にこの本で一番面白かったのがこの章です。
デバイスの進化、ユーザ要求の変化、それらに合わせてAndroidはどのように変わるべきか…著者の異常とも言えるAndroid愛を感じる章です。
本当にここまでAndroidの構造を把握してる人は日本にそうそういないのではないでしょうか?Androidの複雑で膨大な内部構造のコードを読み・理解しているあたり、著者の力量の高さも伺えます。
当然ですが、内容はそこらのAndroidの書籍より数段ハード です。理解が追いつかず、同じ場所を何度も読み返したので読破にも時間がかかりました(それでも理解しきれてない部分が多い…)
でも面白いんですよねぇ。自分の知らない層の話なので本当に面白い。
個人的にですが以下のような人におすすめです。
- Android開発を年単位で経験している
- よくライブラリや自作Viewを作る
- よくView周りのソースを読む
まずある程度Androidの知識がないと厳しいです。昨日今日触り始めました、ではおそらく難しい。
「昨日今日の人」が読むのを止めはしませんが、この本より先に読むべき本はあると思います。本を読む代わりにとにかくコードを書くでもいいです。
逆にある程度なれた人にとってはより最適化されたアプリを作る手助けになるでしょう。
もっともこの本が最適化手法を提示してくれるわけではありません。
ただし、よりAndroidのソースを追いやすくなるのは間違いないです
その結果、より高速で効率のいいアプリを作れるようになる可能性はあると思います。
どの程度直接的に役立つのかはまだ自分もよくわかりません。
しかしいい刺激になるのは間違いないので、「今読む本がない / 次何を読もう?」というAndroidエンジニアには強くオススメしたい本でした。二巻も買います!
ちなみに所々でコラムがあるのですが、Android3.x系ひどい と何度も書いてて笑えましたw 確かに当時のタブレットはいい出来とは言い難かったですからね…
WWDC Cocoa Development Tips まとめ
Cocoa Development Tips
「あなたが知らないCocoaの29のこと」という面白そうな副題がついてたので見てみました。
Internationalization
UserDefaults
- 4つの階層がある
- Argument Domain / Application Domain / Global Domain / Registration Domain
- 読み込み時は、先頭から順に問い合わせる
- 書き込み時はそれぞれの階層で手法が異なる
- Registration Domain > UserDefaults.standard.register([“key” : value])
- ↑これは永続化されない
- Global Domain > defaults write GlobalDomain key -bool value
- ↑コマンドラインから叩く。コードではない
- Application Domain > UserDefaults.standard.set(key, value)
- ↑よく使うもの
- Argument Domain > 起動時に設定。書き込みはない
- 4つの階層がある
役立つUserDefaults
-NSViewLayoutFeedbackLoopDebug YES
- 繰り返し発生しているレイアウトを追跡しやすくなる
-NSApplicationCrashOnException YES
- クラッシュ時の例外をキャッチして表示してくれる
UserDefaults + KVO
- UserDefaultsはKVOで監視できる
//新機能 extension UserDefaulst { @objc dynamic var showAtLaunch : Bool { return self.value(forKey: "showAtLaunch") as? Bool ?? false } } let observation = UserDefaults.standard.observe(\.showAtLaunch) { observed, change in //処理 }
Asset Catalogs
Unit Test
- XcodeのNewFileからUnitTestを選択することでテンプレートが生成される
NSBox
Restorable State
- こちらもMacOS専用
- NSResponderに状態を保持させ、再起動で復元させる?
- restorableStateKeyPathsプロパティを使えば楽になる?
CoreData
- ※もはやRealmの時代だとは思いますが
- NSPersistentContainerを使えば厄介なCoreData初期化で楽できる
- 配列を保存したいときは、RelationShip で ToMany を選択する
- 必要に応じorderedにもチェックをいれる
- Modelの新バージョンを作り、あとはCoreData側がマイグレーションしてくれる
- エラーは適切にハンドリングする
- 特に追加時
NSError
- NSErrorには各種メッセージがあるので、適切にユーザに表示する
- 自分でエラーを発生させるときも適切にメッセージを設定するように
- CocoaErrorを使えば楽できる(新規)
- 専用のドメインを作えばハンドルしやすくなる?
SharedKeySets
- 頻出するキーを渡しておけば高速化できる
- NSDictionary.sharedKeySet と NSMutableDicitonary.init(sharedKeySet:) をペアで使う
let set = NSDictionary.sharedKeySet(forKeys:["id", "name"]) let dict = NSMutableDictionary(sharedKeySet: set) dict["id"] = ... dict["name"] = ...
Accessibility
- VoiceOverはIBから簡単に設定できる
- Appを1024x640でテストしておく
- DeveloperToolのAccessibilityInspectorをうまく使う
Documents
- ユーザに保存させる文章にNSDocumentを使えばVersionControlなどいいようにやってくれる?
- CocoaAPIは使ったことがないのでイマイチつかめず
Reporting Exceptions
- NSApplication reportExceptionをオーバーライドすれば例外をロギングしやすくなる
Debugging
- XcodeのDebug領域をうまく使う
- View hierarchy, Debug memory graph, simulate location
Sound
- NSButtonにSoundを設定できる
※セッション一覧ページにはiOS/Mac/watchOS/tvOSとあったのですが、思ったよりMacでした。CocoaTipsなので当然かもしれませんが。
【書評】関数プログラミング実践入門
関数プログラミング実践入門
1年ちょっと前に買ったものですが、読み直しのついでにレビューをしてみます。
※残念ながら改訂版ではありません..もちろん個人の主観に基づくレビューですので、そこはご了承ください。
購入動機
- SwiftやScala/Java8等、関数型の側面が入った言語を触ることが多くなった
- それに伴い、関数型の知識が欲しかった。より関数型らしく組むには が欲しかった知識
- 特定の関数型言語の知識は求めていない
- 上記理由により、汎用的に関数型の知識を扱っていそうなこの本を選定
TL;DR
全体的に
長所
短所
- 他言語との実装比較が冗長に感じられた(1章)
- 各種サンプルが、関数型であるメリットをもっと感じる内容だとより良かった
レビュー
思ったよりHaskellの本だった、というのが正直なところです。タイトルから期待する内容と実際の内容にかなりのズレがあります(個人的印象ですけど)。タイトルが「Haskell実践入門」だったらまた違ったでしょう。(買わなかった可能性は大きいですが)
タイトルに沿った内容らしいのは、
- 「型付け」などプログラミング全般の話題に触れる0章
- 関数型らしい設計手法を提示した6章
ぐらいでしょうか?
とはいえ実はこの0章が最も関数型のメリットを解説しているのではないかと思います。
関数型言語の特徴、参照透過性と並行処理の相性の良さ、テストのしやすさなどなど関数型のメリットが分かりやすく提示されています。(関数型言語の、というよりHaskellの、というべきかも)
また、6章も「数独」という入門本にしてはかなり複雑な題材を取りあつかい、どのように設計していくかが述べられておりここも面白かったです。
この手の入門書としては相当高難度な題材といえるのではないでしょうか?しかももろに「アルゴリズム」の話で関数型とも相性がいいです。DBやHTTPが不要な分、完全にロジックに集中できます。
ここは本当に見ごたえがありました。ソースコードも豊富で当時は初Haskellでしたがなんとかなりました。
一方で 残りの章は普通のHaskell解説本です
特に1章が厳しい。いくつかのよくある実装パターンでHaskellとの比較を行っているのですが、「相手言語の苦手な分野と比較している」のは残念な印象でした。
たとえば
-
- 前のページで取り扱ってたJavaには型あるんだけど…
CだとTuple返せないから複数の値を返したい時不便でしょ?
- 次のページのJSならできるのでは…? いや、Cこそやりたい放題な気が。
というイメージ。Haskellの優位性をよく分からない理由付けで解説してるだけに感じてしまいました。
残りの章は主にHaskellの文法解説+αです。
Haskell入門書としては「FunctorやApplicativeなど厄介な部分を除き」良書という印象。
翻訳書籍でないので変な日本語に悩まされることもありませんし、各単語の説明もきっちりしてて読みやすいです。
問題はそのへんのつまづきやすい部分をさらっと流してること
この説明だけでIOモナドを扱える人はほぼいないのではなかろうか…? その辺は他の書籍で補完しないと厳しいです。
「数独のためのHaskell解説」と割り切ってる感はあります。確かに数独ではモナドを意識して使わないので数独部分を理解するには十分ではありました。(List/Maybeは他言語にもあり、モナドと意識せず扱えるから困らない)
ちょっと厳しい感想になってしまいましたが、読んでよかったと思ったのは間違いありません。これを読んでいなかったらelmは理解できなかった、触ろうとも思わなかったはずです。
ただしHaskell入門としては明らかに足りていません。特にモナド周り。ここは注意してください。
しかし6章の数独はそれをひっくり返すぐらい面白かった。先ほども述べたとおり、1-5章はこのための準備と割り切る必要はあると思います。
こういう内容が豊富でもっと具体的な設計手法を見れたら/試せたらより良かったと思います。
この数独を経験することでかなりオブジェクト指向での組み方が変わりました。具体的には「参照透過性のあるメソッド」「ないメソッド」を分離したくなる癖が付きました。(参照透過性のあるstaticメソッドがすごい増えた)
Androidでの簡単な例だと以下のようなイメージです。
//before if (state.isActive && !state.isLoading) { button.setText("送信"); } else { button.setText("通信中"); } //after static String getButtonText(State state) { return (state.isActive && !state.isLoading) ? "送信" : "通信中"; } String text = getButtonText(state); //なぜか一旦変数に格納したくなる症状も出た button.setText(text);
とにかくsetTextは1箇所に限定したくなる癖がつきました
コードとしては若干面倒になってますが、副作用を伴う操作は極力減らしたい思考に変わりました。
冗長な面やHaskell入門として足りない面はありますが、このような考え方をかえるきっかけとなった本なので良かったです。
特にelmにつながったのは大きい。(少なくともこの本で得た知識おかげである程度なんとかなったのは事実)
ターゲットとする読者層は狭い気はしますけど、上記のような考えかたに触れてみたいのでしたらオススメです。
逆に本格的にHaskell(含む他の関数型言語)に触れたい、商用投入したい/してるプロダクトに触れる必要がある、というのでしたらその言語に特化した他の本を購入されたほうがいいかと思います。
WWDC2017 Introducing ARKit まとめ
iOS11の大きなウリの一つ、ARKitのセッションです。
動画はこちら
Introducing ARKit
ARとは?
- Virtualな物体をPhysicalな世界に配置すること
- ポケモンGOを始めとするデモ
ARKit
概要
3つの機能がある
Unityに組み込まれている
API概要
ProcessingとRenderingに分かれる
- RenderingがMetalやSceneKit
- ProcessingがAR-Kit
- 内部でAVFoundationやCoreMotionを用いる
ARKit
ARSessionConfiguration
if ARWorldTrackingSessionConfiguration.isSupported { configuration = ARWorldTrackingSessionConfiguration() } else { configuration = ARSessionConfiguration() }
** ARSession
* ARプロセスの管理
* run
/ pause
など
* ARSessionDelegate
で更新された ARFrame を受け取る
** ARFrame * ARレンダリングに必要な情報がある * カメラ画像 * トラッキング情報 * Scene情報 * ARAnchor * 実世界のポジション * ARSessionに追加/削除する
Traking
ARKitは座標だけでなく、「実世界の距離」も計測する
- セッション開始時点からの位置になる
- カメラから複数の特徴点を抽出、その位置差分で位置を出す
- 加速度センサも活用している
サンプルコード
let session = ARSession() session.delegate = self let configuration = ARWorldTrackingSessionConfiguration() sesison.run(configuration)
- ARCamera
- 仮想的なカメラ情報を提供するオブジェクト
- Transform/TrackingState/Camera intrinsics
Demo
デモで気になったクラスをピックアップ
- ARSCNView : ARシーンレンダリングの便利クラス
- SceneKitをはめることができる
arScnView.scene = scnView
- UITapGestureでタップ判定可能
- ARSCNView : ARシーンレンダリングの便利クラス
タップでスクリーンキャプチャを画像としてSceneに追加するサンプル
func handleTap(...) { guard let currentFrame = sceneView.session.currentFrame else { return } //キャプチャした平面Object作成 let imagePlane = SCNPlane(width: sceneView.bounds.width / 6000, height: sceneView.bounds.height / 6000) imgPlane.firstMaterial?.diffuse.contents = sceneView.snapshot() imgPlane.firstMaterial?.lightingModel = .constant //シーンに追加 let planeNode = SCNNode(geometry: imagePlane) sceneView.scene.rootNode.addChildNode(planeNode) //カメラの10cm先に配置 var translation = matrix_identity_float4x4 translation.column.3.z = -0.1 planeNode.simdTransform = matrix_multiply(currentFrame.camera.transform, translation) }
Trackingの品質
カメラの環境に大きく左右される
Trackingの状態
func session(.., cameraDidChangeTrackingState camera: ARCamera) { if case .limited(let reason) = camera.trackingState { ... } }
func sessionWasInterrupted(...) { showOverlay() } func sessionInterruptionEnded(...) { hideOverlay() //必要に応じAR処理を再開 }
Scene Understanding
バーチャルな物体を配置するときに必要な情報のこと
- 平面検出
- ヒットテスト
- 光量推定
平面検出
- テーブルのような水平面を検出できる
- 複数のフレーム情報から計算する
- 広さを測定できる
- 面をマージできる
- ARWorldTrackingSessionConfigurationの
planeDetection
を使う- 現状、
.horizontal
しか値がない模様
- 現状、
ARPlaneAnchor
- 平面が検出されるとDelegateが呼び出される?
- 3軸回転、中央、広さを保持している
HitTest
- カメラからのみた特定の直線が実世界平面のどこに位置するかを調べる
- 4つの種類がある
- Existing Plane Using Extent: 平面の位置/広さを考慮したヒット位置
- Existing Plane : 平面の広さが無限とした場合のヒット位置
- Estimated Plane : まだ平面が検出されてないときに使う
- Feature Point : 面が小さいときに用いる(イレギュラーなケースらしい)
- 画面の座標ベースでテスト可能(0,0が左上、1,1が右下)
光量推定
- 画像ベースの光量推定
- 1000ルーメンがデフォルト
- Configurationで設定する
構成
SceneKit/SpriteKit/Metalのいずれか
SceneKit
- ARSCNView
- カメラ画像を表示する
- SCNCameraが人にあわせ移動する
- 自動でLightingされる
- SCNNode が ARAncher にマップされる
SpriteKit
- ARSKView
- SKNode が ARAnchor にマップされる
- SpriteKitは2D描画用
- 物理計算にテーブルを使うなどが出来る(?)
Metal
- カスタムレンダリングに用いる
SceneKitで配置するだけで動くのは良さそうですね。3Dはあまり経験がないので厳しいと思っていたのですが、結構簡単にできそうでちょっと興味わいてきました
WWDC Introducing Drag and Drop まとめ
今回はiOS11の目玉といえるドラッグ&ドロップ機能のセッションです。
他にもドラッグ&ドロップはセッションが多いので、Appleの気合のいれようが伝わってきますね。
とりあえずIntro部分のセッションを。
Introducing Drag and Drop
ビデオは こちら
Drag&Dropのコンセプト
Drag&dropとは?
- タッチによるデータの移動・コピー
Drag&Dropのゴール
- 素早い応答。非同期取得
- セキュリティ。アクセス制限など
- マルチタッチで直感的に操作できる
基本的にiPad向けの機能
- iPhoneでは同一アプリ内に制限される
API概要
- 4つのフェイズがある
- Lift : 長押しでドラッグ可能になった状態
- Drag : プレビューが移動してる状態。
- SetDown : キャンセル、もしくは転送先の決定
- DataTransfer : ターゲットへのデータ転送
Drag元のAPI
- DragInteraction 系APIが追加
- UIDragInteraction, UIDragInteractionDelegate
- UIDragItem
let view : UIView = ... let delegate : UIDragInteractionDelegate = ... let dragInteraction = UIDragInteraction(delegate : delegate) view.addInteraction(dragInteraction)
Delegateの役割
- Lift時に、
UIDragItem
を返す - データが無い場合はDragフェイズ終了。
- Lift時に、
UIDragItemとは?
- 以下の2つを持つ
- Dragプレビュー
- NSItemProvider
- 以下の2つを持つ
Drop先のAPI
- UIPasteConfiguration が追加
- 対応するデータ種別を指定する
- Drop対象のViewに追加する
let config = UIPasteConfiguration(typeIdentifiersForAcceptingClass : NSStrnig.self) view.pasteConfiguration = config //UIViewを継承して作る? UIResponderのメソッドだった override func paste(itemProiders: [NSItemProvider]) { }
- UIDropInteractinoDelegate で受け取りを行う
- 受け取り可能かどうかを指定するのもこのDelegate
フェーズ詳細
Lift
- ロングプレスで移動可能になった状態
- ドラッグ待ち
- Delegateで「移動するデータとプレビュー用View」を返す
//アイテムの決定 func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: UIDragSession) -> [UIDragItem] { let itemProvider = NSItemProvider(object: "Hello World" as NSString) let dragItem = UIDragItem(itemProvider: itemProvider) return [ dragItem ] } //プレビュー func dragInteraction(_ interaction:UIDragInteraction, previewForLifting item:UIDragItem, session:UIDragSession) -> UITargetedDragPreview? { let imageView = UIImageView(image: UIImage(named: "MyDragImage")) let dragView = interaction.view! let dragPoint = session.location(in: dragView) let target = UIDragPreviewTarget(container: dragView, center: dragPoint) return UITargetedDragPreview(view: imageView, parameters:UIDragPreviewParameters(), target) } //リフトアニメーション func dragInteraction(_ interaction: UIDragInteraction, willAnimateLiftWith animator: UIDragAnimating, session: UIDragSession) { animator.addAnimations { self.view.backgroundColor = UIColor.gray } animator.addCompletion { position in if position == .end { //リフト完了 } else if position == .start { //リフトキャンセル。元に戻る } } }
Drag 送信
func dragInteraction(_ interaction: UIDragInteraction, previewForCancelling item: UIDragItem, withDefault defaultPreview: UITargetedDragPreview) -> UITargetedDragPreview? func dragInteraction(_ interaction: UIDragInteraction, item: UIDragItem, willAnimateCancelWith animator: UIDragAnimating) func dragInteraction(_ interaction: UIDragInteraction, session: UIDragSession, didEndWith operation: UIDropOperation)
Drag 受信
- 移動先が「データを受け取れるか」を返す
- DropInteractionDelegate
//sessionをHandleできるかどうか func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool { return session.canLoadObjects(ofClass: UIImage.self) } //handle可の後、Viewに入った時に処理可能かどうか func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal { return UIDropProposal(operation: UIDropOperation) } // func dropInteraction(_ interaction: UIDropInteraction, sessionDidExit session: UIDropSession)
DropProposalは4種類ある
- cancel
- copy
- move
- これは同一アプリ内限定
- 移動するように作り手があわせる必要がある
- 送信元が「move可能」とする必要がある
- これは同一アプリ内限定
- forbidden
- Cancelににてるが、特殊なバッジがつく
- 利用例: ReadOnlyなフォルダに移動させようとした場合
SpringLoading(レインボーカーソルと訳すべきか?)
- UIButtonに追加
let button = UIButton() button.isSpringLoaded = true let springLoadedInteraction = UISpringLoadedInteraction { (interaction, context) in //長時間の処理 } view.addInteraction(springLoadedInteraction)
Drop
- drop後は
dropInteraction
が呼び出される- データを読み込む場所
func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) { session.loadObjects(ofClass: UIImage.self) { objects in for image in objects as! [UIImage] { self.imageView.image = image } } }
デモ
動画29分からコードを交えながらのデモ
- コルクボードに写真をおくサンプル
- 気になった部分のみメモ
同一アプリ判定
- DropSessionで同一アプリ内か外部アプリかで処理分岐が必要になる
- アプリの仕様によりますけど
//同一アプリ内なら移動、別アプリからならコピー func dropInteraction(..., sessionDidUpdate ...) -> UIDropProposal { let operation: UIDropOperation if session.localDragSession == nil { operation = .copy } else { operation = .move } return UIDropProposal(operation: operation) } //Drop後 func dropInteraction(..., performDrop ...) { if session.localDragSession == nil { //コピー dropPoint = session.location(in: interaction.view) for dragAtime in session.items { loadImage(dragItem.itemProvider, center: dropPoint) } } else { //ボード内での移動、データコピーは不要 } } //Dropアニメーション func dropInteraction(_ ... previewForDropping: ...) { if session.localDragSession == nil { //外部ならなにもしない } else { //内部なら移動処理 return defaultPreview.retargetedPreview(...) } }
肥大化するModelTreeとどう戦うか(が分かっていない)
だんだんelmのサンプルが大きくなってきました。Natigationも導入し、いわゆるSPA化してます。
ここで問題になってきたのが、「どう巨大なTreeを管理するか」
言語の難しさの壁を越えたら次は他言語でもあるあるな問題に遭遇しました。
続きを読む