Auto Layout の Content Hugging Priority と Content Compression Resistance Priority

すぐ忘れるので。

Content Hugging Priority

コンテンツの表示サイズに沿って表示されることの優先度。

そのため、この値が大きい方が優先的にコンテンツと同じサイズになる。

Content Compression Resistance Priority

コンテンツが圧縮されないことの優先度。

そのため、この値が大きい方が優先的にコンテンツが見切れないようになる。

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時間表示")
}

もっと良い判別方法があれば是非とも教えていただきたいです、、、

UnitTest 用のデータを共通化する【Swift】

テストをする際には複数のオブジェクトの初期化をする必要があり、この作業が面倒でテストを書くのが面倒になってしまうことがあったので、どうにかできないかと思い調べてみたところ、良さそうな方法がありました。

clean-swift.com

これを、自分が使いやすいと思った形に少しアレンジしたものを紹介してみようと思います。

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。山の中にあるということもあり、窓からは見えるオーシャンビューはとても綺麗でした!

f:id:komaji504:20170322222818j:plain

肝心なのは、ネット環境です。
事前情報としては WiFi 環境がないということだったので、 WiMax を持参していたのですが、山奥ということもあり、そもそもこの WiMax も使えるかということが不安でした。スイッチを入れてみるたのですが、やはり繋がらない… 部屋の中であちこち移動させてみるとなんとか繋がる場所があったのですが、これも不安定だったので、結局スマホテザリングも併用して乗り切りました。

ちなみに作業場はこんな感じです。

f:id:komaji504:20170322223133j:plain

Swift化

ひたすら Objective-C で書かれているコードを Swift に書き換えていきます。
前日に作戦会議をして担当範囲や進め方を決めていたので、各々、担当範囲を黙々と書き換えて行きました。

f:id:komaji504:20170322223516j:plain

夕食 & 温泉

山の中なので近くにお店もなく、下山するのも大変なので、事前に最寄駅のコンビニで大量に食材を買っておいて、それにありつきました。近くにお店がないような環境だと、自分以外が食べているもの全部が美味しそうに見えてきてしまうのが不思議ですね…

温泉ですが、ここの宿には本来露天風呂があるらしいのですが、修理中とのことなので、部屋についているもので我慢。
ですが、蛇口からはなんと温泉が出る!23時以降は温泉が止められてしまうとのことだったので、早めに温泉を溜めておいて浸かりました。やっぱり温泉は最高!

2日目

散歩

朝に軽く散歩をしました。
自動販売機があったのでコーヒーを買ったところ、サンプルと違う…!
コーヒーには違いがないので良しとしましょう。こんなことも熱海なら全然OKです。

f:id:komaji504:20170322223711j:plain

Swift化

散歩から帰ってきて、コンビニで買った朝食をとったらまたまたひたすら書き換えをしていきました。
2日目ということもあり、黙々とSwift化することにも慣れてガガガっと進めていきました。

夕食

最終日なのでうまいものを食べようと下山することにしました。
タクシーを呼ぼうと思ったのですが、全然捕まえることができなかったので自らの足で山を下りました。男5人が集まっても、やっぱり山はなんだか怖いので、各々が好きな音楽を流しながら気を紛らわせながら歩きました。2km といえども山道はとてもキツく、道が平坦になってきた頃には足は棒切れ状態でした…

食事はやっぱり寿司!!!にしようと思ったのですが、寿司屋はこの辺にはないとのことを伺っていたのでとりあえず魚が食べられるお店へ GO!
刺身定食を食べたのですが、必死の思いで下山したこともあり最高に美味しかったです。刺身は見た目も豪華だしとにかく最高!

f:id:komaji504:20170322222827j:plain

帰りは運良くタクシーを捕まえられたので歩かずに済みました。歩く気力も体力もなかったので本当に良かった…

合宿を終えて

こんな感じで、海と山に囲まれて美味しい魚を食べることもできたので Swift 化という単調で楽しいわけではない作業もガガガっと進められて、開発合宿ってやっぱり最高!という気持ちになりました。普段の機能開発の時間とは別に、こういった機会があると普段なかなか行えないようなことも行えて良いですね。

合宿の環境としては、やっぱり WiFi が用意されているところを探したほうが安心でした。
山の中ということに関しては、景色は綺麗で良かったのですが、やっぱりお店が近くにないのも少し不便だったので、コンビニくらいは近くにある場所が良さそうと思いました。
次回、開発合宿をやるなら上記を満たすような場所を探そうと思います。

何より、こういった開発合宿に関して、積極的に支援してくれる会社に感謝です!

成果

合宿前の Swift の割合は約 30% でした。

f:id:komaji504:20170323001552p:plain


そして、



合宿後の Swift の割合が…



こちらです!!!

f:id:komaji504:20170323003519p:plain

約 5% の Objevtive-C のコードが Swift に置き換わったということですね。
ちなみに、置き換えたコード量でいうと約 5000 行でした。

まだまだ Objective-C のコードがわんさかいるので、引き続き撲滅作業を進めていきたいと思います!

iOSアプリ開発で実機のログに filter をかける

アプリ起動中のログであれば Xcode のコンソールから確認できますが、 アプリを起動したり終了させたときのログを確認したい場合には、Xcode のコンソールでは確認できないので実機のログを確認する必要があるかと思います。

このとき Xcodeツールバーの Window -> Devices から実機を選択すれば Xcode 上でログを確認することができますが、この方法だとログに対して find はできるのですが、 filter はかけられないので結構不便だったりします。

そこで、なんとか filter をかける方法がないかなと思って探してみたら方法がありました〜

libmobiledevice

www.libimobiledevice.org

こちらを使うことでターミナルでログを確認できるので、それを grep すればよいということです。

Homebrew から入れることができるのですが、 iOS10 の実機のログを確認する場合には、以下のように HEAD を指定して install しないといけないようです。

$ brew install -v --HEAD --fresh --build-from-source libimobiledevice

refs:

[SOLVED] Unable to start syslog with iOS 10 on Windows · Issue #325 · libimobiledevice/libimobiledevice · GitHub

使い方

$ idevicesyslog -u 実機のUUID

でターミナルにログを出力することができます。
もちろん UUID を確認するコマンドもあります。

$ idevice_id -l

以下のように出力されるログに対して grep すれば filter をかけることができます!

$ idevice_id -l | xargs idevicesyslog -u | grep hogehoge

これで快適なデバッグライフを送ることができそうです!

try! Swift 記録 2日目

try! Swift に参加してきました!

www.tryswift.co

ので、メモっていたことを書きます。 本当に雑なので、間違っているかもしれません。 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 は早いのでリアルタイムの画像解析が行える
  • ニューラルネットワークをつかうことでクライアントサイドでディープラーニングができるようになった
  • リアルタイムでサーバサイドと通信すると負荷がすごくなるのでクライアントサイドでやれると嬉しい