Hatena Engineer Seminar #9 @ Tokyoに参加しました!
こんにちは。iOSエンジニアの高島(Takasy (@takattata) | Twitter)です。
今回参加した勉強会ではブログ枠ではなかったものの、すごく面白かったので記事を書かせて頂こうと思います。 hatena.connpass.com
会場はサポーターズさん提供でした。
存じ上げなかったのですが、こちら、イベントスペースを無料貸出されているとのことで、覚えておこうと思いました。
間違えていたらそっと教えてくださると嬉しいです!
GUIアプリケーションの構造と設計
Hiroki Kato (@cockscomb) | Twitterさん
peaks.cc
こちらの本の6, 7章を書かれている方でした。宣伝されていたので紹介しておきますね。
ちなみに次の発表者のいけしょーさんは4章を書かれています。
ベータ版が既に配信されていて、入手はしたものの未だ読んでいないので、早く読もうと思います。
それはさておき、発表の内容としては、GUIアプリケーションを作る際に必要になる3つのパターンについての話でした。
GUIアプリケーションの特性として、
- モデルの状態をもとにUIを常に更新し続ける
- 画面間でのデータのやりとりが必要
- メインスレッドを止めてはいけない
があり、それを解決する為に
- Observerパターン
- オブジェクトの生成・管理
- 非同期処理の抽象化
の3つのパターンをうまく使おうという内容です。
このパターンを1つずつ、Android, iOS, Webだと何に当たるかを並べて紹介され、実際にどの様なものなのかをKotlinで説明して下さる、という流れです。
どのアーキテクチャを使ってもどれにもこれらが入ってくると思うし、うまく使えば整理することができるのではないか、と締めくくられていました。
この3つのパターン、ここをまず抑えたら初級レベルから抜け出せそうだな、と感じました。
学ぶべき要点が分かって非常にありがたかったです。
また、Android開発を未だしたことがないので、そういうのがあるのかーという程度にしか分からならかった点もあったので、しっかりスライドで学ばせて頂こうと思います。
そういえば、話の中でCoreDataがすごく好きと仰っていたので、その理由を詳しく懇親会でお聞きできれば良かったなぁ、と今書いていて思いました。
Swift4を振り返りSwift5の夢を見る
すごく面白かったです!
が!理解とメモが追いつかなかった...!是非スライドをご覧ください。
どの発表もそうでしたが、この発表のスライドでちゃんと復習したいと思います。
流れとしては
- Swift 4.0
- Swift 4.1
- Swift 5.0
の順に、特筆すべき新機能や特徴、それが入るとなぜ良いのかなどの説明をしてくださっていました。 1つずつ挙げるとかなりの長さになってしまいそうなので割愛します。
Swift5でメインになってくるABI Stabilityですが、これが入るのは知っていたものの、入ると何が嬉しいのかは分かっておらず、懇親会で質問させていただきました。
(※内容に誤りがある場合、自分の記憶違い+うろ覚え故ですので、コメント頂けると幸いです!)
そもそも現状Swiftでアプリを作るとSwiftの標準ライブラリなどがアプリに含まれる為、Objective-Cでアプリを作る時より大きくなっている。 iOSにSwiftの標準ライブラリなどを入れられると良いけれど、ABI Stabilityが実装されていない状態でそれをしようとすると、例えばiOSに古いバージョンのものが入っていて、アプリは最新のSwiftのバージョンのものをコンパイルしてiOSで起動させようとすると起動しない、ということが起こる。
以上を踏まえて、Swift 4 ABI安定化への道を読むと多少理解出来るかと思います。
何度か紹介されていたSwift Tweets - connpassはちゃんと後追いしようと思います。
次回はこちらです。
swift-tweets.connpass.com
あと、プロポーザルを見たことはほぼないので、確認してみようと思いました。
プロポーザルの番号もあげてくださっているので、そこから見てみようと思います。
チームの人とこんなプロポーザルあったね、という話が出来る環境、すごく面白そうですよね。
Kotlinの言語機能をフル活用したAndroidアプリの開発
可愛いうさぎの写真(飼っていらっしゃるそう)と、
「Kotlin、まさかNull安全だけで満足していませんか?」
という煽り気味なセリフ(ご本人談)から始まりました。
はてなでの新技術導入の流れからKotlinを導入する話、Kotlinの言語機能でこれが良い、という流れでした。
Kotlinを導入する時はGoogleI/Oの発表直後から1日半ほどで決定したそうです。
紹介されていたのは、
- data class
- 拡張関数 / 拡張プロパティ
- Delegated property
などです。
特に拡張関数ではKotlin化の手順も紹介されていて、JavaからKotlinに一部的にでも書き換えている場合には非常に参考になりそうでした。
また、質疑応答では、
Q:「拡張関数は便利ですが、使うルールはどう決まっていますか?」
A: 「厳格には決まっていません。チーム的にも結構、使って良いよね、という感じです。レビューでダメだったら指摘します。
Androidのフレームワークを拡張する時とか、引数4つのメソッドだけど使うのは1つだけの時とかに1つだけ設定する拡張関数作るとかで使ってます。
長くなりそうなコード且つよく使う時に使います。」
Q: 「どのファイルに何を書くとかは?」
A: 「ViewならViewの拡張関数をまとめているファイルがあったりするので、そんな感じにまとめています。」
とのことでした。
はてなブックマークアプリにおけるチーム開発について
ペロペロ (@experopero) | Twitterさん
東京にディレクター、デザイナー、プランナーがいて、京都にエンジニアがいるというチーム構成になっている為、互いの認識のずれを防ぐ工夫をしていらっしゃいました。
リリースに至るまでの流れと工夫している取り組みの紹介という内容でした。
ビジョンの共有をしっかりとしているとのことで、エンジニアだとしても技術だけでなく、プロジェクトにも主体的にならないといけない、という主旨の話をされていました。
本当にその通りだと思います。
そもそもビジョンと合致しているならば、エンジニアだからこそ湧く発想などをどんどんコミットしていきたいと思いそうですが、意識していないとその感覚も薄れていくので、こういう共有は定期的にしっかりとするべきだと改めて感じました。
特に面白かったのは、内側タイムと、見積もり時に日数をピザで例えるところですね。
インターン生用のタスクを準備する時の話も興味深かったですね。
長くなりそうなので割愛しますが、スライドに分かりやすくまとめられていましたので、是非ご覧ください。
おわりに
総じて内容が濃く、非常に面白かったです!
懇親会も非常に盛り上がっていたように感じます。
iOSだけでなく、Androidの発表もある勉強会に参加する度にAndroidを少しでも分かっていたら、もっと楽しめるんだろうなぁ...!と若干悔しくもあるので、そろそろAndroidも触ってみようかな、と思いました。
勉強会、色々な刺激を受けて良いですね。
今後も積極的にHatena Engineer Seminarの参加狙っていこうと思いました。
はてなさん、ありがとうございました!
We're hiring!!
弊社ではWeb / ネイティブアプリエンジニアを募集しております。
ご興味がありましたらお気軽にご連絡下さいませ!
エンジニアの方、ぜひ情報交換しましょう!
potatotips#44に参加しました!(iOS編)
今月勉強会に色々参加させて頂いている高島(Takasy (@takattata) | Twitter)です。
前回Money Forwardさんで開催された
に参加した際にすごく良かったので、今回エウレカさんで開催された
もブログ枠で参加してきました!
会場はお洒落で、Wi-Fiだけでなく電源も提供されていて、勉強会では珍しいなと思いました。
今回は弊社のエンジニアの井戸田くんがAndroidブログ枠で参加していたので、iOSブログ枠だった自分はiOSの発表だけに記載を留めさせて頂きますね。 Androidは乞うご期待でお願いします。
ありがたいことにtogetterにまとめてくださっている方がいらっしゃったので貼っておきます。 togetter.com
間違っている箇所等ありましたら、気軽にコメントにお願いします!
アプリ内でWebAPIを抽象化するためのフレームワークAbstractionKitの紹介
VASILYさんのテックブログにも書いていらっしゃるとのことで、併せて読むとなお良しです。 tech.vasily.jp
紹介されているAbstractionKitはこちら github.com
APIリクエスト周りで困ったことがまだ特になく、発表を聞いている時は話の内容がよく理解できませんでした。
あたりのおかげでしょうか。
同じように分からなかった方は、ブログで流れを追ってからスライドを見ると良いと思います。
- EndpointDefinitionというプロトコルを使用して、エンドポイントを定義する。(=エンドポイントのモデル化)
- 通信ライブラリと1の橋渡しとなるGenericRequest構造体を作る
- 抽象レイヤーとして機能する部分を作る(2を使って取得した結果からどうするかを記述する)
- 他画面へのデータ反映の為にブロードキャスト用のストリームを準備して、受け取りたい時に購読する
これは後ほどソースを読み込ませて頂こうと思いました。勉強になります。
スライドのまとめページの「AbstractionKit + APIKit + RxSwiftの説明時間あったらします(多分ない)」は是非ともして頂きたいなぁ。
Showcase Library について
(まだ資料があげられていなかったので、資料が上がり次第更新させて頂きます。) github.com
「UIViewが5つ横に並んでいて、これを無限スクロールでカルーセル表示したい」
を簡単に作る為に作ったライブラリの紹介でした。
横だけでなく、縦や円状の動きなどにも対応していました。
X/Y軸に-1~1で範囲を持ち、動く方向を指定できるとのことです。
図や動画など、視覚的に理解を促進するスライドだったので、資料上がるのが楽しみです。
これも後ほどソースを読み込ませて頂きたいと思いました。
Introduction Differ
corin (@corin8823) | Twitter さん
ちょうど一週間前にもCA.swift #4 - connpassで発表されていたのに、今回も発表されていました。すごい。
Fluxでの設計についてはその時の資料を参照されると良いと思います。こちらの発表も面白かったです。
speakerdeck.com
UITableViewのリストから1行削除する時、reloadDataを走らせてテーブルごとリロードするとアニメーションしないし、1行の更新なのに全て更新されるのが微妙、という点から、
github.com
こちらの紹介をされていました。
デモはこちら github.com
Fluxでアプリ作ってみたいと思うので、その際に参考にさせて頂こうと思いました。
WKWebViewのキャッシュについて調べた
www.slideshare.net
hotpepsi (@hotpepsi) | Twitter さん
競技プログラミングが趣味な強者です。
iOS10でWKWebViewのキャッシュが原因でユーザーのアプリの使用量がすごいことになったところから調査が始まったそうです。
調査する時の勉強にもなりそうだと思いました。
WKWebViewがオープンソースだということも知りませんでした。いずれ読んでみたいです。
各OSでのWKWebViewの調査結果が載っていて、iOS10で肥大化したのはバグではないかとのことでした。
バグレポートに書く + WWDCで開発者に伝え、iOS11ではキャッシュがアプリの使用サイズには含まれなくなったそうです。
「同じAPIでも、内部実装や挙動が変わることがある」というのを忘れないようにしようと思いました。
入力を型で表現する
Motoki Narita (@motokiee) | Twitter さん
KAURUの製品情報の取得がサクサクですごく触ってみたくなるデモから始まりました。
この出品画面がそこそこ複雑で、今後入力経路が増えることも考えて、複数の入力を1つの型で表現するようにされた話でした。
データの有無で判別するとかでなく、入力の型自体で分けるのは分かりやすくて良いなと思いました。
まとめにも書かれていらっしゃいましたが、「データ自体の処理と画面の処理を分離しやすい」のは良いですよね。
ソースをたくさん貼ってくださっていて、具体的な実装方法が分かるので、スライド必見です。
carthage verify
CocoaPodsしか使ったことがないのでピンと来なかったのですが、Carthageではライブラリに追随していない時にはビルドエラーになるという仕組みが標準ではないそうです。
その問題にどうアプローチしていったかの話でした。
結論としてはCarthage/workflowsにあるcarthage-verifyスクリプトを使うとCocoaPodsと同じことが実現できるそうです。
Carthageを使う時の為に覚えておこうと思います。
余談ですが、コントリビュータのいけしょー (@ikesyo) | Twitterさんが素敵なツイートをしていらっしゃっいました。
なんだか #potatotips でCarthageが盛り上がってたようですが、何かあればお声掛けください。
— いけしょー (@ikesyo) 2017年10月25日
AR FACE DETECTION
Kaoru (@TachibanaKaoru) | Twitter さん
ARKitの紹介や現在iPhoneXのみ利用可能な顔認証の話でした。
ARの敷居は低く、どちらも実装が非常に簡単とのことです。
Xcode9のテンプレートプロジェクトから立ち上げて動くので、たしかにすぐ出来そうだな、と思いつつ、SceneKitやSpriteKitを触ったことがないので、そこのインプットも考えると優先順位が後回しになっていく現状がありますね...。ですが、やはりARKitは触っていきたいな、と改めて思わされる内容でした。
どういう仕組みで動いているのかが分かる発表だったので、是非スライドをご覧ください。
紹介されていた不思議の国のアリスARは触ってみようと思います。(かなりウサギが怖いですが。)
iOSのCorner Rouding Strategy
Shunki Tan🤔 (@shunkitan) | Twitter さん
紹介されていたドキュメントはこちら Texture | Corner Rounding
スクラムマスター。懇親会でチーム開発についての話聞いてみようかと思って完全に忘れました...。
CALayerのcornerRaduisは毎フレーム、レンダリングが発生しているとのことで、極力使うのは避けようという話でした。
Cornerの種類を挙げ、ハイパフォーマンス順に実現の仕方を紹介してくださいました。
紹介されていたドキュメントのフローがすごく分かりやすかったです。
アプリのパフォーマンスを上げないと辛くなった時に手をつけようと思います。
終わりに
以上、iOSの発表だけですが、1つ1つの内容が濃かったなと感じます。
ブログを書くと内容を振り返らざるを得ないから良いですね!
参加した勉強会は今後なるべく記事にしていこうと思いますので、今後ともよろしくお願いします。
ちなみに、KAURUチームが書いた薄い本の仁義なきじゃんけん大会では敢え無く負けました。
We're hiring!!
弊社ではWeb / ネイティブアプリエンジニアを募集しております。
ご興味がありましたらお気軽にご連絡下さいませ!
エンジニアの方、ぜひ情報交換しましょう!
iOS Tips集 2
こんにちは。エンジニアの井戸田です。
Cluexでは7月に「ままのて」という、 妊娠、育児に関する医師監修のQ&Aやメッセージをお届けするアプリをローンチしました。
前回iOS Tips集 第1弾を書きました。第1弾はこちらの記事になります。 cluex-developers.hateblo.jp
今回はその第2弾になります。Nativeチームから集めた知識を文章化しました。
NavigationBarを透過、borderを消す方法
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // 処理 self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) self.navigationController?.navigationBar.shadowImage = UIImage() } }
StatusBarの文字色を白にする
storyboard、コードで変更することは可能ですが、1つ1つ全てのViewControllerに対してやらなくてはいけません。
以下の方法では一回の設定で全てのViewControllerのStatusBarの文字を白にすることができます。
TARGETSのinfoに追加する。
key: View controller-based status bar appearance Value: No key: Status bar style Value: UIStatusBarLightContent
画像に合わせてアスペクト比を変更
表示した画像のアスペクト比が異なる場合、StroyboardのAspect Rationは使えません。
ソースコード上で画像のアスペクト比を変更させることができます。
NSLayoutConstraint
というクラスを使用して制約を設定する。
imageView.image = image let constraint = NSLayoutConstraint(item: imageView, attribute: .height, relatedBy: .equal, toItem: imageView, attribute: .width, multiplier: (response.value?.size.height)! / (response.value?.size.width)!, constant: 0) NSLayoutConstraint.activate([constraint])
- storyboardの方でUIImageViewの上下左右の制約をかける。ここではアスペクト比やwidth, heightの制約はかけません。
tableView:estimatedHeightForRowAtIndexPath
メソッドでUITalbeViewAutomaticDimension
を返す。
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: indexPath) -> CGFloat { return UITableViewAutomaticDimension }
ref.) http://crossbridge-lab.hatenablog.com/entry/2016/02/29/125917
NavigationBarに複数のボタン配置した時の隙間を無くす
図1のようにナビゲーションバーに複数ボタンを入れる場合、以下のようにするとボタンの間に隙間が開いてしまう。
func setupNavigationItem() { let starIcon = UIImage.fontAwesomeIcon(name: .starO, textColor: .darkGray, size: CGSize(width: 36, height: 36)) let icons: [UIBarButtonItem] = [ UIBarButtonItem(image: starIcon, style: .plain, target: self, action: nil), UIBarButtonItem(barButtonSystemItem: .action, target: self, action: nil) ] navigationItem.rightBarButtonItems = icons }
図2のように隙間をなくす場合、一度UIButtonに入れてあげてからsizeToFit関数を呼んでUIBarButtonItemにしてあげることで隙間がなくなる。
func setupNavigationItem() { let starIcon = UIImage.fontAwesomeIcon(name: .starO, textColor: .darkGray, size: CGSize(width: 36, height: 36)) let icons: [UIBarButtonItem] = [ translateBarButtonItem(icon: starIcon), UIBarButtonItem(barButtonSystemItem: .action, target: self, action: nil) ] navigationItem.rightBarButtonItems = icons } private func translateBarButtonItem(icon: UIImage) -> UIBarButtonItem { let button = UIButton() button.setImage(icon, for: .normal) button.sizeToFit() return UIBarButtonItem(customView: button) }
UIStackViewに背景色を付ける
UIStackViewにはUIViewが内包されておりません。
UIStackView自体に背景色を付けるためにはUIStackView内にViewをaddSubviewをする必要があります。
まずaddSubviewするUIViewのConstraintsを制御するExtensionを切ります。
extension UIView { func pin(to view: UIView) { NSLayoutConstraint.activate([ leadingAnchor.constraint(equalTo: view.leadingAnchor), trailingAnchor.constraint(equalTo: view.trailingAnchor), topAnchor.constraint(equalTo: view.topAnchor), bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) } }
上記のExtensionは該当するUIViewを引数のUIViewに対して、スペース無く一面に広げる制約を付与します。
下記はUIViewControllerの一例です。
UIStackViewにUIViewを挿入し、最後にpinでUIStackViewのサイズに広げています。
class ViewController: UIViewController { @IBOutlet weak var stackView: UIStackView! private lazy var backgroundView: UIView = { let view = UIView() view.backgroundColor = .red return view }() override func viewDidLoad() { super.viewDidLoad() pinBackground(backgroundView, to: self.view) } private func pinBackground(_ view: UIView, to stackView: UIStackView) { view.translatesAutoresizingMaskIntoConstraints = false stackView.insertSubview(view, at: 0) view.pin(to: stackView) } }
ref.) https://useyourloaf.com/blog/stack-view-background-color/
We're hiring!!
弊社ではWeb / ネイティブアプリエンジニアを募集しております。
ご興味がありましたらお気軽にご連絡下さいませ!
エンジニアの方、ぜひ情報交換しましょう!
iOSDCにLTで参加してきました!
こんにちは、iOS開発中ながら先日メイン機をAndroidにした高島です。
今更ではありますが、今回iOSDCに初参加<をさせて頂いたことについて振り返ろうと思います。
ちなみに話させて頂いた内容はこちらです。
iOSDCとは
今年で2回目の開催で、9/15-/17に早稲田大学の西早稲田キャンパスで開催されました。
カンファレンスと言うとGDC(Game Developers Conference)にしか行ったことがなく、欠片も話が分からずにとりあえず空気だけ楽しんで帰ってきた思い出しかなかったので、話についていけるだろうかと内心ドキドキしていましたが、フレンドリーさがあるカンファレンスでとても良かったです!参加して良かった!
運営スタッフの、交流の場にしてほしいという想いがすごくよく伝わるカンファレンスでした。
もちろんトークの内容もすごく良かったですよ!
気になる内容はこちらの神記事をご覧下さい。(参加ブログや俺コンまでまとめて下さっているので本当にありがたいです。)
qiita.com
*俺コン: 採択されなかったトークを話す場が後日持たれていたのですが、その内の1つです。
LTで話すに至るまでの経緯
そもそも、iOSDCを知ったきっかけは勉強会の時でした。
iOS開発をする様になってから勉強会に少しずつ参加する様になったのですが、7/21に行なわれたこちらの勉強会
iOS Creators' Meetup vol.4 - connpass
に参加していたiOSDC運営スタッフの方々が何度も「CfP出しましたよね!」と仰っていたことで知るようになりました。
CfPを出すのは「初心者だしなぁ」と躊躇っていましたが、運営スタッフの方に「初心者枠的なの欲しいからどんどん出すと良いよ。肉の話とかiOSと関係ない話をする人もいるくらいだしね!」と背中を押して頂き、冷める前に夜な夜な3本ほどLTのCfPを提出しました。
そして1週間後、トーク採択のお知らせメールが届きました。(驚いて三度見くらいしました。)
LT準備期間
OSSコードリーディングを題材にしたのも、上記の勉強会の際にNuke(GitHub - kean/Nuke: A powerful image loading and caching framework)というOSSを読んだことをLT(初めてOSSを読んでみた(Nuke) // Speaker Deck)で話させて頂いたところから来たのですが、iOSDCのLT枠が5分ということを考えるとOSSの中を追うには時間が足りないので、OSSを取り上げていくことは諦めました。
そこでコードリーディングの利点や読むコツにフォーカスし、改めて自分の方法を振り返りつつ、コードリーディングについての資料を漁り、まとめました。
伝えたいことについてはスライドにまとめられたと思いますので、ご興味ある方はご覧頂けると幸いです。
また、採択されてから期間もある中で、自身のモチベーションを保つ為に、
というのは凄く効果的でした。
余談ですが、前夜祭後に開催されたスピーカーディナーで、「発表いつですか?」「明日です。」「いいなー!日曜日は高みの見物できるじゃないですかー!」とか「まだスライドできてないんですよ...!!帰ったら頑張りますハハッ」などの話で一体感が生まれる感じが面白かったです。
いざ、LT!
話す直前までものすごく緊張していました...。 (最初スクリーンの前に晒されてる時の緊張感と言ったらなかったですね...!!↓その時の写真)
前回のLTの様子を知らなかったので、LTが始まると共になにやらかっこいい音楽が流れ始め、乾杯とともにノリノリな感じで始まったことにとても驚きましたし、LTの場数を踏んでいそうな方々の発表が続き、内心非常に焦りました。
実際前に立って話してみると、皆さん飲み物片手にすごくよく聴いてくださっていて、話しやすかったです。
iOSDCをLTのスピーカーとして参加してどうだったか
今回iOSDCへの参加も含め、LTで参加させて頂く過程を通して得られたと思う点は
- 外部に発信することへの敷居が下がった
- 人とのつながり
です。
外部に発信することへの敷居が下がった
もともと学んだことを人に伝える習慣がなかったのですが、弊社に入社してから、社内LT大会などを通して発信する機会を得るようになりました。
とはいえ、社外に発信するとなると、踏みとどまってしまうことが多くありました。
今回LTを発表して、「このくらいの内容でも大丈夫なんだ」「がっつりとした技術の発表までは出来なくても、気づきやきっかけを提供することは出来るのだな」と感じ、外部に発信することへの敷居がかなり下がりました。
また、自分が気づいていなかったことに対してフィードバックをしてくださる方もいらっしゃるので、得られるものは大きいと感じました。
マサカリが飛んでくることを恐れてしまっては、その得られる機会を逃してしまうので、今後は得た知見や気づきを積極的に発信していこうと思います。
人とのつながり
iOSDCでは「隣の人と話してみてください」とスタッフが呼びかけていることもあり、人に声を掛けやすい雰囲気があります。
携帯でTwitterを見ていたら、「そのクライアント作ったの自分なんですよ!使ってもらえて嬉しいです!」と声を掛けてもらえたり、スピーカーはネームカードにTwitterアカウントのアイコンが印刷されているので、そのアイコンを見て「あ、フォローしてます」と声を掛けてもらえたりもしました。
(もっと積極的に声を掛けてみれば良かった...!!と今になって後悔しているので、来年は自ら声掛けにいこうと思います。)
懇親会の時にもスピーカーとして参加していると、話した内容について話し掛けて頂いたり、その話題をきっかけに話し掛けたり出来るので、非常に話しやすかったです。
今回を機に少し人とのつながりが増えたように感じました。
(ちなみにiOSDCで出た料理、昼食も含め美味しかったです。)
参加後に弊社で取り組んでいること
弊社のアプリ開発チームのエンジニアは全員iOSDCに参加していたので、参加後、皆でiOSDCの内容をフォローすべく、聴いたセッションの共有をしました。 現在は、ラーニングランチという、昼食時にチーム開発について話し合ったり、知識を共有する時間を使って、アーキテクチャのセッション中に上げてくださっていたこちらのサンプル(https://github.com/marty-suzuki/iOSDesignPatternSamples)のコードリーディングを行なっています。 今後は俺コンやリジェクトコンなどのフォローもしていっても良さそうですね!
終わりに
iOSDC本当に良かったです!スタッフの方々ありがとうございました! 今回得られたことを今後に生かして、より良いアプリを作るべく精進したいと思います。
写真はiOSDC Japan提供のものを使用させて頂きました。ありがとうございます。
全写真です。ご査収ください! #iosdc
— iOSDC (@iosdcjp) 2017年9月21日
前夜祭https://t.co/bDZi3cbgQM
1日目https://t.co/DXQSr3xyx4
2日目https://t.co/25tEGJRrZA
みんなのアルバムhttps://t.co/nEuFBpBZaD
We're hiring!!
弊社ではWeb / ネイティブアプリエンジニアを募集しております。
ご興味がありましたらお気軽にご連絡下さいませ!
エンジニアの方、ぜひ情報交換しましょう!
Railsアプリ開発の豆知識 その1
こんにちは。エンジニアの内山です。
今回は社内で個人課題としてRailsサンプルアプリを開発することになりましたので、その様子を書きます。 現在も引き続き取り組んでいますが、その途中経過を書きたいと思います。
私はRuby on Rails初心者の状態(前職で多少使っていましたがほぼ独学)で今年の6月にCluexにジョインし、 弊社の運営するwebサービス「mamanoko」に関する簡単なタスク対応をしていたのですが、 Railsに関する基本的な知識やベストプラクティス、その他周辺技術(特にフロント技術が苦手です・・・)についてまだまだ実力不足を痛感しており 実装で壁にぶち当たることもありました。
そういうわけで、「スクラッチでアプリを開発し、開発のアンチパターン・ベストプラクティスを知る」ことを目的に サンプルアプリを開発することにしました。 Rails開発に関するtips、チュートリアルやブログなどはとても参考になるものが多数ありますが、 レビューで受けたアドバイスや調べたこと(=今回学んだこと)を元に豆知識としてまとめたいと思います。 特に開発初心者の方の役に立つと私としてはとても嬉しいです。
作るもの
- ブログアプリ:ユーザを作成してログインすると、記事の閲覧・作成などができる。また、記事には複数のキーワード(タグ)を設定することができる。
- Rails, Gem, js, jQuery, bootstrapを使い、ユーザ認証や画像アップロード機能を実装し、スタイルも整える。
- テストも書く。 ※学習時間も含め、約1ヶ月で実装。毎週開発リーダーのレビューを受ける。
設計
基本を押さえて、以下の方針で設計しました。
DOA
DOA(Data Oriented Approach)とは、サービスを開発する際に、データの設計から行うアプローチです。
webサービスでは機能変更などが度々発生しますが、データの構造の変更は頻繁にあるわけではないので、 設計段階でデータの構造をしっかり決めておくことで効率よく開発ができるという考え方です。 複数のシステムを開発する際にDBを共有するときにも重要です。 今回は以下のようなデータ構造にしました。
REST
RESTの基本アクションを利用した設計です。例えば記事機能(articles)はこのような形で構成します。 この説明が分かりやすいと感じました。(http://igarashikuniaki.net/rails_textbook/crud.html)
action | 処理 |
---|---|
#index | 全記事を表示する画面 |
#show | 選択した記事を表示する画面 |
#new | 記事を新規作成する画面 |
#create | #newからpostされた記事を作成し、#indexにリダイレクトする。画面は無し。 |
#edit | 選択した記事を修正する画面 |
#update | #editからpostされた記事を更新し、#indexにリダイレクトする。画面は無し。 |
#destroy | 選択された記事を削除し、#indexにリダイレクトする。画面は無し。 |
実装開始
ということで実装に入っていきます。まっさらな状態で一瞬迷ってしまいますが、 DOAの考え方に従って、変更の少ないModel→Controller→Viewの順で作っていきます。 機能としては大きく記事機能(articles)、記事へのタグ付け機能(keywords)、ユーザ認証(users)がありますが、スモールステップでまずはarticlesの機能から作っていくことにしました。
「スモールステップでつくる」といっても「articlesのmodel、controller、viewからつくる」という単位ではおそらくダメで、DOAの考え方から、先に必要なモデル/DBはすべて作っておくべきです。「変更の可能性の少ないDB周りから実装を固める」というのが肝です。あとからモデルを追加してリレーションを設定したりするのは混乱の元なので・・・ このため、articles、keywords、articles-keywords(中間テーブル)のモデルとDBを先に作ります。(ただし、usersについてはdeviseというGemを利用して後ほど作ります。) なお、この記事で全てのリソースを取り上げるのは時間がかかるので、今回は記事機能(articles)を中心に見ていきたいと思います。
Model
このように実装し、マイグレーションしました。なお、本記事では明示していませんが、文字数制限等を設けたいのでそれらの設定を行なっています。
Modelです。入力文字数によってバリデーションをかけ、各テーブル間のアソシエーションを設定しました。
[app/models/article.rb(記事)] class Article < ApplicationRecord validates :title, presence: true, length: { maximum: 30 } validates :text, presence: true, length: { in: 10..1000 } attachment :image has_many :articles_keywords has_many :keywords, through: :articles_keywords end
[app/models/articles_keyword.rb(中間テーブル)] class ArticlesKeyword < ApplicationRecord belongs_to :article belongs_to :keyword end
[app/models/keyword.rb(キーワード)] class Keyword < ApplicationRecord validates :keyword, presence: true, uniqueness: true, length: { maximum: 20 } has_many :articles_keywords has_many :articles, through: :articles_keywords end
migration
migrationです。各DBカラムに対して、必要に応じてオプションを設定しました。
[db/migrate/yyyymmddhhmmss_create_article.rb(記事)] class CreateArticles < ActiveRecord::Migration[5.0] def change create_table :articles do |t| t.string :title, null: false t.text :text, null: false t.string :image t.references :user, index: true, foreign_key: true t.timestamps null: false end end end
[db/migrate/yyyymmddhhmmss_keyword_article.rb(中間テーブル)] class CreateArticlesKeywords < ActiveRecord::Migration[5.0] def change create_table :articles_keywords do |t| t.references :article, index: true, foreign_key: true t.references :keyword, index: true, foreign_key: true t.timestamps null: false end end end
[db/migrate/yyyymmddhhmmss_keyword.rb(キーワード)] class CreateKeywords < ActiveRecord::Migration[5.0] def change create_table :keywords do |t| t.string :name, null: false t.timestamps null: false end end end
Model/Migrationに関する学び
【Gemについてあらかじめ調べておく】 ここまで、あたかも想定通りに進んでいるかのように書いたのですが(その方が分かりやすいという判断もありましたが・・・)、実はUserモデルをいったん作成したものの削除したりしました。ユーザー認証機構としてdeviseというGemを使用する予定だったため、機能を実装しながら平行してdeviseの知識をインプットしていた折、必要な機能(ファイル)のほとんどがdeviseを導入することで簡単に生成できることが分かったためです。Gemやプラグインを利用するのがあらかじめ分かっている場合は事前に調べて手戻りを防ぐべきだなと、反省です。今後の実装の教訓としたいと思います。
【DBのカラムにオプションをつける】 データ操作に関するバリデーションはActiveRecordによって実現しますが、予期せぬDB不整合を防ぐために、マイグレーションファイルのDBカラムにも各種オプションを設定すべきです。null値を許容しない「null: false」以外にも、以下のようなオプションがあります。DBの整合性を維持するために設定しておきましょう。
option | 内容 |
---|---|
string | 文字列 |
text | 長い文字列 |
integer | 整数 |
float | 浮動小数 |
decimal | 精度の高い小数 |
datetime | 日時 |
timestamp | タイムスタンプ |
time | 時間 |
date | 日付 |
binary | バイナリデータ |
boolean | Boolean |
Controller
このような実装にしました。CRUDの考え方に基づいた標準的な作りです。
[app/controller/articles_controller.rb] class ArticlesController < ApplicationController before_action :set_article, except: [:index, :new, :create] # GET /articles def index @articles = Article.order(id: :desc) end # GET /articles/:id def show end # GET /articles/new def new @article = Article.new end # POST /articles def create @article = Article.new(article_params) if @article.save redirect_to articles_path else render :new end end # GET /articles/:id/edit def edit end # PATCH /articles/:id def update if @article.update(article_params) redirect_to articles_path else render :edit end end # DELETE /articles/:id def destroy @article.destroy redirect_to articles_path end private def article_params params.require(:article).permit(:title, :text, :image) end def set_article @article = Article.find(params[:id]) end end
Controllerに関する学び
- 【generatrorで作成されるファイル】 ジェネレータを使うと様々なファイルが自動的に作成できて便利ですよね。でもどのようなファイルが作られるかご存知でしょうか?このようなファイルが作られます。
$rails generate controller articles Running via Spring preloader in process 44 create app/controllers/articles_controller.rb invoke erb create app/views/articles invoke test_unit create test/controllers/articles_controller_test.rb invoke helper create app/helpers/articles_helper.rb invoke test_unit invoke assets invoke coffee create app/assets/javascripts/articles.coffee invoke scss create app/assets/stylesheets/articles.scss
せっかく作成されたファイルですが、例えば「helperはいらないなぁ」というようなことがあると思います。そういうときは、config/applicaiton.rb
を以下のように編集することで、不要なファイルが作成されなくなります。
[config/application.rb] module Sample class Application < Rails::Application config.generators do |g| g.helper false end end end
すると、指定したとおりhelperが作られなくなります。
$rails generate controller articles Running via Spring preloader in process 108 create app/controllers/articles_controller.rb invoke erb create app/views/articles invoke test_unit create test/controllers/articles_controller_test.rb invoke assets invoke coffee create app/assets/javascripts/articles.coffee invoke scss create app/assets/stylesheets/articles.scss
めったに使わないならrails generate controller articles --no-helper
でいいんですけどね。。。私の場合、実際はcontrollerが必要になったらファイルを新規追加で作ってしまったりすることもありますが、generatorよく使う方は試してみてください。
- 【deleteとdestroyの違い】 destroyアクションの中では、指定した記事を削除するという処理を行います。 削除にはdeleteメソッドまたはdestroyメソッドが使用可能ですが、その違いは「ActiveRecordオブジェクトを介する削除にはdestroy、そうでない削除にはdeleteを使う」と説明されます。ActiveRecordオブジェクトを介して処理を行うとは、「コールバックを通りバリデーションが行われてから削除される」ということを意味します。
ActiveRecordを利用すると「あるオブジェクトを削除したら、関連するオブジェクトも同時に削除する」という処理をコールバックによって実現することができます。複数のオブジェクト間で依存関係がある場合、コールバックを通ることでその依存関係も一緒に削除できます。
VIew
indexだけ載せます。若干スタイル等入っていますが、まだまだシンプルな状態です。
[app/views/articles/index.html.erb] <div class='container'> <h1>#index</h1> <div class='center-block'> <% @articles.each do |article| %> <div class='well well-lg'> <h3><%= article.title %></h3> <%= simple_format(article.text) %> <%= link_to 'show', article_path(article) %> <%= link_to 'edit', edit_article_path(article) %> <%= link_to 'destroy', article_path(article), method: :delete %> </div> <% end %> </div> </div>
Viewに関する学び
- 【view側でソートしない】 今回のアプリでは最新の記事が一番上に来るように表示したかったので、当初はviewで以下のように実装していました。
… <% @articles.reverse_each do |article| %> …
が、必要な処理はviewに渡す前に行うべきであるという指摘に納得し、以下の通り書き直しました。 前掲のコントローラ内で以下の通り処理し、viewでは余計な処理をしないように修正しました。
[app/controller/articles_controller.rb] def index @articles = Article.includes(:user).order(id: :desc) end
[app/view/articles/index.html.erb] … <% @articles.each do |article| %> …
ソートに限らず、Rails内でロジックを書くべき場所は常に意識しようと思います。
routing
articlesのみ設定した状態なのでこんな感じです。
[config/routes.rb] Rails.application.routes.draw do resources :articles end
次回予告
サンプルアプリ開発は引き続き取り組んでおり、現在、キーワード設定機能やユーザログイン機能などを実装した段階です。 最低限のCSSで整え、現在はこんな感じです。見た目についてもbootstrapを利用してさらにリッチにしていく予定です! 続編のブログでは、Gemの導入などに関する豆知識などをお伝えできればと思います。
それではまた次回!
We’re hiring!!
弊社ではWeb / ネイティブアプリエンジニアを募集しております。
ご興味がありましたらお気軽にご連絡下さいませ!
エンジニアの方、ぜひ情報交換しましょう!
iOS Tips集
こんにちは。エンジニアの志村です。
Cluexでは7月に「ままのて」という、妊娠中〜育児中まで医師監修のQ&Aやメッセージをお届けするアプリをローンチしました。
上記に伴いNativeアプリ専門のチームが出来ました。
現在、私含め3人でままのての改善やAndroid版の実装を行っております。
社内では2ヶ月に1回のLT大会や毎日終業前に行う豆知識の時間(軽いTips等を1人2〜3分程度で話す時間)を利用して、積極的に知識の共有や新しい知識を習得するようにしております。
今回はその中で出た知識をまとめたものを文章化しました。(このシリーズ定期的に出したいなぁ)
bounds
スクリーンからみて、Viewのサイズや位置などを保持するプロパティ。
class HogeViewController: UIViewController { @IBOutlet weak var tableView: UITableView! override func viewDidLoad() { super.viewDidLoad() // スクリーン上のtableViewのwidthを取得 var width = self.tableView.bounds.width // スクリーン上のtableViewのheightを取得 var height = self.tableView.bounds.height } }
by 井戸田
IPアドレスの取得
1. getifaddrs(&ifaddr) 2. if (flags & (IFF_UP|IFF_RUNNING|IFF_LOOPBACK)) == (IFF_UP|IFF_RUNNING) 3. freeifaddrs(ifaddr)
ポイント
- ifaddr変数のポインタを渡して、ifaddrに取得した内容を格納する。
- ビットフラグで状態確認している。flagsがUP, RUNNINGの時&&LOOPBACKではない時を指す。
- getifaddrs関数の中でifaddrにメモリ確保しているので、メモリ解放する為にifaddrを渡す。
参照
get an iPhone's device name and get Ip address in swift 3
by 高島
addSubview, addSublayerをした時にきちんとremoveしよう
addSubview, addSublayerをUIViewに対して行った場合、追加されたUIViewやCALayerは残り続ける。
もし、UITableViewのcellForRowAt等で当該処理を行った場合、スクロールの度にUIViewやCALayerが追加され、メモリを圧迫するだけでは無く、表示そのものが崩れる場合があるので注意。
なので基本的にaddSubviewやaddSublayerを使用する場合は、事前にremoveFromSuperview, removeFromSuperlayerを行うべきである。
func addCornerRadius(corners: UIRectCorner, radius: CGFloat) { if let oldMaskLayer = self.layer.value(forKey: "maskLayer") as? CAShapeLayer { oldMaskLayer.removeFromSuperlayer() } let maskPath = UIBezierPath( roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius) ) let maskLayer = CAShapeLayer() self.layer.setValue(maskLayer, forKey: "maskLayer") maskLayer.frame = self.bounds maskLayer.path = maskPath.cgPath self.layer.mask = maskLayer }
上記コードはUIViewに対して特定のcornerのみにradiusを付ける関数である。
上記コードで、もしif let oldMaskLayer〜
が存在しない場合 && tableView.reloadData()
を行い、heightが再計算される場合、前回使用していた高さでCAShapeLayerが描画されており、2重に線が描画される恐れがある。
by 志村
iOS9におけるremoveObserverの扱い
iOS9より、NotificationCenterはaddObserverで受け取ったオブザーバをweakで参照するようになる。
なので、明示的にremoveFromObserverをする必要がなくなった。
// Before iOS9 override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: "update:", name: MyNotification, object: nil) } deinit { NotificationCenter.default.removeObserver(self, selector: "update:", name: MyNotification, object: nil) } // iOS9 override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: "update:", name: MyNotification, object: nil) } deinit { }
参考
swift - NSNotificationCenterのremoveObserverについて - スタック・オーバーフロー
by 志村
以上になります。 このような感じで毎日知識を共有するようにしております。 次回からAndroid版も出来ると良いなぁ…
We’re hiring!!
弊社ではWeb / ネイティブアプリエンジニアを募集しております。
ご興味がありましたらお気軽にご連絡下さいませ!
エンジニアの方、ぜひ情報交換しましょう!
Docker with ECS × Railsを実現させるために考えたこと(CI編)
こんにちは。エンジニアの志村です。
前回・前々回とRailsをDocker運用する記事を書いておりますが、今回はCI編です。
過去の記事は下記になります。
導入編でも書いた通り、CIはJenkinsを使用することにしました。 Dockerでの運用前はCircleCIを使用しておりました。
現在の構成
構成
MasterとSlaveの2段構成にしています。
Masterにジョブを置くとジョブの管理が面倒になるのと、ジョブの組み合わせ次第では不具合が起きる可能性があるので、Masterにはジョブを持たせず、テストやlint等はslaveで行うように設定しています。
Master, Slave共にDocker + ECSで構築しています。
ECSでSlave用のクラスタを用意し、
Amazon EC2 Container Service Plugin - Jenkins - Jenkins Wiki
を使用してSlaveを管理しています。
- Pipelineを使用。Pipelineは全てJenkinsfileにて記述し、Gitリポジトリで管理
- Docker+ECSでMaster & Slaveを管理
- docker-composeでテスト・lintを走らせる
上記のように、Jenkinsコンテナ内でさらにRailsのDockerコンテナを起動している状態となります。所謂Docker in Dockerです。
調べたところ、Docker in Dockerには賛否両論あるようです。
ホストマシンのsocketをマウントし各コンテナで使用するように設定してあるので、今回の例だと、RailsのコンテナからJenkinsのコンテナが見えてしまうという問題があります。
しかし、
- Dockerfileに記述すれば誰でもJenkinsの設定を変えることが出来る
- ECSを使用して簡単にSlaveを管理出来る
という利点から今回はこのDocker in Dockerの方式を採用しました。
フロー
- Githubのpush or Jenkinsからの操作
- GithubのStatusをPendingに変更
- docker-compose build
- rubocop
- scss-lint
- slim-lint
- RSpec
- (Deployモードなら)docker build
- (Deployモードなら)ECRへのプッシュ
- (Deployモードなら)harmonik*1を使用したデプロイ
というフローになっております。
フローは4種類となり、それぞれJenkinsfileで管理するようにしています。
環境 | モード |
---|---|
production | deploy |
production | test |
staging | deploy |
staging | test |
モードはtest・deployの2種類です。
testモードはRSpecまで(master, developブランチならば当該サーバに自動デプロイ)、deployモードはdocker build→デプロイまでを行うように設定しています。
こうすることによって、テスト無しでドッグフーディング環境にデプロイすることが出来、ビジネスサイドのチェックも素早く行うことが出来ます。
Jenkinsfile
Jenkinsfileはフェーズごとにファイルを分けました。
Jenkinsfile Jenkinsfile.test Jenkinsfile.build Jenkinsfile.deploy
と言った具合です。
JenkinsfileはGroovyで記述します。
Jenkinsfile
環境やモードによってどの処理をするか、クレデンシャルの読み込み、またGithubのStatusを替えるといった処理を記述しています。
- Jenkinsのパラメータから環境・モードを判別
- Slackに開始を通知
- mode == ‘test’の場合、GithubのStatusをPendingに変更
- それぞれに必要なCredentialをS3からダウンロード
- deployモードならbuild, deployファイルの処理を行う
- testモードならtestファイルの処理を行う。master, developブランチならさらにbuild, deployファイルの処理を行う
- 終了通知をslackに送る
Jenkinsfile.test
testモードで使用するファイルです。主にテストやlintを回します。
- テストで使用するENVをS3からダウンロード
- docker-compose build
- bundle install
- Rubocop
- scss-lint
- slim-lint
- rake db:create && rake db:schema:load
- RSpec
Jenkinsfile.build
deployモード、またmaster・developブランチのtest終了後に使用します。 コンテナをビルドし、ECRにプッシュするところを担当しています。
- 各コンテナに使用するクレデンシャルをS3からダウンロード
- 各コンテナをビルド(タグはブランチ名+コミットハッシュ)
- 各コンテナをECRの各レポジトリにプッシュ
とこちらも至ってシンプルです。
Jenkinsfile.deploy
こちらは前回の記事で記述したデプロイツールのharmonik*2を使用します。
- バッチコンテナのデプロイ
- メンテナンスコンテナのデプロイ
- Rails関連コンテナのデプロイ
とこちらも比較的シンプルです。 Jenkinsfileを分割することにより、共通するコードをまとめることが出来、非常に見通しが良くなりました。
と弊社では上記のようにJenkinsを運用しています。 最初の設定は大変ですが、Dockerを利用することにより管理コストの削減や興味がある人間が気軽にJenkinsを設定出来るようになりました。