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 を置く等しなければならなそうです。
UIAlertController 使ってみた
UIAlertController を使うことで Alert や Action Sheet を画面に表示することができます。 Alert や Action Sheet とはこういうやつ↓です。
Alert | Action Sheet |
---|---|
使う際の流れとしては、以下のような感じです。 Alert も Action Sheet もやることは同じなので Alert について書きます。
- Alert となる UIAlertController のインスタンスを生成する
- 表示する各項目 (キャンセルボタンとか) を作る
- 各項目をインスタンスに設定する
- 表示する ViewController で present する
Alert となる UIAlertController のインスタンスを生成する
インスタンス生成時にタイトルとメッセージと Action か Action Sheet かを設定します。
let alert = UIAlertController(title: "Alert", message: "Alert Message", preferredStyle: .alert // ここを .actionSheet に変えると Action Sheet になります )
表示する各項目 (キャンセルボタンとか) を作る
項目の style (UIAlertActionStyle) は .default
, .destructive
, cancel
の3つがあります。
.cancel
以外は複数指定できます。
let defaultAction = UIAlertAction(title: "Default", style: .default, handler: { action in print("Default") }) let destructiveAction = UIAlertAction(title: "Destructive", style: .destructive, handler: { action in print("Destructive") }) let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: { action in print("Cancel") })
各項目をインスタンスに設定する
インスタンスに対して addAction(action: UIAlertAction)
で設定できます。
alert.addAction(defaultAction) alert.addAction(destructiveAction) alert.addAction(cancelAction)
表示する ViewController で present する
現在表示されている ViewController で Alert を表示する場合は以下のように書きます。
present(alert, animated: true, completion: nil)
iPad 対応
iPad の場合、 Action Sheet はポップオーバーで表示されます。
Action Sheet for iPad |
---|
この時、以下の設定がされていないとクラッシュするので忘れないよう気をつけてください。
// 表示先の View actionSheet.popoverPresentationController?.sourceView = view // 表示場所 actionSheet.popoverPresentationController?.sourceRect = rect
サンプル
こちらにサンプルを置いたので良かったら試してみてください。
shibuya.swift #5 で初めての LT をしてきた
shibuya.siwft で生まれて初めての LT をしてきました。
shibuya.swift にはお手伝いとして何度か参加させてもらっていたのですが、今回は自分が話す側だったのでとても緊張しました。頭が真っ白になることなく無事終わったので良かったです。
話すことになった経緯
会社の先輩に「なんか話してよ」と言われたので、「あ、はい、わかりました!(参加ボタンぽち)」という感じで LT をすることになりました。
自分は、今年配属されてからの6ヶ月間は Ruby on Rails でのAPI開発をやってきました。iOS開発をやるようになったのは7月からで、まだ3ヶ月しか経っていません。そのため、話す内容なんてない...とか思っていたのですが、勢いで参加ボタンを押してしまったので、何かネタを見つけなければと思いひたすら考えていました。
ペーペーの自分が技術的な話をするのはちょっと厳しいなと思っていたので、なんか経験談みたいのを話せればなあと思っていました。
だったら新卒の数ヶ月間で API開発とiOS開発両方やってきた人はあんまりいなそうだからそういうこと話せばいいのかあ、でも需要あるのかなあとか思っていたところ、「新卒でAPI開発からiOS開発やってきてどうだったかみたいな話が面白そう」という助言をいただいたので、やっぱりこれしかないと思い内容を練り始めました。
資料づくり
LT は見たことあるけどどうやって資料作れば良いかわからん!という感じだったので、まずは他人の発表資料を漁って、 LT の資料とはどんな感じかをつかむところから始めました。 資料だけだとちょっとよくわからんという感じもあったので、動画も見たりしました。
他にもLT 資料 作り方
みたいな感じでググったりしてみたりもしました。
まあ雰囲気は掴めた気がしたのですが、結局よくわからんとなったので、内容を練ることにしました。
まずは頭の中で、どんなことをやってきたか、どんなことを学んできたかを振り返ってみました。 ざっと、こういうことがあったなあという感じには振り返ることができ、なんとなくの話の流れをイメージすることができてので、次に、各話をもっと深堀りするためにマインドマップを書いてみました。
まあ書いてみたのですが、頭でざっと振り返った以上のことは書けずに特に進展がなかったので 、 実際にどんなことがあったのか具体的に思い出すために、 GitHub の PR や issue を眺めることにしました。
ネタ探しにはこれが一番効果がありました。この時に自分はどう考えて、どういうアドバイスをもらって、どういうことを学んだのかということを結構具体的に思い出すことができました。
そして、項目ごとに文章に書き出してみました。これを幾つか書いていると、ここの話を書こうとか、ここはやめておこうとか結構まとまってきたので、先輩に見てもらって、内容とか流れをブラッシュアップしていきました。
これを何回か繰り返して文章ではまとまったので、スライドを作り始めました。
まずスライドにはひたすら箇条書きで書いていきました。最初から最後までずらっと適当にスライドに書き起こしてから、一通りの流れを見て、それからスライドを分割したり、まとめたりを繰り返していきました。
最後にレイアウトやデザインを少しだけ修正して完成させました。
練習
あまり緊張せずとも人前で話すことができる人もいるかと思いますが、自分はとても緊張してしまいます。
今回緊張してしまう理由は、大人数の・初対面の人に対して・LT をする ことに慣れていないことだと思います。常日頃当たり前にやっていることは緊張しないのと同様に、これにも慣れてしまえば緊張もしなくなると思います。
ただ、大人数という部分にすぐ慣れるなかなか難しいかと思います。初対面の人に対してという部分も同様です。ですが、LT をするという部分は、練習を重ねることで慣れることができます。そのために、何度も練習を繰り返しました。
練習は、以下の4つのフェーズに分けて行いました。
- 原稿を読みながら練習する
- 原稿をなるべく見ないようにして練習する
- スライドだけを見ながら練習する
- 何も見ずに練習する
これらをタイマーで時間をはかったり、音声を録音したりしながら行いました。
本番
とにかくやるだけ!!
やってみて
慣れていないことをするにはとても抵抗があって、最初の一歩がなかなか踏み出せないのですが、なんだかんだやってしまえばやれるもだなあと思いました。クオリティはどうであれ。
今回の LT も準備すればできるということが分かり、とても自信となったので、また機会があればやってみたいなあという気持ちになりました。
今後は、技術的な話にもチャレンジしていきたいです。
最後に、会場準備をしてくださった Fabric さん、ありがとうございました!
Swift の Class, Protocol を Objective-C で使う
Objective-C 側で ProjectName-Swift.h を import すれば使えるでしょとか思っていたのですが、すんなり使えなかったので書いておきます。
Class を使う
SampleObjcClass.h と SampleObjcClass.m からなる Objective-C のクラス内で、 Swift で書かれた SampleSwiftClass を使いたいとします。 この方法は、ヘッダファイル (.h) と インプリメンテーションファイル (.m) で違います。
インプリメンテーションファイルでは、ProjectName-Swift.h を import するだけで大丈夫です。
// SampleObjcClass.m #import "ProjectName-Swift.h" @implementation SampleClass @end
ヘッダファイルの場合は、対象のクラスを class で宣言することで呼び出せるようになります。
// SampleObjcClass.h @class SampleSwiftClass @interface SampleObjectiveCClass @end
継承する
Objective-C のクラスで Swift のクラスを継承することはできないようです。
そのため、 Swift のクラスを継承したいときは Objective-C のクラスを Swift に書き換える必要があります。何か別の方法あったら教えていただきたいです、、、
Protocol を使う
先ほど同様、 SampleObjcClass.h と SampleObjcClass.m からなる Objective-C のクラス内で、 Swift で書かれた SampleSwiftProtocol を使いたいとします。
この場合、 Swift 側で SampleSwiftProtocol の定義に @objc を付ける必要があります。
// SampleSwiftProtocol.swift @objc protocol SampleSwiftProtocol { }
Objective-C 側は、クラスの場合と同様にインプリメンテーションファイルでは、ProjectName-Swift.h を import するだけで大丈夫です。
// SampleObjcClass.m #import "ProjectName-Swift.h" @implementation SampleClass () @end
ヘッダファイルの場合は、対象のプロトコルを @protocol で宣言することで呼び出せるようになります。
// SampleObjcClass.h @protocol SampleSwiftProtocol @interface SampleObjectiveCClass @end
適用する
適用する定義は、インプリメンテーションファイルにて記述する必要があるようです。ヘッダファイルではエラーとなってしまいます。
// SampleObjcClass.m #import "ProjectName-Swift.h" @implementation SampleClass () <SampleSwiftProtocol> @end
// SampleObjcClass.h @protocol SampleSwiftProtocol // エラーになる @interface SampleObjectiveCClass <SampleSwiftProtocol> @end