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.???
- ポッドで入れた
- ライブラリーのような扱いで入れた
- メインのアプリケーション起動ではアプリとして扱うようにした
instantiateInitialViewController() が nil になる
同じことで何度もはまっている気がするのでメモ。
SampleViewController の Storyboard と Class を追加して、Storyboard から以下のようにインスタンスを作ろうとすると、nil になってしまう。
let storyboard = UIStoryboard(name: "SampleViewController", bundle: nil) let viewController = storyboard.instantiateInitialViewController() // => nil
typo もないし、何でだ〜〜〜!となっていたけど、
Storyboard の Show the Attributes Inspector で Is Initial View Controller
がチェックされていなかった、、、
Swift で循環参照するケース
循環参照という言葉を最近よく耳にしていて、なんとなく相互参照の結果メモリリークしてしまうというのは分かっていたのですが、じゃあどうしたら起きてしまうのかというところがあやふやだったので調べてみました。
循環参照
インスタンスは参照カウンタという値を持っており、変数に代入されたりして強参照されることで参照カウンタが 1 増えます。この参照カウンタが 0 になるとインスタンスは解放されます。
class Sample {} // Sample クラスのインスタンスが生成されるが参照カウンタが 0 のままなのですぐに解放される Sample() // Sample クラスのインスタンスが生成されて sample に代入されているので参照カウンタが 1 となり解放されない var sample: Sample? = Sample() // nil を代入すると参照がなくなるのでインスタンスの参照カウンタが 0 になり解放される sample = nil
循環参照とは、参照カウンタが 0 になり得ない状態をつくってしまう参照のことです。
これにより、インスタンスは解放されることなく残り続けてしまい、メモリリークとなってしまいます。
具体的なケースをいくつか書いてみます。 ( Swift 3.0 )
図では、赤枠が変数、青枠がインスタンス、緑枠がクロージャを表しています。
ケース1
class Computer { var printer: Printer? } class Printer { var computer: Computer? } var computer: Computer? = Computer() // Computer の参照カウンタ1 var printer: Printer? = Printer() // Printer の参照カウンタ1 computer.printer = printer // Printer の参照カウンタ2 printer.computer = computer // Computer の参照カウンタ2 computer = nil // Computer の参照カウンタ1 printer = nil // Printer の参照カウンタ1 // 各インスタンスの参照カウンタは1なので解放されていない // しかしプログラムから参照することはできない
各インスタンスのプロパティで一方のインスタンスを強参照しているので、変数からの参照がなくなった後も各インスタンスは解放されません。
変数からインスンタンスへの参照がなくなったことによりプログラムからインスタンスを参照することができなくなってしまい、無駄なインスタンスが生じている状態になっています。
対処例
このケースはプロパティに対して弱参照を用いることで解決できます。
class Computer { var printer: Printer? } class Printer { // weak をつけることで弱参照となる weak var computer: Computer? } var computer: Computer? = Computer() // Computer の参照カウンタ 1 var printer: Printer? = Printer() // Printer の参照カウンタ 1 computer.printer = printer // Printer の参照カウンタ 2 printer.computer = computer // Computer の参照カウンタ 1 // 弱参照は参照カウンタを増やさない computer = nil // Computer の参照カウンタ 0 , Printer の参照カウンタ 1 // computer が解放されることで computer.printer からの Printer への参照もなくなる printer = nil // Printer の参照カウンタ 0
weak
をつけてプロパティ定義をすると、そのプロパティからインスタンスへの参照は弱参照となります。
弱参照とは、参照カウンタを増やさずに参照する方法で、これにより循環参照を防ぐことができます。
delegate を実装するときに delegate プロパティに対して weak
をよくつけるかと思いますが、それはこのケースのためです。
ケース2
class Printer { var printClosure: (() -> Void) init() { self.printClosure = { print("Printer name is", self) } } func run() { printClosure?() } } var printer: Printer? = Printer() // Printer の参照カウンタ 2 , クロージャの参照カウンタ 1 printer = nil // Printer の参照カウンタ 1
クロージャは、生成されたタイミングでクロージャ内で扱われているインスタンスへの参照をキャプチャします。つまり、クロージャが解放されるまで参照が保持されるので、強参照をしている場合はインスタンスの参照カウンタが 1 増えたままということになります。
そのため、上記のケースでは、 Printer のインスタンスが生成されたときに self ( = Printer ) を強参照するクロージャが生成されているので、 Printer のインスタンスの参照カウントが 1 になります。
そして、このクロージャをプロパティへ代入しているため、 Printer のインスタンスがクロージャを強参照し、クロージャもまた Printer のインスタンスを強参照しているので循環参照となっています。
対処例
ケース1と同様にプロパティを弱参照とすることもできますが、クロージャがキャプチャする参照を弱参照へと変更する方法もあります。
その場合は以下のように [weak self]
と記述します。
class Printer { var printClosure: (() -> Void) init() { // self の参照を弱参照にする self.printClosure = { [weak self] in print("Printer name is", self ?? "") } } func run() { printClosure?() } } var printer: Printer? = Printer() // Printer の参照カウンタ 1 , クロージャの参照カウンタ 1 printer = nil // Printer の参照カウンタ 0, クロージャの参照カウンタ 0
これにより循環参照を防ぐことができます。
しかし、弱参照とすることで参照先のインスタンスが解放されることを許容することになるので、クロージャ内ではオプショナル型として扱われます。上記の場合では、 self はオプショナル型となっています。そのため、オプショナルバインディング等でアンラップする必要があります。
unowned
ちなみに unowned
というのもあり、これをつけることで参照カウンタを増やさずに参照することができます。
weak
との違いは、 オプショナル型と暗黙的オプショナル型の違いのように、 参照先が解放されてしまっている ( nil
になっている ) ときに実行時エラーとなるかどうかということのようです。
weak
は実行時エラーになりませんが、 unowned
は実行時エラーとなります。
ですが、 unowned
の場合はオプショナルバインディング等でアンラップする必要がありません。
ケース3
class Computer { var printer: Printer? func printName() { printer?.run { print("Computer name is", self) } } } class Printer { var printClosure: (() -> Void)? func run(printClosure: @escaping () -> Void) { self.printClosure = printClosure run() } func run() { printClosure?() } } var computer: Computer? = Computer() // Computer の参照カウンタ 1 var printer: Printer? = Printer() // Printer の参照カウンタ 1 computer?.printer = printer // Printer の参照カウンタ 2 computer?.printName() // Computer の参照カウンタ 2 , クロージャの参照カウンタ 1 computer = nil // Computer の参照カウンタ 1 printer = nil // Printer の参照カウンタ 1
このケースでは、 func printName()
実行した時点で循環参照となってしまっています。
このメソッド内では クロージャを受け取るメソッド func run(printClosure: @escaping () -> Void)
を実行しているのですが、渡したクロージャが Printer のプロパティへと代入されてしまっているのでクロージャへの強参照が生じています。
これにより、クロージャが Computer のインスタンスを self
で強参照する、 Computer のインスタンスがプロパティで Printer のインスタンスを強参照する、 Printer のインスタンスがプロパティでクロージャを強参照するという循環参照となってしまっています。
対処例
class Computer { var printer: Printer? // self の参照を弱参照にする func printName() { [weak self] in printer?.run { print("Computer name is", self ?? "") } } } class Printer { var printClosure: (() -> Void)? func run(printClosure: @escaping () -> Void) { self.printClosure = printClosure run() } func run() { printClosure?() } } var computer: Computer? = Computer() // Computer の参照カウンタ 1 var printer: Printer? = Printer() // Printer の参照カウンタ 1 computer?.printer = printer // Printer の参照カウンタ 2 computer?.printName() // Computer の参照カウンタ 1 , クロージャの参照カウンタ 1 computer = nil // Computer の参照カウンタ 0 , Printer の参照カウンタ 1 printer = nil // Printer の参照カウンタ 0 , クロージャの参照カウンタ 0
他と同様に弱参照を用いて防ぎます。
このケースの厄介な部分は、 func printName()
の処理を見ただけだとクロージャがどう扱われているのかわからないという点です。 今回は func run(printClosure: @escaping () -> Void)
の処理を見たときに、クロージャをプロパティへ代入していることがすぐにわかりますが、メソッド内の処理が複雑になってくると、クロージャがどう扱われるのかというのが把握しづらくなってしまいます。
@escaping
そこで @escaping
というものがあります。
func run(printClosure: @escaping () -> Void)
という部分で使われていますが、 これが付いていると、そのクロージャがどこかで強参照されるということを保証してくれます。そのため、このメソッドにクロージャを渡す際は、クロージャ内で扱うインスタンスを [weak self]
のように弱参照としておく必要があるということがすぐわかります。
まとめ
なんとなくわかった気がしたのですが、普段コードを書いていてもシュッと「これは循環参照するぞ」なんてわからなそうなので、迷ったらとりあえず weak
をつけておけばいいのかなあという感じがしました。
追記
上記のエントリで改めて @escaping 属性について書きましたが、 @escaping 属性がついていれば循環参照の可能性があるので弱参照にする、ついていなければ強参照にするで良さそうです。
UIPickerView の使い方
UIPickerView とはこういうやつです。
これを使うための最低限の部分です。
UITableView 等と同様に DataSource と Delegate を設定して、表示する列数、行数、要素を各メソッドで return するといった感じです。
import UIKit class ViewController: UIViewController { @IBOutlet weak var pickerView: UIPickerView! { didSet { pickerView.dataSource = self pickerView.delegate = self } } let components = (1...100).map { "\($0)" } } extension ViewController: UIPickerViewDataSource { // 列数 // 1列であってもこのメソッドを省略できない func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 } // 行数 func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return components.count } } extension ViewController: UIPickerViewDelegate { // 要素 func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return components[row] } }
各要素の表示領域の backgroundColor を変更したりとか色々手を加えたい場合は、上記の Delegate メソッドではなく
func pickerView(UIPickerView, viewForRow: Int, forComponent: Int, reusing: UIView?)
UIPickerViewDelegate - UIKit | Apple Developer Documentation
を使って、色々手を加えた View を返すようにすることでできそうです。
ちなみに、UIDatePicker の countDownTimer の hour や min のように固定ラベルをつけたいとなっても、それようのプロパティやメソッドはなさそうでした。 やるなら UIPickerView とは別途、固定の UILabel を置く等しなければならなそうです。