退職します!
3年2ヶ月在籍したSpeeeを退職します。今日が最終日です。
なにしてたの?
元々サーバーサイドのエンジニアとして新卒入社してPHPやっていて、自社サービスのiOSアプリ用のAPIを作ってました。そのうちアプリ側のちょっとしたデバッグとか修正をXcodeでやるようになり、アプリおもしれーなとなってきたタイミングで社内公用語がPHPからRubyになるとのことで、どうせ未経験から勉強するならRubyよりアプリやりたいなとなり転向させてもらいました。そこからはそのプロジェクトでObjective-Cを書いたり幾つかのプロジェクトでSwiftを書いたりしました。
取り組んだこととしては、
- Swift1.xから2.0への移行
- RxSwiftの導入
- API抽象レイヤーの型々しい設計
- TravisCI上でCarthageの依存フレームワークをキャッシュしたりCartfile.resolvedの差分をみて更新分だけ再ビルドしたりする仕組みを作って導入
- typo警察
なんで辞めるの?
アプリチームは半分くらい(以上かな?)が業務委託メンバーで入れ替わりもそこそこあってさみしいなーみたいなことと、Ruby勢のわいわいやっていくぞという雰囲気を羨ましく思っていたことがちょっとモニョっていました。 あと、去年の夏秋くらいにプロジェクトにモニョモニョ〜という感じの状態があったことをきっかけに、漠然とあと1年くらいかな〜くらいな気持ちが湧きました。
あとは直接的な理由があるわけじゃなくて、名前を知ってる会社からオファーを貰って、経営層から現場のエンジニアまで話を聞かせてもらい、いろいろ比較するとこっちのほうがいい!となって辞めることにしたという感じです。9時半に出社しなくていいのと台風の時は自宅作業ができるのすごいという感じ。
次どうするの?
7月から五反田でiOSアプリエンジニアとして働きます。希望すればiOS以外のポジションも挑戦させてもらえそうなので、落ち着いたらKotlinでAndroidをやってみたいと思っています。六本木よりもランチが安いとのことで嬉しいです。かれーの店うどんという気になりすぎる店があるので楽しみです。ホームページがパンクで良かったです。
ほしいものリスト
AutoLayoutのエラーを紐解くときにやっていること
以前ふとこんなツイートをしたところ意外とLikeされた。
AutoLayoutのエラーがコンソールに出た時意味不明だけどviewのaccesibilityIdentifierとconstraintのidentifierに任意の文字列入れておくとメモリアドレスの部分がそれに置き換わるのでおすすめ
— 🙌🐼ひらり🐼🙌 (@hiragram) 2017年6月8日
AutoLayoutの制約に矛盾があるときにコンソールにでるメッセージ
これをみて直さないといけないときに自分がやっていることをまとめておく。
ビューのaccessibilityIdentifier
を設定する
コードからならhogeView.accessibilityIdentifier
xib/storyboardならここ。
すると
UIView:0xXXXXXXXXXXXX
と出てたところがaccessibilityIdentifier
に置き換わる。
制約のidentifier
を設定する
コードからならconstraint.identifier
xib/storyboardなら制約を選択してここ。
すると
アドレスを指定してビューに背景色をつけたりする
コンフリした制約が多すぎてidentifierつけるのがだるいときとかはlldb上でアドレスを指定してUIViewのインスタンスをとってきてbackgroundColor
で色を変える。
エラーでる画面のviewDidAppear
とかでブレークして、
(lldb) e let $v = unsafeBitCast(0x7fc85dd0a0f0, to: UIView.self) (lldb) e $v.backgroundColor = UIColor.yellow
でブレークを解除して動かすとアプリ上で色が変わる。隠れてたら階層見るアレで見て。
NSLayoutConstraintのアドレスを渡すと対象のビューに枠線をつけるlldbプラグインを作った
Python初めて書いたので許して
#!/usr/bin/python import lldb def process(debugger, command, result, internal_dict): lldb.debugger.HandleCommand(""" expr -l swift -- func $process(_ address: Int) { let constraint = unsafeBitCast(address, to: NSLayoutConstraint.self) guard let view1 = constraint.firstItem as? UIView else { return } view1.layer.borderColor = UIColor.red.cgColor view1.layer.borderWidth = 2 guard let view2 = constraint.secondItem as? UIView else { return } view2.layer.borderColor = UIColor.blue.cgColor view2.layer.borderWidth = 2 } """.strip()) lldb.debugger.HandleCommand('expr -l swift -- $process('+command+')') def __lldb_init_module(debugger, internal_dict): debugger.HandleCommand("command script add -f visualizeAutoLayoutTargets.process visualizeAutoLayoutTargets") print "enabled visualizeAutoLayoutTargets command."
導入とか使い方はこの辺を参考にしたのでそっちを見てください
使った感じはこんな感じ
AutoLayoutのエラーからNSLayoutConstraintのアドレス拾って渡すと対象のビューに赤と青の枠つけてくれるlldbプラグインできた pic.twitter.com/1KZZ1I03CH
— 🙌🐼ひらり🐼🙌 (@hiragram) 2017年6月10日
これは記事頭のツイートがちょっと盛り上がってからふーんと思って作ったものなので実務から生まれたものではないです。使いづらいとか知らない。
おわり
自分用 けっこう体調崩したとき用
- 出版社/メーカー: コカ・コーラ
- 発売日: 2012/04/03
- メディア: 食品&飲料
- この商品を含むブログを見る
ウイダーinゼリー エネルギー マスカット味 180g×6個
- 出版社/メーカー: 森永製菓
- メディア: ヘルスケア&ケア用品
- この商品を含むブログを見る
- 出版社/メーカー: 明治
- メディア: ヘルスケア&ケア用品
- この商品を含むブログを見る
- 出版社/メーカー: 小林製薬
- 発売日: 2003/02/10
- メディア: ヘルスケア&ケア用品
- クリック: 12回
- この商品を含むブログ (4件) を見る
まごころ一膳 富士山の銘水で炊きあげたおかゆ 4種×2袋 (白がゆ、梅がゆ、玉子がゆ、紅鮭がゆ)
- 出版社/メーカー: キユーピー
- メディア: 食品&飲料
- この商品を含むブログを見る
Segueを使わずにかっこよく画面遷移する方法を考えた
最近Segueをいかに安全に使って画面遷移するかということを考えていたけど、#swtwsを見ていてそもそもSegueを使わない/嫌いという人が結構いるんだなと思ったのでSegueを使わないで楽に安全に画面遷移する方法を考えてみたら意外といい感じになった。
Segueの危うい所
- コード内ではSegueのidentifierを単なる文字列として扱う所
performSegue(withIdentifier: "toDetailVC", sender: nil)
だからSegueの名前が変わった時に直し忘れていてもコンパイル時にチェックできない。
- 遷移先のViewControllerをいじるのはいわゆる
prepareForSegue
メソッドの中なので、複数の遷移に関する処理が一つのメソッドにまとめられてしまう所
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { switch segue.identifier { case "toDetailVC": // DetailVCの準備 case "toOtherVC": // OtherVCの準備 default: break } }
prepareForSegue
の中で遷移先の型でVCを取得するにはダウンキャストが必要な所
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { switch segue.identifier { case "toDetailVC": // segue.destinationの型はUIViewController let vc = segue.destination as! DetailViewController vc.hoge = "hogehoge" default: break } }
identifierと同じく行き先の型が正しいかどうかもコンパイル時のチェックができない。
これくらいかと思う。
Segueを使わずに画面遷移する
本題。
Segueを使わずに画面遷移をするということは、どうにかして遷移先のVCのインスタンスを作って、present
なりshow
なりで表示するということだろう。
VCのインスタンスをどこから作るかというと、UIStoryboardのinstantiateInitialViewController()
かinstantiateViewController(withIdentifier:_)
を使うことが多いはずだ。
コードに起こすとこんな感じだろうか。
@IBAction func buttonTapped() { let storyboard = UIStoryboard.init(name: "DetailViewController", bundle: nil) let vc = storyboard.instantiateInitialViewController() as! DetailViewController vc.hoge = "hogehoge" show(vc, sender: nil) }
確かにSegueのidentifierからは脱却したが、Storyboardの名前はまだ文字列で指定しているし、ダウンキャストもある。こんなコードがいろんなVCのいろんなところに散らばっているのは、相変わらず治安が悪いと言えるだろう。
こんな方法ならどうですか
こんな感じのコードで気軽に画面遷移できたらよくないですか。
@IBAction func buttonTapped() { show(DetailViewController.self, sender: nil) { (vc) in // ここでのvcはUIViewControllerではなくDetailViewControllerのインスタンス vc.hoge = "hogehoge" } }
これは以下の仕組みによって実現できる。
まず、UIViewControllerのサブクラスに対して、「このVCはStoryboardからインスタンスを作ることができる」ということを示すプロトコルを定義する。
public protocol StoryboardInstantitable {} // StoryboardInstantiatableなUIViewControllerのサブクラスで利用可能になるもの public extension StoryboardInstantitable where Self: UIViewController { static var storyboardName: String { // UIStoryboard.init(name:_, bundle:_)に渡すname (*1) return String.init(describing: Self.self) } static var storyboard: UIStoryboard { return UIStoryboard.init(name: storyboardName, bundle: nil) } static func instantiateFromStoryboard() -> Self { // storyboardからインスタンスを生成して自分自身の型にキャストして返す (*2) return storyboard.instantiateInitialViewController() as! Self } }
これによって、以下のコードでキャスト済のVCのインスタンスを得られるようになった。
let vc = DetailViewController.instantiateFromStoryboard()
次に、UIViewControllerにこのようなextensionを追加する。
public extension UIViewController { public func show<T: StoryboardInstantitable>(_ vcType: T.Type, sender: Any?, configuration: ((T) -> Void)?) where T: UIViewController { // 中身はのちほど... } }
シグネチャが長くなってしまったが、重要なのはvcType: T.Type
とconfiguration: ((T) -> Void)
である。
このメソッドは型パラメータTを持ち、このTはStoryboardInstantiatableプロトコルを実装し(<T: StoryboardInstantiatable>
)、且つUIViewControllerあるいはそのサブクラスである(where T: UIViewController
)という制約がかかっている。
vcType: T.Type
は遷移先のVCの型である。
configuration: ((T) -> Void)
は遷移先のVCのインスタンスを引数にとるクロージャで、この中で遷移先VCに値を渡したり、プロパティを変更したりすることを想定している。
このメソッドの具体的な実装は以下のようになる。数字の順番が逆転しているが、数字の順に説明する。
public func show<T: StoryboardInstantitable>(_ vcType: T.Type, sender: Any?, configuration: ((T) -> Void)?) where T: UIViewController { let vc = T.instantiateFromStoryboard() // ① _ = vc.view // ③ configuration?(vc) // ② show(vc, sender: sender) }
まず、①の行では先程定義したStoryboardInstantiatableのinstantiateFromStoryboard
メソッドを使い、Storyboardから遷移先VCのインスタンスを得る。
次に、②の行で呼び出し元から受け取ったconfigurationブロックを使ってVCのインスタンスを設定する。
但し、この時点ではまだIBOutletなプロパティが初期化されておらず(=nil)、例えばconfigurationブロックの中でIBOutletでStoryboardに接続されたUILabelのテキストを設定しようとすると暗黙アンラップによってアプリがクラッシュする。
これはUIViewControllerのview
が遅延ロードされるためで、一度読み出してやればサブビューも含めて初期化される(loadView
やviewDidLoad
も呼ばれる)。その為の③の行である。
configurationブロックを用いてVCの設定をしたら、あとは従来あるUIViewControllerのshow
メソッドで画面遷移を実行するだけだ。
これで、先程示した
show(DetailViewController.self, sender: nil) { (vc) in // ここでのvcはUIViewControllerではなくDetailViewControllerのインスタンス vc.hoge = "hogehoge" } // (trailing closureを使わなければ) show(DetailViewController.self, sender: nil, configure: { (vc) in // ここでのvcはUIViewControllerではなくDetailViewControllerのインスタンス vc.hoge = "hogehoge" })
という呼び出し方ができるようになった。
導入のしやすさ
一番簡単にこの仕組みに乗っかるなら、VCのクラス名 = storyboardの名前 且つ そのVCがInitial View Controllerにすれば遷移先のVCのクラスにStoryboardInstantiatableプロトコルへの適合を記述するだけでよい(追加でメソッドを生やしたりしなくてよい)。
もう既に開発が進行しているアプリで、例えばクラス名=storyboard名になっていなくて、わざわざリネームするのが面倒なときは
extension DetailViewController: StoryboardInstantiatable { static let storyboardName = "SomeOtherName" }
とデフォルト実装を上書いてやるだけでいいし、Initial View Controllerでないときも同様に
extension DetailViewController: StoryboardInstantiatable { static func instantiateFromStoryboard() -> DetailViewController { return storyboard.instantiateViewController(withIdentifier: "IdentifierForThisClass") as! DetailViewController } }
としてやるだけでよい。
ごちゃごちゃコードがあってわからん
そうおっしゃると思って、GitHubにサンプルつきで上げておきました。cloneして触ってみてください。
注意
記事中のSwiftのコードは本文を書きながらそらで打ったものもあるのでコピペしてもコンパイル出来ないかもしれません。だからやっぱりサンプルを触ってみてください。
クソアプリ開発は目的ではなく手段
クソアプリ Advent Calendarの16日目です。
先日個人で作ったSushiCameraというアプリをリリースした。
個人で作ったアプリがストアリリースまで行ったのは初めてだったので、やる前の気持ちや今の気持ちを雑に書く。
クソアプリ作るぞ〜という気持ちがあって作ったわけではなくて、「カメラ」「画像加工」あたりの技術に触りたかったのでそのために雑に作ったという感じで、大衆にウケようとか収益化しようみたいなことは一切考えてなかった。
もともと今まで関わってた(or関わってる)アプリがステルスリリースだったりファーストリリース前だったりして「私の作品はこれです」といえるものが無かったのもあって、せっかく作るなら他者から見える形で、1つのプロダクトとして最低限の体だけは整えようと思っていた。テーマにした「カメラ」「画像加工」以外の例えばUIとかシェア機能とかそういうのは本当に適当に作ったり全く作らなかったりした。
ある程度作っていろいろ好きに作り込んでみて、満足したら後は適当に整えてストアに申請。何度かリジェクトされたけどなんだかんだ通った。
自分のプロフィール(Wantedlyとか)にストアのURLを載せられるというのはなかなか気分がいい。ストアに出してからはもうコードをいじるモチベーションもそんなになくなってたのでほったらかしていたら、「SushiCameraをみてご連絡しました」という企業からメッセージが来たりした。
のちのち転職とか考えるときに効いてくればいいやーくらいに思ってはいたので、いきなりそういう連絡が来てマジかとなった。たまたまどこかで目に触れて連絡してみようとなったんだとしたら、かなり運が良かったと思う。正直これが自分のなかで成功体験としてかなり強く印象に残っていて、クソでもアプリとして出すことに意味はあるなあとより思うようになった。
自分のスキル/経験を端的に示す意味でも、その技術を扱ったシンプルなアプリを作って置いておくのはポートフォリオ的に使えてよい。それはアプリとしてはクソアプリの域を出なかったとしても、自分のアピールには十分つかえるだろう。
SushiCameraリリース以前は休日にずっとコードを書くみたいなことはあんまりなくて、せいぜい小さいスクリプトを書いて遊んだり、新しいライブラリをちょっと触ってみたりぐらいだったが、最近は結構プロダクトを作るためのコードを書く時間が増えたと思う。一昨日から新しい動画カメラアプリを作り始めた。これは習作というよりはちゃんと作り込んでみたい系のアプリだけど。
僕はクソアプリを作ったぞ〜わいわい〜みたいな楽しみ方は当人としても傍から見てもあんまりおもしろいと思っていないが、客観的にクソに見えるアプリでも、そこに自分にとって有意義なチャレンジとかアウトプットとしての意義がちゃんとあれば、どんどん作って出せばいいのではという視点を得られたよい経験だった。
アプリをつくる、サービスをつくる以外にコードのアウトプットとしてOSSなんかがあげられるけど、これは僕は過去に何度か挑戦しつつもコードを書く、技術力を身につける、ということ以外に考えなきゃいけないことが多くてなかなか大変だなという感じで何度も投げ出している。この辺は向き不向きがあると思うので、僕は今後も習作クソアプリとたまにちゃんとしたアプリをがんばってつくっていく感じの努力を続けていきたいと思う。
結局言いたいことは、クソアプリを出すことは自分の技術と経験をきちんと見える形で残しておくための手段であるということである。