iOS の時間表示設定を取得する
iOS の日付と時刻の設定の24時間表示が ON になっていれば24時間表示用の UILabel を、そうでなければ12時間表示用の UILabel を表示するという用に、時間表示設定によって View を切り替えたいと思ってググってみたのですが、解決策が見つからなかったので軽く調査してみました。
Locale の調査
DateFormatter を扱う時等に、強制的に24時間表記で時刻を扱えるようにと NSLocale.system を使用したことを思い出して、Locale が時間表示設定の情報を持っているのではと思って確認してみました。
(lldb) po NSLocale.system ▿ (fixed) - identifier : "" - kind : "fixed"
identifier と kind しか持っていない、、、
念のために24時間表示設定がONの時の Local.current も確認してみました。
(lldb) po NSLocale.current ▿ ja_JP (current) - identifier : "ja_JP" - kind : "current"
Locale が時間表示設定の情報を持っているわけではなさそうですね、、、
となれば、次は———
と別の方法で調査できたらよかったのですが、思いつく方法がなかったし、ググっても見つからないということは時間表示設定はそもそも取得できないのではと思ったので、以下の方法で無理やり判別することにしました。
if String(describing: Date()).contains("午") { print("12時間表示") } else { print("24時間表示") }
もっと良い判別方法があれば是非とも教えていただきたいです、、、
Swift の Date の操作
Date の操作方法が全く覚えられないので、調べたことをここにまとめていく。
日時を指定して Date 生成
Calendar(identifier: .gregorian).date(from: DateComponents(year: 2017, month: 8, day: 13))
ある Date の次の日の Date 生成
Calendar(identifier: .gregorian).date(byAdding: .day, value: 1, to: Date())
UnitTest 用のデータを共通化する【Swift】
テストをする際には複数のオブジェクトの初期化をする必要があり、この作業が面倒でテストを書くのが面倒になってしまうことがあったので、どうにかできないかと思い調べてみたところ、良さそうな方法がありました。
これを、自分が使いやすいと思った形に少しアレンジしたものを紹介してみようと思います。
Seeds Struct
以下のように Human というクラスがあったとします。
class Human { var name: String var age: Int var height: Double var job: String init(name: String, age: Int, height: Double, job: String) { self.name = name self.age = age self.height = height self.job = job } }
この Human に関するテストを書くといったときに、以下のようにテストを書く度に値を指定して初期化していくかと思いますが、本当に全部の値を指定しなければならないというケースはそれほど多くはないのではないかと思います。
let takeru = Human(name: "takeru", age: 30, height: 180.0, job: "engineer") let rika = Human(name: "rika", age: 28, height: 160.0, job: "designer")
こういった時に上記の記事に書いてあるようにして Seeds というテスト用のデータを持つ Struct を用意することで、Xcode 等の補完機能を用いながら以下のように簡単にデータを作成することができます。
struct Seeds { struct Humans { static let takeru = Human(name: "takeru", age: 30, height: 180.0, job: "engineer") static let rika = Human(name: "rika", age: 28, height: 160.0, job: "designer") } } Seeds.Humans.takeru => Human(name: "takeru", age: 30, height: 180.0, job: "engineer") Seeds.Humans.rika => Human(name: "rika", age: 28, height: 160.0, job: "designer")
Problem
ですが、上記の様に Stored Property を static で宣言してしまうとシングルトンになってしまいます。
そのため、テスト間でオブジェクトのプロパティの値を変更する時に、思わぬところでテストが落ちてしまうということがあるかもしれないので、値を変更する際には気をつけなければなりません。
let takeru = Seeds.Humans.takeru takeru.age = 10 // 一度プロパティの値を変更するとその後も維持されてしまう Seeds.Humans.takeru.age => 10
Computed Property
そこで、テストデータの定義には以下のように Computed Property を用いるようにします。
こうすることで、以下のように Seeds からオブジェクトを呼ぶ出す度に異なるオブジェクトが返ってくるため、安心してプロパティの値を変更することができます。
struct Seeds { struct Humans { static var takeru: Human { return Human(name: "takeru", age: 30, height: 180.0, job: "engineer") } static var rika: Human { return Human(name: "rika", age: 28, height: 160.0, job: "designer") } } } let takeru = Seeds.Humans.takeru takeru.age = 10 // 異なるオブジェクトなので 30 のまま Seeds.Humans.takeru.age => 30
Unique Value
先ほどの Human に一意の値となる myNumber というプロパティを持たせる必要が生じたとします。
class Human { var name: String var age: Int var height: Double var job: String let myNumber: Int // 追加 init(name: String, age: Int, height: Double, job: String, myNumber: Int) { self.name = name self.age = age self.height = height self.job = job self.myNumber = myNumber // 追加 } }
こういった、 オブジェクト毎に異なる一意の値を持たせたいという場合においても、Seeds に以下の uniqueInt の様なコンピューテッドプロパティを持たせることで、オブジェクトを呼び出す度に自動的に持たせることが可能となります。
struct Seeds { static var increment = 0 static var uniqueInt: Int { increment += 1 return increment } struct Humans { static var takeru: Human { return Human(name: "takeru", age: 30, height: 180.0, job: "engineer", myNumber: Seeds.uniqueInt) } static var rika: Human { return Human(name: "rika", age: 28, height: 160.0, job: "designer", myNumber: Seeds.uniqueInt) } } } Seeds.Humans.takeru.myNumber => 1 Seeds.Humans.takeru.myNumber => 2
テストは人間が見過ごしてしまうような部分や人間がすべきでないような部分をカバーすることができるので書くに越したことはないのですが、やはり辛い面も多々あるかと思うので、テストを書く度に毎回同じ様なオブジェクトを用意していて辛いなあと感じたら、こういったテストデータ作るくんを用意してみてはいかがでしょうか!
Swift化開発合宿 in 熱海♨️
先日、会社のモバイルエンジニア5名でSwift化開発合宿に行ってきました!
普段はなかなか Swift 化を進めることができないので、こういった機会を設けてガガガっと進めてしまおうということから、今回の合宿が決まりました。
この開発合宿記録を書いていきます。
1日目
移動
みんなでお昼ご飯を食べてから宿泊所の最寄駅である網代駅に向かいました。
特急等は一切使用しなかったので、電車だけで約2時間半かかりました。
着くと綺麗な梅の花がお出迎え。
普段は都会のビルばかりを見ているので、男5人で「梅が綺麗だね」とかなんとか言いながら、熱海に到着したことに浸っていました。
合宿場となる別荘は、駅から2kmほどの山奥にあるということを聞いていたので、早速タクシーを捕まえました。
タクシーの運転手さんと「この辺はイノシシがそこらへんにいるから気をつけなよ」「昔は良くイノシシを捕まえて食ったもんだよ」「俺らからしたら犬もイノシシも変わらんよ」と、いのししトークで盛り上がっているとあっという間に到着しました。
宿泊所
旅館ではなく、元々は別荘であっただろう場所だったので、部屋はとても広くて3LDK。山の中にあるということもあり、窓からは見えるオーシャンビューはとても綺麗でした!
肝心なのは、ネット環境です。
事前情報としては WiFi 環境がないということだったので、 WiMax を持参していたのですが、山奥ということもあり、そもそもこの WiMax も使えるかということが不安でした。スイッチを入れてみるたのですが、やはり繋がらない… 部屋の中であちこち移動させてみるとなんとか繋がる場所があったのですが、これも不安定だったので、結局スマホのテザリングも併用して乗り切りました。
ちなみに作業場はこんな感じです。
Swift化
ひたすら Objective-C で書かれているコードを Swift に書き換えていきます。
前日に作戦会議をして担当範囲や進め方を決めていたので、各々、担当範囲を黙々と書き換えて行きました。
夕食 & 温泉
山の中なので近くにお店もなく、下山するのも大変なので、事前に最寄駅のコンビニで大量に食材を買っておいて、それにありつきました。近くにお店がないような環境だと、自分以外が食べているもの全部が美味しそうに見えてきてしまうのが不思議ですね…
温泉ですが、ここの宿には本来露天風呂があるらしいのですが、修理中とのことなので、部屋についているもので我慢。
ですが、蛇口からはなんと温泉が出る!23時以降は温泉が止められてしまうとのことだったので、早めに温泉を溜めておいて浸かりました。やっぱり温泉は最高!
2日目
散歩
朝に軽く散歩をしました。
自動販売機があったのでコーヒーを買ったところ、サンプルと違う…!
コーヒーには違いがないので良しとしましょう。こんなことも熱海なら全然OKです。
Swift化
散歩から帰ってきて、コンビニで買った朝食をとったらまたまたひたすら書き換えをしていきました。
2日目ということもあり、黙々とSwift化することにも慣れてガガガっと進めていきました。
夕食
最終日なのでうまいものを食べようと下山することにしました。
タクシーを呼ぼうと思ったのですが、全然捕まえることができなかったので自らの足で山を下りました。男5人が集まっても、やっぱり山はなんだか怖いので、各々が好きな音楽を流しながら気を紛らわせながら歩きました。2km といえども山道はとてもキツく、道が平坦になってきた頃には足は棒切れ状態でした…
食事はやっぱり寿司!!!にしようと思ったのですが、寿司屋はこの辺にはないとのことを伺っていたのでとりあえず魚が食べられるお店へ GO!
刺身定食を食べたのですが、必死の思いで下山したこともあり最高に美味しかったです。刺身は見た目も豪華だしとにかく最高!
帰りは運良くタクシーを捕まえられたので歩かずに済みました。歩く気力も体力もなかったので本当に良かった…
合宿を終えて
こんな感じで、海と山に囲まれて美味しい魚を食べることもできたので Swift 化という単調で楽しいわけではない作業もガガガっと進められて、開発合宿ってやっぱり最高!という気持ちになりました。普段の機能開発の時間とは別に、こういった機会があると普段なかなか行えないようなことも行えて良いですね。
合宿の環境としては、やっぱり WiFi が用意されているところを探したほうが安心でした。
山の中ということに関しては、景色は綺麗で良かったのですが、やっぱりお店が近くにないのも少し不便だったので、コンビニくらいは近くにある場所が良さそうと思いました。
次回、開発合宿をやるなら上記を満たすような場所を探そうと思います。
何より、こういった開発合宿に関して、積極的に支援してくれる会社に感謝です!
成果
合宿前の Swift の割合は約 30% でした。
そして、
合宿後の Swift の割合が…
こちらです!!!
約 5% の Objevtive-C のコードが Swift に置き換わったということですね。
ちなみに、置き換えたコード量でいうと約 5000 行でした。
まだまだ Objective-C のコードがわんさかいるので、引き続き撲滅作業を進めていきたいと思います!
iOSアプリ開発で実機のログに filter をかける
アプリ起動中のログであれば Xcode のコンソールから確認できますが、 アプリを起動したり終了させたときのログを確認したい場合には、Xcode のコンソールでは確認できないので実機のログを確認する必要があるかと思います。
このとき Xcode のツールバーの Window -> Devices から実機を選択すれば Xcode 上でログを確認することができますが、この方法だとログに対して find はできるのですが、 filter はかけられないので結構不便だったりします。
そこで、なんとか filter をかける方法がないかなと思って探してみたら方法がありました〜
libmobiledevice
こちらを使うことでターミナルでログを確認できるので、それを grep すればよいということです。
Homebrew から入れることができるのですが、 iOS10 の実機のログを確認する場合には、以下のように HEAD を指定して install しないといけないようです。
$ brew install -v --HEAD --fresh --build-from-source libimobiledevice
refs:
使い方
$ idevicesyslog -u 実機のUUID
でターミナルにログを出力することができます。
もちろん UUID を確認するコマンドもあります。
$ idevice_id -l
以下のように出力されるログに対して grep すれば filter をかけることができます!
$ idevice_id -l | xargs idevicesyslog -u | grep hogehoge
これで快適なデバッグライフを送ることができそうです!
try! Swift 記録 2日目
try! Swift に参加してきました!
ので、メモっていたことを書きます。 本当に雑なので、間違っているかもしれません。 2日目の分です。 1日目の分はこちら。
テスト可能なコードを書くということの2つの側面
なぜテスト
- バグを防げる
- テストができるコードだけ書くようにしている
- 関数型のように
- テストをドキュメント化している
- それに向かって振る舞うように実装している
- ファイルを読み出してその内容を計算してコンソールに出力して結果を返すという場合
- 入力と出力を分ける
- 入力は引数以外にも、関数の内部から直接呼び出しているものも隠された入力と言える
- 明示的なインプットと暗黙的なインプットがあるということ
アウトプットに関して
- 難しいのは side-effect があるから
- でも sideeffect はコード上で判断できるようにしておくべき
- どうやるか
- print の引数を直接返すようにする
インプットに関して
- 難しいのは co-effects があるから
- これは新しい分野なので詳しい説明はない
- 何が操作されるのかが見えないといけない
- グローバルに扱うものに関しては singleton などを直接使わずに間に struct を挟む
- グローバルに定義しないでグローバル用の一つの struct を用意して入れてしまう
- currentUser とか date も cookieStrage も langage も apiClient も scheduler も userDefaults も
- 基本的に全て protocol を間に挟んで行う
- こうして挟んだ protocol を使って引数を挟むようにする。
Q.どのタイミングでテスト書いているか
テスト駆動開発でやっている
Q.プロパティベースのテストは行なっているか
行なっていないが行いたい
誰もが知りたいSequenceとCollectionのすべて
Sequence
- エレメントのリスト
- 有限である
- 一度だけしかイテレートできない
- Sequence Protocol は map や fileter が適宜されている protocol
- これに沿って何かを実装するれば、 map や filter が使えるようになる
- 型に条件を持たせるには where を使う
- 副作用として複雑になってしまう
BidirectionalCollection
- 前にも後ろ戻りもできる Collection
- 他にも Collection が3つあるのでそこら辺見ると良さそう
Iterator の何が嬉しいか
- 基本的は内部で使っているだけなのであまり使うことがない
- 自分で何か順番で見ていく処理を追加したい時に Iterator を見るというようにすることができる。
iOSにおけるDocument IndexingとApp Search
App Search を使う
- Core SpotLigth API
- spotlight で検索したときにからアプリ内のデータを返してタップしたらアプリを開く
- 実装
- import CoreSpotlight
- CSSearchableItemIndexDelegate protocol を実装する
- 情報を最新にしておく
- beginBatch is background thread
- apple のドキュメントはない
- endBatch を呼んでから beginBatch する
- スレッド処理は手動でやらないとならない
- developer setting の identifer で Extension を取り出せる
きちんと型付けされたメッセージ
- 可読性の高いコードを書く
- メソッドのインターフェースに着目する
- 引数にどういう処理がされて返り値が返ってくるのか
- 返り値が何のための値なのか
方法
返り値をどう分かりやすくするか
- コメント
- 返り値の型を typealias で作る
- この型がどういう定義なのかを確認しなければいけなくなる
- 一度しか使わないのであればデメリットの方が大きい
返り値が optional の場合にはどうするか
- typealias
- enum で valid, invalid を作る
- これらをやると結局複雑になっているので必ず良いとは言えない
weak の値をつかう場合
- weak はいかを表す
- 保持されていない
- 要件ではない
- 変更可能
- これは本当に必要なものなのか
- こういったことは慣習を元に用意されている
- これを防ごうとしてラッパーを用意するとまた複雑になってくる
- 慣習であるならば隠蔽すると逆にわかりづらくなってくるのでは
結論
- 最適解というのはない
- 他者がどういうところを疑問に持つのかを考える
- メンバーの価値観を把握してコードを書くことが大事
モックオブジェクトをより便利にする
- メソッドが呼ばれたことを判断するフラグは Bool じゃなくて Int にすれば回数まで担保できる
- フラジャイルテストは避ける
- 壊れて欲しくないときにも壊れてしまうようなテスト
- エラーメッセージを良いものにしよう
- 引数に file や line を メッセージ をメソッドに渡す
- Hamcrest Matchers おすすめ
Client-Side Deep Learning
- MPSCNN
- metal は早いのでリアルタイムの画像解析が行える
- ニューラルネットワークをつかうことでクライアントサイドでディープラーニングができるようになった
- リアルタイムでサーバサイドと通信すると負荷がすごくなるのでクライアントサイドでやれると嬉しい
try! Swift 記録 1日目
try! Swift に参加してきました!
ので、メモっていたことを書きます。 本当に雑なので、間違っているかもしれません。 1日目の分です。2日目の分はこちら。
Swift開発者が知りたかったけど聞きにくい機械学習のすべて
- 機械学習ではデータを集めて関数を定義する
- 誤差をエラーという
- 身n長を平均する関数の場合
- 男女で分ければ誤差が違うので精度が上がる
- 複数の要素の係数を定義して足し合わせていくと正確度が上がっていく
- 線形回帰
- アプリ開発者に影響があるか
- ある
- 自分でも理解が深くなくてもできるか
- できる
- 機械学習は言語でいうとまだ ver1.0 みたいなもの
- 普及してきていて勉強できる機会が増えている
- グーグルの TensorFlow とか
- 機械学習はSwiftでできるのか
- 以下の2STEPでできる
- モデル定義して訓練
- デプロイして動かす
- モデルにはパイソンとテンサーフローを使うべき
- テンサーフローのライブラリをSwiftでバンドルすることができる
- Swiftでモデルのポーティングができる
- 以下の2STEPでできる
- どういう時に使える
- AVFoundationやCoreImageを使うようなもの
- UIデザインを使うようなものに似ている
- 確実ではないものに対して
- 機械学習のモデルのインプットはUIのような曖昧なものに向いている(例えば入力フォームとか)
- どこから始められる
Q.例で挙げたメガネの作り方は
サーバで演算して結果をモバイルに送ってレンダリングしている
ニューラルネットワークはすごいので今後はサーバ側でやっていることもモバイルでやりたい
Swift on Android
- SilverSwift の話ではない
- C言語は全ての橋渡しとなる言語
- Swift はそのC言語を触るのが得意
- UIKit は Android に移植されることはない
- Swift が Android で動くことを考えるので UIKit とかのライブラリとかは関係ない
- 本当の Android は SDK(java)で書かなければならなかった
- NDK はお粗末
- Lua
- Blurr SDK
- CMake でプリロードしてそれをシッピングする
- twitter は @codingswift
Q.メモリ管理の違い
SDKではNDKを使っている
Android はJavaメモリ管理を使っている
NDK ではあらゆるメモリを使えるようにしている
SwiftのPointy Bits
- Swiftを安全にしているもの
- 安全とは
- クラッシュするコードもかけてしまう
- 予期せぬ振る舞いの時だけクラッシュする
- 予期せぬ振る舞いを防ぐという意味で安全
- メモリアドレスはbiteが最小単位
- バッファポインタはメモリアドレスのrangeを格納する
アプリを新次元に導く3D Touch
メリット
- 3DTouch はやりたいことに早くアクセスできる
- 3DTouch 特集という取り上げられ方もある
- 3DTouch API は使いやすいよ
実装
- ホーム画面で3D Touchした時には static と dynamic アクションがある
- static は Info.plist で定義する
- 必須項目が二つあって二の項目はオプション
- dynamic はコード上で動的に定義する
Peek & Pop
- プレビューするやつ
- ユーザが3DTouch使えるか確認する必要がある
- viewDidLoad とかで forceTouchCapability == .availabe をチェックする
- 3D Touch 使えない場合はロングプレスで代替機能を実装できる
- updateもする(表示するデータの更新?)
- デリゲートメソッドは二つだけ
- UIPreviewInteraction で圧力の強さも0~1の範囲でアクセスできる
Q.テストできるのか?
まだ試していない
Q.後方互換としてロングプレスを実装することで一応対応できるといったがラッパークラスは用意しているか
OSS があるのでそれが使えそう
Pixcels プロセスと情熱
- 高価なものを安価に利用できるようにするためにアプリを作った
- 大事なこと
- 課題に注目するが大事
- 技術ではなくて人のモチベーション
- 自分のコネを生かす
毎日リアクティブ
どのような時に使うのか
- 複雑なコードがある場合に一定までは複雑になるがそれを越えると簡素化される
- 非同期なイベント(UIや通信)に有効
問題
- call stack が使えないのでデバッグがしづらい
- 一定のストリームごとでデバッギングする
- KVO がパワルのなのでどこでも使いたくなる
- オーバーヘッドがでかく、パフォーマンスが悪くなる。
- バインディングを使っていくことで解決できる
- サイドエフェクトに注意深くinjectしなければならない
- 混沌を避ける
Q.初見の人がいる場での導入できるかと学習コストはどうか
学ぶのは楽ではないがそれほど問題ないだろう
毎日触れていれば大丈夫
リアクティブを試すコミュニティも増えている
クックパッドアプリのテストを味合う
- 全コードは10万行くらい
- Swift コードは 30%
- リリースサイクルは2週間から最近は1ヶ月
- 狩野モデルにいて最低限必要なこと(must be quality)は画面遷移でクラッシュしないこと
- なぜUIテストを実装し続けてきたか
- クラッシュを防げる
- tarnip(ターニップ)でシナリオを書いている
- 設定とかでパスが必要な場合はバージョンに依存してしまうのでハードコーディングしない
- UITest は image diff でやると良い
- API の request 回数のテストも書いている
- UIの8割のテストをカバーしている
- そのおかげで早く安定して開発を行えている
- Appium を使っている
Q.Appium を選択した理由
アーキテクチャの関係
独自のツールを構築する
- ネイティブはコンパイル遅いからリアクトネイティブやってみた
- 抽象化してみようとやってみたところドメイン思考にいきつく
- 再利用できないという結論に
- プロジェクトを分けて差分だけビルドしようと思ったけど長期的に見ると複雑になって大変そう
- API志向にネイティブ開発は向いていないがリアクトネイティブは向いている
Q.どんなアプリでもリアクトネイティブにできるか - 高度なアニメーションをしたいときにはリアクトネイティブが適切かというとどうだろうか - やろうと思えばできるだろうが
Q.???
- ポッドで入れた
- ライブラリーのような扱いで入れた
- メインのアプリケーション起動ではアプリとして扱うようにした