Hatena Engineer Seminar #9 @ Tokyoに参加しました!

f:id:cluex-developers:20171031080636p:plain

こんにちは。iOSエンジニアの高島(Takasy (@takattata) | Twitter)です。

今回参加した勉強会ではブログ枠ではなかったものの、すごく面白かったので記事を書かせて頂こうと思います。 hatena.connpass.com

会場はサポーターズさん提供でした。
存じ上げなかったのですが、こちら、イベントスペースを無料貸出されているとのことで、覚えておこうと思いました。

間違えていたらそっと教えてくださると嬉しいです!

GUIアプリケーションの構造と設計

Hiroki Kato (@cockscomb) | Twitterさん

speakerdeck.com

togetter.com

peaks.cc こちらの本の6, 7章を書かれている方でした。宣伝されていたので紹介しておきますね。
ちなみに次の発表者のいけしょーさんは4章を書かれています。
ベータ版が既に配信されていて、入手はしたものの未だ読んでいないので、早く読もうと思います。

それはさておき、発表の内容としては、GUIアプリケーションを作る際に必要になる3つのパターンについての話でした。
GUIアプリケーションの特性として、

  • モデルの状態をもとにUIを常に更新し続ける
  • 画面間でのデータのやりとりが必要
  • メインスレッドを止めてはいけない

があり、それを解決する為に

  • Observerパターン
  • オブジェクトの生成・管理
  • 非同期処理の抽象化

の3つのパターンをうまく使おうという内容です。
このパターンを1つずつ、Android, iOS, Webだと何に当たるかを並べて紹介され、実際にどの様なものなのかをKotlinで説明して下さる、という流れです。
どのアーキテクチャを使ってもどれにもこれらが入ってくると思うし、うまく使えば整理することができるのではないか、と締めくくられていました。

この3つのパターン、ここをまず抑えたら初級レベルから抜け出せそうだな、と感じました。
学ぶべき要点が分かって非常にありがたかったです。
また、Android開発を未だしたことがないので、そういうのがあるのかーという程度にしか分からならかった点もあったので、しっかりスライドで学ばせて頂こうと思います。

そういえば、話の中でCoreDataがすごく好きと仰っていたので、その理由を詳しく懇親会でお聞きできれば良かったなぁ、と今書いていて思いました。

Swift4を振り返りSwift5の夢を見る

いけしょー (@ikesyo) | Twitterさん

speakerdeck.com

togetter.com

すごく面白かったです!
が!理解とメモが追いつかなかった...!是非スライドをご覧ください。
どの発表もそうでしたが、この発表のスライドでちゃんと復習したいと思います。

流れとしては

  • 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アプリの開発

たくじ (@takuji31) | Twitterさん

speakerdeck.com

togetter.com

可愛いうさぎの写真(飼っていらっしゃるそう)と、
「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さん

speakerdeck.com

togetter.com

東京にディレクター、デザイナー、プランナーがいて、京都にエンジニアがいるというチーム構成になっている為、互いの認識のずれを防ぐ工夫をしていらっしゃいました。
リリースに至るまでの流れと工夫している取り組みの紹介という内容でした。

ビジョンの共有をしっかりとしているとのことで、エンジニアだとしても技術だけでなく、プロジェクトにも主体的にならないといけない、という主旨の話をされていました。
本当にその通りだと思います。
そもそもビジョンと合致しているならば、エンジニアだからこそ湧く発想などをどんどんコミットしていきたいと思いそうですが、意識していないとその感覚も薄れていくので、こういう共有は定期的にしっかりとするべきだと改めて感じました。

特に面白かったのは、内側タイムと、見積もり時に日数をピザで例えるところですね。
インターン生用のタスクを準備する時の話も興味深かったですね。
長くなりそうなので割愛しますが、スライドに分かりやすくまとめられていましたので、是非ご覧ください。

おわりに

総じて内容が濃く、非常に面白かったです!
懇親会も非常に盛り上がっていたように感じます。
iOSだけでなく、Androidの発表もある勉強会に参加する度にAndroidを少しでも分かっていたら、もっと楽しめるんだろうなぁ...!と若干悔しくもあるので、そろそろAndroidも触ってみようかな、と思いました。
勉強会、色々な刺激を受けて良いですね。
今後も積極的にHatena Engineer Seminarの参加狙っていこうと思いました。
はてなさん、ありがとうございました!

We're hiring!!

弊社ではWeb / ネイティブアプリエンジニアを募集しております。
ご興味がありましたらお気軽にご連絡下さいませ!
エンジニアの方、ぜひ情報交換しましょう!

www.wantedly.com

www.wantedly.com

potatotips#44に参加しました!(iOS編)

今月勉強会に色々参加させて頂いている高島(Takasy (@takattata) | Twitter)です。
前回Money Forwardさんで開催された

potatotips.connpass.com

に参加した際にすごく良かったので、今回エウレカさんで開催された

potatotips.connpass.com

もブログ枠で参加してきました!

f:id:cluex-developers:20171026080141p:plain

会場はお洒落で、Wi-Fiだけでなく電源も提供されていて、勉強会では珍しいなと思いました。

今回は弊社のエンジニアの井戸田くんがAndroidブログ枠で参加していたので、iOSブログ枠だった自分はiOSの発表だけに記載を留めさせて頂きますね。 Androidは乞うご期待でお願いします。

ありがたいことにtogetterにまとめてくださっている方がいらっしゃったので貼っておきます。 togetter.com

間違っている箇所等ありましたら、気軽にコメントにお願いします!

アプリ内でWebAPIを抽象化するためのフレームワークAbstractionKitの紹介

speakerdeck.com

ひらり (@hiragram) | Twitter さん

VASILYさんのテックブログにも書いていらっしゃるとのことで、併せて読むとなお良しです。 tech.vasily.jp

紹介されているAbstractionKitはこちら github.com

APIリクエスト周りで困ったことがまだ特になく、発表を聞いている時は話の内容がよく理解できませんでした。

  • CleanArchitectureでレイヤー分かれているからAPIリクエストが点在することはない
  • 基本的にデータは画面間を跨がない

あたりのおかげでしょうか。
同じように分からなかった方は、ブログで流れを追ってからスライドを見ると良いと思います。

  1. EndpointDefinitionというプロトコルを使用して、エンドポイントを定義する。(=エンドポイントのモデル化)
  2. 通信ライブラリと1の橋渡しとなるGenericRequest構造体を作る
  3. 抽象レイヤーとして機能する部分を作る(2を使って取得した結果からどうするかを記述する)
  4. 他画面へのデータ反映の為にブロードキャスト用のストリームを準備して、受け取りたい時に購読する

これは後ほどソースを読み込ませて頂こうと思いました。勉強になります。
スライドのまとめページの「AbstractionKit + APIKit + RxSwiftの説明時間あったらします(多分ない)」は是非ともして頂きたいなぁ。

Showcase Library について

(まだ資料があげられていなかったので、資料が上がり次第更新させて頂きます。) github.com

「UIViewが5つ横に並んでいて、これを無限スクロールでカルーセル表示したい」
を簡単に作る為に作ったライブラリの紹介でした。
横だけでなく、縦や円状の動きなどにも対応していました。
X/Y軸に-1~1で範囲を持ち、動く方向を指定できるとのことです。
図や動画など、視覚的に理解を促進するスライドだったので、資料上がるのが楽しみです。
これも後ほどソースを読み込ませて頂きたいと思いました。

Introduction Differ

speakerdeck.com

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でも、内部実装や挙動が変わることがある」というのを忘れないようにしようと思いました。

入力を型で表現する

speakerdeck.com

Motoki Narita (@motokiee) | Twitter さん

KAURUの製品情報の取得がサクサクですごく触ってみたくなるデモから始まりました。
この出品画面がそこそこ複雑で、今後入力経路が増えることも考えて、複数の入力を1つの型で表現するようにされた話でした。
データの有無で判別するとかでなく、入力の型自体で分けるのは分かりやすくて良いなと思いました。
まとめにも書かれていらっしゃいましたが、「データ自体の処理と画面の処理を分離しやすい」のは良いですよね。
ソースをたくさん貼ってくださっていて、具体的な実装方法が分かるので、スライド必見です。

carthage verify

speakerdeck.com

r_plus (@r_plus) | Twitter さん

CocoaPodsしか使ったことがないのでピンと来なかったのですが、Carthageではライブラリに追随していない時にはビルドエラーになるという仕組みが標準ではないそうです。
その問題にどうアプローチしていったかの話でした。
結論としてはCarthage/workflowsにあるcarthage-verifyスクリプトを使うとCocoaPodsと同じことが実現できるそうです。
Carthageを使う時の為に覚えておこうと思います。

余談ですが、コントリビュータのいけしょー (@ikesyo) | Twitterさんが素敵なツイートをしていらっしゃっいました。

AR FACE DETECTION

speakerdeck.com

Kaoru (@TachibanaKaoru) | Twitter さん

ARKitの紹介や現在iPhoneXのみ利用可能な顔認証の話でした。
ARの敷居は低く、どちらも実装が非常に簡単とのことです。
Xcode9のテンプレートプロジェクトから立ち上げて動くので、たしかにすぐ出来そうだな、と思いつつ、SceneKitやSpriteKitを触ったことがないので、そこのインプットも考えると優先順位が後回しになっていく現状がありますね...。ですが、やはりARKitは触っていきたいな、と改めて思わされる内容でした。
どういう仕組みで動いているのかが分かる発表だったので、是非スライドをご覧ください。
紹介されていた不思議の国のアリスARは触ってみようと思います。(かなりウサギが怖いですが。)

iOSのCorner Rouding Strategy

speakerdeck.com

Shunki Tan🤔 (@shunkitan) | Twitter さん

紹介されていたドキュメントはこちら Texture | Corner Rounding

スクラムマスター。懇親会でチーム開発についての話聞いてみようかと思って完全に忘れました...。
CALayerのcornerRaduisは毎フレーム、レンダリングが発生しているとのことで、極力使うのは避けようという話でした。
Cornerの種類を挙げ、ハイパフォーマンス順に実現の仕方を紹介してくださいました。
紹介されていたドキュメントのフローがすごく分かりやすかったです。
アプリのパフォーマンスを上げないと辛くなった時に手をつけようと思います。

終わりに

以上、iOSの発表だけですが、1つ1つの内容が濃かったなと感じます。
ブログを書くと内容を振り返らざるを得ないから良いですね!
参加した勉強会は今後なるべく記事にしていこうと思いますので、今後ともよろしくお願いします。

ちなみに、KAURUチームが書いた薄い本の仁義なきじゃんけん大会では敢え無く負けました。

We're hiring!!

弊社ではWeb / ネイティブアプリエンジニアを募集しております。
ご興味がありましたらお気軽にご連絡下さいませ!
エンジニアの方、ぜひ情報交換しましょう!

www.wantedly.com

www.wantedly.com

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は使えません。
ソースコード上で画像のアスペクト比を変更させることができます。

  1. 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])
  1. storyboardの方でUIImageViewの上下左右の制約をかける。ここではアスペクト比やwidth, heightの制約はかけません。
  2. 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
}

f:id:cluex-developers:20171011111208p:plain
図1

図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)
}

f:id:cluex-developers:20171011111216p:plain
図2

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 / ネイティブアプリエンジニアを募集しております。
ご興味がありましたらお気軽にご連絡下さいませ!
エンジニアの方、ぜひ情報交換しましょう!

www.wantedly.com

www.wantedly.com

iOSDCにLTで参加してきました!

こんにちは、iOS開発中ながら先日メイン機をAndroidにした高島です。
今更ではありますが、今回iOSDCに初参加&LTをさせて頂いたことについて振り返ろうと思います。
ちなみに話させて頂いた内容はこちらです。

www.slideshare.net

iOSDCとは

iosdc.jp

今年で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を取り上げていくことは諦めました。
そこでコードリーディングの利点や読むコツにフォーカスし、改めて自分の方法を振り返りつつ、コードリーディングについての資料を漁り、まとめました。
伝えたいことについてはスライドにまとめられたと思いますので、ご興味ある方はご覧頂けると幸いです。

また、採択されてから期間もある中で、自身のモチベーションを保つ為に、

  • iOS開発者のTwitterでの盛り上がり具合や力の入れ具合を見る
  • LT準備期間中に勉強会に参加して、他のスピーカーの方々と話す

というのは凄く効果的でした。

余談ですが、前夜祭後に開催されたスピーカーディナーで、「発表いつですか?」「明日です。」「いいなー!日曜日は高みの見物できるじゃないですかー!」とか「まだスライドできてないんですよ...!!帰ったら頑張りますハハッ」などの話で一体感が生まれる感じが面白かったです。
f:id:cluex-developers:20171014042233j:plain

いざ、LT!

話す直前までものすごく緊張していました...。 (最初スクリーンの前に晒されてる時の緊張感と言ったらなかったですね...!!↓その時の写真) f:id:cluex-developers:20171014042544j:plain 前回のLTの様子を知らなかったので、LTが始まると共になにやらかっこいい音楽が流れ始め、乾杯とともにノリノリな感じで始まったことにとても驚きましたし、LTの場数を踏んでいそうな方々の発表が続き、内心非常に焦りました。
実際前に立って話してみると、皆さん飲み物片手にすごくよく聴いてくださっていて、話しやすかったです。

iOSDCをLTのスピーカーとして参加してどうだったか

今回iOSDCへの参加も含め、LTで参加させて頂く過程を通して得られたと思う点は

  • 外部に発信することへの敷居が下がった
  • 人とのつながり

です。

外部に発信することへの敷居が下がった

もともと学んだことを人に伝える習慣がなかったのですが、弊社に入社してから、社内LT大会などを通して発信する機会を得るようになりました。
とはいえ、社外に発信するとなると、踏みとどまってしまうことが多くありました。
今回LTを発表して、「このくらいの内容でも大丈夫なんだ」「がっつりとした技術の発表までは出来なくても、気づきやきっかけを提供することは出来るのだな」と感じ、外部に発信することへの敷居がかなり下がりました。
また、自分が気づいていなかったことに対してフィードバックをしてくださる方もいらっしゃるので、得られるものは大きいと感じました。
マサカリが飛んでくることを恐れてしまっては、その得られる機会を逃してしまうので、今後は得た知見や気づきを積極的に発信していこうと思います。

人とのつながり

iOSDCでは「隣の人と話してみてください」とスタッフが呼びかけていることもあり、人に声を掛けやすい雰囲気があります。
携帯でTwitterを見ていたら、「そのクライアント作ったの自分なんですよ!使ってもらえて嬉しいです!」と声を掛けてもらえたり、スピーカーはネームカードにTwitterアカウントのアイコンが印刷されているので、そのアイコンを見て「あ、フォローしてます」と声を掛けてもらえたりもしました。
(もっと積極的に声を掛けてみれば良かった...!!と今になって後悔しているので、来年は自ら声掛けにいこうと思います。)
懇親会の時にもスピーカーとして参加していると、話した内容について話し掛けて頂いたり、その話題をきっかけに話し掛けたり出来るので、非常に話しやすかったです。
今回を機に少し人とのつながりが増えたように感じました。
(ちなみにiOSDCで出た料理、昼食も含め美味しかったです。) f:id:cluex-developers:20171014043111j:plain

参加後に弊社で取り組んでいること

弊社のアプリ開発チームのエンジニアは全員iOSDCに参加していたので、参加後、皆でiOSDCの内容をフォローすべく、聴いたセッションの共有をしました。 現在は、ラーニングランチという、昼食時にチーム開発について話し合ったり、知識を共有する時間を使って、アーキテクチャのセッション中に上げてくださっていたこちらのサンプル(https://github.com/marty-suzuki/iOSDesignPatternSamples)のコードリーディングを行なっています。 今後は俺コンやリジェクトコンなどのフォローもしていっても良さそうですね!

終わりに

iOSDC本当に良かったです!スタッフの方々ありがとうございました! 今回得られたことを今後に生かして、より良いアプリを作るべく精進したいと思います。

写真はiOSDC Japan提供のものを使用させて頂きました。ありがとうございます。

We're hiring!!

弊社ではWeb / ネイティブアプリエンジニアを募集しております。
ご興味がありましたらお気軽にご連絡下さいませ!
エンジニアの方、ぜひ情報交換しましょう!

www.wantedly.com

www.wantedly.com

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を共有するときにも重要です。 今回は以下のようなデータ構造にしました。 f:id:cluex-developers:20170908153348p:plain

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を利用してさらにリッチにしていく予定です!

f:id:cluex-developers:20170908152754p:plain
続編のブログでは、Gemの導入などに関する豆知識などをお伝えできればと思います。

それではまた次回!

We’re hiring!!

弊社ではWeb / ネイティブアプリエンジニアを募集しております。
ご興味がありましたらお気軽にご連絡下さいませ!
エンジニアの方、ぜひ情報交換しましょう!

www.wantedly.com

www.wantedly.com

iOS Tips集

こんにちは。エンジニアの志村です。
Cluexでは7月に「ままのて」という、妊娠中〜育児中まで医師監修のQ&Aやメッセージをお届けするアプリをローンチしました。

appsto.re

上記に伴い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)

ポイント

  1. ifaddr変数のポインタを渡して、ifaddrに取得した内容を格納する。
  2. ビットフラグで状態確認している。flagsがUP, RUNNINGの時&&LOOPBACKではない時を指す。
  3. 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 / ネイティブアプリエンジニアを募集しております。
ご興味がありましたらお気軽にご連絡下さいませ!
エンジニアの方、ぜひ情報交換しましょう!

www.wantedly.com

www.wantedly.com

Docker with ECS × Railsを実現させるために考えたこと(CI編)

こんにちは。エンジニアの志村です。
前回・前々回とRailsをDocker運用する記事を書いておりますが、今回はCI編です。
過去の記事は下記になります。

cluex-developers.hateblo.jp

導入編でも書いた通り、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の方式を採用しました。

フロー

  1. Githubのpush or Jenkinsからの操作
  2. GithubのStatusをPendingに変更
  3. docker-compose build
  4. rubocop
  5. scss-lint
  6. slim-lint
  7. RSpec
  8. (Deployモードなら)docker build
  9. (Deployモードなら)ECRへのプッシュ
  10. (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を替えるといった処理を記述しています。

  1. Jenkinsのパラメータから環境・モードを判別
  2. Slackに開始を通知
  3. mode == ‘test’の場合、GithubのStatusをPendingに変更
  4. それぞれに必要なCredentialをS3からダウンロード
  5. deployモードならbuild, deployファイルの処理を行う
  6. testモードならtestファイルの処理を行う。master, developブランチならさらにbuild, deployファイルの処理を行う
  7. 終了通知をslackに送る

Jenkinsfile.test

testモードで使用するファイルです。主にテストやlintを回します。

  1. テストで使用するENVをS3からダウンロード
  2. docker-compose build
  3. bundle install
  4. Rubocop
  5. scss-lint
  6. slim-lint
  7. rake db:create && rake db:schema:load
  8. RSpec

Jenkinsfile.build

deployモード、またmaster・developブランチのtest終了後に使用します。 コンテナをビルドし、ECRにプッシュするところを担当しています。

  1. 各コンテナに使用するクレデンシャルをS3からダウンロード
  2. 各コンテナをビルド(タグはブランチ名+コミットハッシュ)
  3. 各コンテナをECRの各レポジトリにプッシュ

とこちらも至ってシンプルです。

Jenkinsfile.deploy

こちらは前回の記事で記述したデプロイツールのharmonik*2を使用します。

  1. バッチコンテナのデプロイ
  2. メンテナンスコンテナのデプロイ
  3. Rails関連コンテナのデプロイ

とこちらも比較的シンプルです。 Jenkinsfileを分割することにより、共通するコードをまとめることが出来、非常に見通しが良くなりました。

と弊社では上記のようにJenkinsを運用しています。 最初の設定は大変ですが、Dockerを利用することにより管理コストの削減や興味がある人間が気軽にJenkinsを設定出来るようになりました。

*1:前回の記事で紹介したデプロイツールです

*2:前回の記事で紹介したデプロイツールです