文字っぽいの。

文字を書いています。写真も混ざります。

嫁の転職を支える技術

f:id:FromAtom:20180214204559p:plain
嫁を面接した企業が「あれ、これって……。」となる画像

はじめに

最近嫁が転職活動をしていたのですが、デザイナーは転職活動をする際に「ポートフォリオ」なるものが必要になるそうです。Webデザイナーである嫁は、このポートフォリオをWebサイトとして製作したいとのこと。

エンジニアリングを生業とする旦那として、ここは一肌脱がねばと思い、ポートフォリオサイトの構築をしました。

コントラクター(僕)のスペック

  • iOSエンジニア
  • Webアプリは過去にやっていた

クライアント(嫁)のスペック

  • デザイナー(仕事はWeb系)
  • Ruby, JavaScriptはほぼ書けない
  • HTML, CSSは書ける
  • Git, GitHubは使える

クライアント(嫁)の要望

  • パブリックに公開するのではなく、履歴書に添えたい
    • URLを知っている人しかアクセスしない
  • パスワード(BASIC認証)が欲しい
  • デザインはいちから自分で組みたい
  • SlimとSassを使いたい
  • イラストやロゴなど趣味や個人で依頼された仕事の制作物ものせたい

とのことです。最初はTumblrで簡単に作ればええじゃろと思っていたのですが、思いの外しっかりと仕組みを作る必要がありそうです。

成果物

実際に利用した構成を基に、テンプレートとして使えるように整形したのがこちらです。

github.com

どうぞご利用ください。

構成

iOSエンジニアとしてはServer-Side-Swiftを選択したいところですが、Middlemanを使いたかったのでRubyベースです。下記Gemfileを読めば、だいたいの構成はわかると思います。

source 'https://rubygems.org'
ruby "~> 2.4.0"

gem 'slim', '>= 3.0'
gem 'rake'
gem 'puma'
gem 'rack-contrib'

# For faster file watcher updates on Windows:
gem 'wdm', '~> 0.1.0', platforms: [:mswin, :mingw]

# windows does not come with time zone data
gem 'tzinfo-data', platforms: [:mswin, :mingw]

# Middleman Gems
gem 'middleman', '>= 4.0.0'
gem 'middleman-livereload'

静的ページ周りをMiddlemanで管理し、pumaを使ってBASIC認証を実現しています。

ホスティングとデプロイ

ホスティングはHerokuのHobbyプランを利用しました。Herokuを選んだ理由は、

  • 環境構築が容易
  • GitHubと連携した自動デプロイの仕組みが簡単に作れる
  • 他サービスの値段・手間を比較した時の約700円/月という安さ
  • HTTPSで提供できる

という感じです。

クライアントはプログラミングバリバリできるタイプではないので、絶対にデプロイは自動化しようと考えていました。HerokuはGitHubと連携し、 master が変更されると自動でデプロイしてくれる仕組みがあります。この機能のおかげでクライアントには「Pull Request作ってmasterにマージしたあと、数分待てばHerokuに反映されるから。」と伝えるだけでよく、非常に便利でした。

構成を考える際に気をつけたこと

クライアントがデザインに集中できるように気をつけました。RubyJavaScriptなどのコーディングをすることなく、ポートフォリオサイトが完成できるように留意して環境を構築しました。その為には、

  • ホットリロードの仕組みがある
  • 新しいページを作る際にプログラミングをしたり、コマンドを打つ必要が無い
  • SassとSlimもホットリロードされて使える
  • Herokuにサラッとデプロイできる

という要件を満たす必要がありました。やはりMiddlemanはよくできているため、「ここに置いて、こういう形式で書けば、こう使える」と教えるだけで済み、とても助かりました。

加えて、オーバーエンジニアリングをしないように気をつけました。クライアントが欲しているのは、転職活動時に履歴書として利用するポートフォリオサイトです。エンジニアとしては、CMSミニブログ的なシステムを開発したいと考えてしまいますが、今回の案件ではそこまでの機能は必要ありません。

さらに、なるべく早く開発を終えてクライアントにシステムを納品しなければ、転職活動期間中にポートフォリオサイトが間に合わないという、本末転倒な結果になってしまいます。そういった背景を加味した結果、今回の構成に落ち着きました。

最後に

このポートフォリオサイトの効果があったのか無かったのか分かりませんが、嫁は無事に転職することができました。めでたしめでたし。

"carthage-verify" を使って `carthage bootstrap` 忘れを防ぐ

やりたいこと

CocoaPodsがビルド時に「pod install しないとだめだよ」とエラー吐いてくれるのが便利なので、Carthageでも同じことをやりたい。

実現方法

Carthage公式のworkflowsというリポジトリcarthage-verify というスクリプトがあるので、そちらを使います。

github.com

手順

まずは carthage-verify をcloneするなりDownloadするなりして、手元に持ってきます。ディレクトリ構成としては、下記のように scripts ディレクトリを作って、その中に入れてあげます。

.
├── sample.xcodeproj
├── sample.xcworkspace
└── scripts
    └── carthage-verify <- [コレ!]

次にBuild PhasesにScript Phaseを追加していきます。こんな感じです。

f:id:FromAtom:20180104190320p:plain

下記はコピペ用のスクリプトです。

./scripts/carthage-verify
if [ $? != 0 ] ; then
    # print error to STDERR
    echo "error: The sandbox is not in sync with the Cartfile.resolved. Run 'carthage bootstrap --
    platform iOS --use-ssh --no-use-binaries --cache-builds' or update your Carthage installation." >&2
    exit 1
fi

結果

わざと手元の ./Carthage 内を古い状態にして、ビルドしてみると、

f:id:FromAtom:20180104190958p:plain

このようにちゃんとエラーを出してくれます。良かったですね。

参考文献

こちらのスライドを参考にしました。

speakerdeck.com

【esa】社内ドキュメントツールのホッテントリを分かるようにしたら捗った話

この記事はピクシブ株式会社 Advent Calendar 2017の20日目です。

昨日はおしゃれな動画を作るマンであるまつらいの

inside.pixiv.blog

でした。かっこよくてずるいですね。


こんにちは、Atomです。普段はiOS EngineerとしてSwiftを書いたり、9%チューハイを片手に街を散歩したりしています。

さて、弊社ではesaを利用したポエム駆動開発が活発に行われています。esaは、

esaは「情報を育てる」という視点で作られた 自律的なチームのためのドキュメント共有サービスです。

というサービスで、社内での情報共有を気軽に行うことができます。詳しい利用事例やポエム駆動開発についてはこちらの記事をご参照ください。

gihyo.jp

esaはとても優れたサービスで、だれでも気軽にポエム、思考、ノウハウなどを共有することができます。弊社では今年1年で1617件の記事が投稿されており、とても活発に利用されていることが分かります。

一方で、記事が多くあるとすべてを読み尽くすのは厳しくなってきます。Twitterはてなブックマークに加えて、esaの新着記事もすべて読んでいては仕事する時間がなくなってしまいます。

そこで、今熱い記事がわかるyakitoriというWebアプリを作りました。

yakitoriの様子
yakitoriの様子

加えて、社内Slackにも6時間毎に通知するようにしました。

運良く僕の記事が通知されていた

実装

yakitoriはHeroku上に下記3つのアプリケーションを動かすことで構築されています。ちなみに全てRuby製で、無料の範囲でやりくりしています。

  • App1: 定期的にesaの全記事取得しスコアを計算してRedisに保存する君
  • App2: SinatraでRedisから取得したデータを表示するだけ君
  • App3: 定期的にRedisから取得したデータをSlackに投げる君

という構成になっています。マイクロサービシーズっていうやつですね(笑顔)。App2, App3は非常にシンプルな構成なので、キモであるApp1について簡単に解説します。

詳しいコードは https://github.com/FromAtom/Yakitori にあるので、コアの部分だけ解説をします。

time_diff = Time.now - Time.parse(created_at)
duration_hour = time_diff / (60 * 60)
point = COMMENT_GRAVITY * comments_count + STAR_GRAVITY * stars_count + WATCHERS_GRAVITY * watchers_count
score = (point - 1.0) / ((duration_hour + 2.0) ** GRAVITY) # この数値をもとにランキングを作る

このコードで各記事のスコアを計算しています。数式で表すとこのようになります。なお、この計算式はHacker Newsのものを参考にしています。

 
    score =  \frac{(p - 1)} {{(t + 2)}^{G}}


  • t: 投稿からの経過時間[h]
  • G: 重み付け係数(1.8ぐらいがちょうどよい)

となっていて、この時のp

 
    p = star\_count \times 1.0 + comment\_count \times 1.2 + watch\_count \times 0.2

と設定しています。

esaでは記事に星をつけるスター、記事の更新を通知で受け取るウォッチ、そしてコメントがあり、これらを利用して記事を書いた人にフィードバックを送ることができます。なので、これら3つの数値を重み付け(1.0, 1.2, 0.2の定数がそれ)をしながら記事のスコアとして利用しました。ちなみに、コメントをするとウォッチも自動的にされるので、ウォッチは弱めにしてあります。

そして、投稿からの経過時間(t)が増えていくことで、古い記事のスコアは低くなるようになっています。

やってみて

今までであれば見ることがなかった記事も見るようになり、新着記事を全部みる必要もなくなったのでとても便利になりました。また、普段Swiftばかり書いているので、こういった小さなサービスやツールをRubyでササッと書くのは楽しかったです。(本当はSwiftで書こうと思ったけどRedis周りが面倒でやめた……)

esaAPIも用意されているので、記事を簡単に取得することができてとても助かります。

最後に

明日はverno3632がAndroidの話をしてくれるようです。楽しみですね!

そしてピクシブ株式会社では、仕事の合間に小さなツールを作るのが好きな人や、焼き鳥が好きなスマートフォンアプリエンジニアを募集しています。焼き鳥が好きではないスマートフォンエンジニアも応募しています。

recruit.jobcan.jp recruit.jobcan.jp www.wantedly.com

iOS9以降では "NotificationCenter.default.removeObserver(_:name:object:)" をdeinitに書かなくても良いですよ。

表題通り。間違ってたら教えて欲しい。

iOS9以降をDeployment Targetにしている場合のみの話ですので、iOS8系をサポートしている場合は今まで通り明示的にremoveしましょう。

今までは NotificationCenter.default.addObserver(_:selector:name:object:) して追加されたNotificationを、明示的にremoveする必要がありました。用途によりますが、 viewWillDisappeardeinit の中で NotificationCenter.default.removeObserver(self) とか書いていることと存じます。

この処理がiOS9以降を対象としたビルドでは不要になりました。

ほんまかいなという話ですが、addObserver(_:selector:name:object:) - NotificationCenter | Apple Developer Documentation を見ると、

If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its dealloc method. Otherwise, you should call removeObserver(_:name:object:) before observer or any object passed to this method is deallocated.

と書いてあります。便利ですね。

9%の世界

f:id:FromAtom:20171210153847j:plain

ストロングゼロがTLでにわかに流行っている。

ちょうど休日だったのと、洗濯機が焼肉屋でこびりついた匂いを消してくれるまで暇だったので、缶チューハイを片手に商店街を散歩してみた。 昼下がりにこんな酒を片手にふらふらとしていると、定職についていない気持ちになってなんだか楽しい。

カップルの男性が「今日何も食べてない」と苛立っているのを、おそらく彼女であろう女性が夕飯の提案をしながらたしなめていたり、 路上の看板に歩きスマホの男性がぶつかりかけているのを見ていた。

商店街には何かしらの目的がないと訪れない。 ましてや9%の酒を片手に散策するために来たことは一度もないし、そんな目的で来る人はほとんど居ないだろう。 ただ、いったん酒を飲んでしまえば、あとはもうカッコつけることができない世界になる。 僕は昼間から高アルコールな缶チューハイを片手に徘徊するおっさんになって、その缶を飲み尽くすまではどんなお店にも入れなくなる。 スタバなどのカフェはおろか、100均ですら気後れする。

そんな状態だから、街を歩き回るしかやることがない。 強制的な散歩状態になるから、いろいろ発見もある。 アルコールの効果で体は温まるし、普段見ない角度で街を見ることができる。 なんだか高尚なことをしている気分にすらなる。

一通り歩き回って、缶も空になったので、丁寧にチューハイを買ったコンビニまで戻って捨てた。 酔いも回っていたので、そのままコンビニでコーヒーを買って近所の公園で飲みながら、 夕飯のこと、明日の仕事のこと、家で脱水が終わっているであろう洗濯機の中身のことを考え、 コーヒーを飲み終わるとまたコンビニに戻って捨て、家に帰った。

ちなみに、9%のチューハイは結構あって、ストロングゼロ以外にはキリンの氷結ストロングやビターズがある。 チューハイではないけども、角ハイボール缶の濃いめも9%だ。 ストロングゼロは結構甘いけど、ビターズはそんなに甘くないという特徴がある。 個人的にはビターズが好きで、ハイボールまで行くと味がなさすぎてさみしい。 単体でも飲めるけども、おつまみとも合うのがよい。

こういった飲み物を愛飲すると次の日(つまりそれは将来)が破滅してしまうことはわかっているので普段は飲まないけれども、 深夜のセブンイレブンで適当なパウチ(ごぼうサラダや豆のサラダがあるやつ)とホットスナックと9%チューハイのトール缶を買って帰るときの、 あの幸福なのか不幸なのかわからない感覚は結構好きではある。

Xcodeでプロジェクトごとにインデントスタイルを指定する

環境

やりたいこと

EditorConfigのようにインデントのスタイルを指定しておくことで、新しい協力者が増えた時に「あ、インデントはハードタブなんで。」などという煩わしい対話をしないようにしたい。

手順

Xcodeを開いて、プロジェクトファイルを選択します。

f:id:FromAtom:20171109195550p:plain

そしてこの"Text Settings"にある"Indent Using"を選択します。

f:id:FromAtom:20171109195840p:plain

ここで重要なのは、すでにTabsが選択されていても、もう一度Tabsを選択しないとプロジェクトのインデント設定がされない という点です。

なお、ちゃんと反映されたか確認したい場合は、git diff などで差分を確認して

f:id:FromAtom:20171109200825p:plain

usesTabs = 0; もしくは usesTabs = 1; が設定されているかを見ると良いです。

この設定が済めば、設定画面で

f:id:FromAtom:20171109201515p:plain

このようにSpacesが設定されていても、プロジェクト内のファイルではTabsでインデントがされます。また新しく生成されるファイルでも、もちろんTabsでインデントされます。

おまけ

プロジェクトファイルを選択すると全体のインデント設定ができますが、個々のファイル毎でもインデントの設定ができます。なので *.swift ファイルではTabsで、*.mm ファイルはSpacesでインデント指定をするということも可能です。

感想

EditorConfigが使えなくなって、Xcodeではプロジェクトごとのインデント設定はできないと思い込んでいましたが、実はできました。

謝辞

教えて頂いた @kishikawakatsumi さん、ありがとうございます!

iOSのTwitterKitで投稿しようとすると、401が返ってきて投稿できない問題。

環境

  • Xcode9.1
  • Swift 4.0.2
  • TwitterKit 3.2.1

問題

上記環境でTwitterに投稿をするコードを書いた。雑にコードの様子を書くと、Twitter.sharedInstance().logInをしてログインが成功した後で、

guard let session = Twitter.sharedInstance().sessionStore.session() else {
    return
}

let apiClient = TWTRAPIClient(userID: session.userID)
let request = apiClient.urlRequest(
    withMethod: "POST",
    url: "https://api.twitter.com/1.1/statuses/update.json",
    parameters: [
        "status": status
    ],
    error: nil
)
apiClient.sendTwitterRequest(request) { response, _, error in
    if let error = error {
        print(error)
    }
}

とする感じ。特に難しいことはしていないのでシミュレータでは難なく動いたが、なぜか実機だと "Request failed: unauthorized (401)" と error が返されて投稿できない。なんでやねん。

解決方法

Twitterのアプリ管理画面( Twitter Application Management ) で対象のアプリを開いて、

  • Access Levelを "Read and write" から "Read only" に変えて "Update Settings"ボタンを押す
  • Access Levelを "Read only" から "Read and write" に変えて "Update Settings"ボタンを押す

とすると直る。

どうやらアプリを登録してすぐ表示されている"Read and write"というAccess Levelは正しくないらしい。実際にこの解決方法を試す前に、自分のアカウントで連携したアプリ一覧を見てみると、

f:id:FromAtom:20171109125450p:plain

となっていて、『読取専用』と書いてある。つらい。

感想

どうやらこの挙動はTwitterアプリや連携を開発したことがある人の間では、よく知られているらしい。知らんがな。

iOS11からSocialKitが使えなくなり、TwitterKitを使うことになったアプリが多くあると思うが、この罠に引っかからないことを切に願う。つらい。