yashiganiの英傑になるまで死ねない日記

週末はマスターバイクでハイラルを走り回ります

顕在化するmainBundleリスク

iOS向けのアプリケーションやライブラリで画像やローカライズファイルなどのリソースを使うとき,bundleという仕組みを利用します. bundleはアプリケーションやライブラリに組み込まれ,実行時に各リソースファイルとの橋渡しをします. 例えば,ローカライズに使うNSLocalizedStringマクロはこのように定義されています.

#define NSLocalizedString(key, comment) \
       [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil]

[NSBundle mainBundle]は実行中のアプリケーションbundleを返します. tableが実際に使用されるローカライズファイルですが,nilだとデフォルトのLocalizable.stringsが利用されます. NSLocalizedStringsはここから定義された文字列を探し,返すというわけです. ここからはリソースにアクセスするためにbundleの仕組みが利用されていることがよくわかります.

顕在化する[NSBundle mainBundle]リスク

長らくiOSアプリではライブラリの組み込みにリンクでは無く,アプリケーションへ直接組み込むことが標準的な手法でした. しかし,WWDC2014においてiOSにもdynamic frameworkが解放されました. それに伴いCocoaPodsやCarthageなどの構成管理ツールもdynamic frameworkへの対応が進められています.

さて,先ほどのbundleですが,dynamic frameworkに対応するとどうなるのでしょうか? frameworkを使うと,bundleはアプリケーションではなくそのframeworkそのものにbundleされます. つまり,frameworkを使った際はbundleからデータをロードする際,frameworkのbundleからリソースをロードするためにこのメソッドを使うことができません.

そこで,ライブラリ側でのbundleのロードについては,[NSBundle bundleForClass:]を利用することでうまくいきます.

let bundle = NSBundle(forClass: TheLibrary.self)

ライブラリで使っているクラスからbundleを特定することでframeworkのbundleにアクセスできます. 従来の方式でもbundleもろともアプリケーションに直接埋め込んでいたのですから,当然動作します([NSBundle mainBundle]と同じものが返ってくる).

まとめ

ローカライズや独自のリソースを使っているライブラリの多くは[NSBundle mainBundle]を使っているでしょう. これは完全にcontributeチャンスですので,心当たりがあればすかさずcontributeいたしましょう. 筆者も既に2つのプロダクトにcontributeしております.

参考

Cocoa勉強会関西でSwiftの型について発表しました #cocoa_kansai

Swiftでコーディングしていると,型について色々と考えることがあります. 型の捉え方は学術的にも色々あるとおもいますが,このスライドは自分の経験から自分なりの捉え方なので,間違っていることや補足などあれば教えて下さい.

スライドの補足

例に出しているResult<T>ですが,Swiftコンパイラの仕様でこのままではコンパイルすることができません. このような型に包んで,Result<Box<T>>型にするか,@autoclosureで包むとコンパイルが可能になります.

class Box<T> {
    let value: T
    init(_ value: T) { self.value = value }
}

反省

最初に大きな声で挨拶したらなんか気持ちがアガってしまって,異様なテンションでプレゼンしてしまった. 完全に傾きすぎた…

反響を紹介します

参考資料

型破りの例としてEither型のデータ構造を紹介しました. これらのSwiftでの実装は以下のプロダクトが参考になります.

また,Either型のデータ構造については,すごいHaskellを楽しく学ぼう!でも詳しく紹介されていますので,詳しく知りたい方はそちらを参照されるのもいいとおもいます.

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

初めてエンジニア面接をすることになって準備したことと反省

photo by MDGovpics

技術面接を担当する機会があった. 今まで,面接されることはあってもする側になるのは初めて. 短い時間でよい成果を得るため,事前に色々と準備をしてから望むことにした.

最初に

面接といえば質問だ. 逆に言うと質問の集合が面接と言っても差し支えない. そこでなにを質問すべきか考えた.

採用面接の目的は一緒に働く仲間(を増やす|になる)ということだ. つまり,お互いに一緒に働きたいと感じるエンジニアなのかを判断できればパーフェクトコミュニケーションと言えるだろう.

事前準備

会話は3往復くらいさせると深く人となりがわかるらしい. なにも用意しないとそんなのは絶対無理なので,事前に質問を用意しておく.

エンジニアの場合,応募資料はだいたい以下の2つがある.

この2つからエンジニア像を作りつつ,質問したいことを用意した.

ブログやGitHub

ここから掴みたいのは2つ.

  • だいたいのエンジニア像(実力・考え方)
  • 最近どういうことに興味を持っていそうか

興味を持ったエントリがいくつあるかとか,どういうときにハマったかとかを注意深く観察し,どういうエンジニアなのかイメージを作っておく. あと,それとなく興味がありそうなことを掴んでおくと話の節々で盛り上がりそうなのでメモしておく.

職務経歴書

質問はなるだけここに書いてあることから作った. なぜなら,職務経歴書に書くくらいなんだからアピールしたいに決まってる.

そこで,実際のエピソードを詳しく引き出す準備をする. 自分なら聞いてほしいのは以下の3つ.

  • なぜそれをしたのか(why)
  • どうやってやったのか(how)
  • よかったこと/悪かったことや反省点

少なくとも3往復はできそう.助かった.

エピソードについて深く聞くことで,事前資料から作ったエンジニア像を修正していく. 仕事でのエピソードはブログなどで書いてることとも関連があるはずなので,エントリ時点からどういう風に見解が変わったのかも注目したい.


ここまでで聞きたいことリストを作ることができる. あとは,ひとつのトピックについてLTぶんくらい話すと満足できそうなので5分話すとして,自分の持ち時間を考慮しつつ優先順位をつけておいた.

実践

面接を受ける人も自分を見て職場を選んでいることを念頭にコミュニケーションをとる. ここではガチガチに予定通りというわけではなく,できるだけ盛り上がるほうに転がす. そのほうが楽しいし,本音も出そうだからだ. 臨機応変に質問の順番を変えてもいい.

面接後の評価

応募資料と面接とのギャップを元に評価するのがいいと感じた. 最終的に一緒に働きたい度でフィードバックすることにした.

反省

技術的雑談レベルで盛り上がるとまではいかなかったけど,わりと深く話を聞くとこができた. 細かい修正はあれど基本的な技術面接のスキームとして使えそうだとおもったので今後も実践していきたい.

いやいや,もっとこうやったらいいよ!ってのがあればアドバイスください.

SwiftでNS_OPTIONSスタイルのEnumを実現する

Swiftenumは便利ですが,たまには使い慣れたNS_OPTIONSスタイルのenumを作りたくなるときがあります. 素朴にやるとこのように実装できます.

/// My Options
enum Options: UInt {
  case None = 0
  case A    = (1 << 0)
  case B    = (1 << 1)
}

しかし,これでは全ての組み合わせのパターンをcaseとして用意する必要がありますし,用意したところで常にrawValueでの比較が必要です.

// My Options doesn't have it!
let options: Options = Options(rawValue: .A.rawValue | .B.rawValue)

こんなのはとてもじゃないけど使えません.

UIViewAutoresizingの実装を見てみる

UIKitにはNS_OPTIONSを使ったenumがいくつもあり,それらはSwiftでもObjective-Cのときと同じように使うことができます. 実際にはどう実装されているのでしょうか? 代表的なNS_OPTIONSを使ったenumである,UIViewAutoresizingを例に実装を見てみます.

struct UIViewAutoresizing : RawOptionSetType {
    init(_ rawValue: UInt)
    init(rawValue: UInt)

    static var None: UIViewAutoresizing { get }
    static var FlexibleLeftMargin: UIViewAutoresizing { get }
    static var FlexibleWidth: UIViewAutoresizing { get }
    static var FlexibleRightMargin: UIViewAutoresizing { get }
    static var FlexibleTopMargin: UIViewAutoresizing { get }
    static var FlexibleHeight: UIViewAutoresizing { get }
    static var FlexibleBottomMargin: UIViewAutoresizing { get }
}

素朴にenumで実装するわけではなく,structで実装されています. 通常Swiftenumcaseで各値を決めますが,全てstaticpropertyとして定義されているのがおもしろいです.

Objective-Cのときと同じように使える秘密は,RawOptionSetTypeにあってこのような継承関係を持っています.

RawOptionSetType
       |
       |----------------------------------------------
       |                      |                      |
_RawOptionSetType   BitwiseOperationsType   NilLiteralConvertible
       |
       |----------------
       |               |
RawRepresentable   Equatable

これらを実装するとこうなります.

/// My Options
struct Options : RawOptionSetType {
    typealias RawValue = UInt
    private var value: UInt = 0
    init(_ value: UInt) { self.value = value }
    init(rawValue value: UInt) { self.value = value }
    init(nilLiteral: ()) { self.value = 0 }
    static var allZeros: Options { return self(0) }
    static func fromMask(raw: UInt) -> Options { return self(raw) }
    var rawValue: UInt { return self.value }

    static var None: Options { return self(0) }
    static var A: Options { return Options(1 << 0) }
    static var B: Options { return Options(1 << 1) }
}

これでUIViewAutoresizingと同じように使えます.

let options: Options = .A | .B

大変な実装をすることでようやくNS_OPTIONS相当のstructを手に入れることができました. 深淵なるSwiftのテクニックに触れることができて,大満足ですね.

お得情報コーナー

こんなに実装してらんねえ!と思ったアナタに朗報です! Bridging Headerにこのように書いてみましょう.

typedef NS_OPTIONS(Options, NSUInteger) {
  OptionsNone,
  OptionsA,
  OptionsB,
};

これで大変な実装なしにSwiftにexportされます. お手軽ですね!

pure Swiftで実装したいというアナタにはこれがオススメです.

このジェネレータを使えばメッチャ簡単にNS_OPTIONS相当のstructの実装が作れます.

参考

RawOptionSetTypeについての解説です. かなり初期に書かれたものなので,現在は微妙に仕様が変わっていますが参考になるとおもいます.

Hatena Engineer Seminar #4で「はてなのiOSアプリとSwift」という発表をしました #hatenatech

2/7に開催されたHatena Engineer Seminar #4 @Tokyoで「はてなiOSアプリとSwift」という発表をしてきました.

現在,はてなiOSアプリ開発ではSwiftを標準的に採用しており,その経験を紹介した形です. 発表スライドはこちらから御覧ください.

Swift with apps by Hatena // Speaker Deck

補足

例に使っているコードについて

例としてあげたSwiftのコード例は冗長なものですが,型推論できずコンパイルできなかったので意図に助長な実装にしています. 推論がキチンと働けばsortedに与えるclosureの引数は省略することができます.

func latestHotentry(bookmarks bs: [Bookmark]) -> Bookmark? {
// 仕様どおりに型推論されればこれでよい
    return bs.filter { $0.isHotentry }
             .sorted { $0.bookmarked.compare($1.bookmarked) == .OrderedDescending }.first
}

スライドでも言及していますが,主にclosureまわりは推論が弱い印象があります.

Objective-Cの例について,改行を盛っているという意見があるかもしれません. その意見は全くもって正しくて,スライドの都合上可読性のため意味的には無駄な改行を入れています. しかし,実際に実装するにあたっても可読性のために同じようなところに改行を入れるとおもいます.

懇親会で質問されたこと

覚えている範囲ですが,懇親会で質問されたことを紹介します.

はてなにはモバイルアプリのエンジニアは何人くらいいるのか

現状アプリ開発を担当しているのは4人です. ちなみに現在開発に関わってはいませんが,スキルを持っているエンジニアはあと3人いるので,スマート会は7人で構成された組織です.

ReactiveCocoaは使っていないのか

使っていません. ReactiveCocoaはかなり大きく複雑なライブラリですし,実装を大きく変えてしまいますので,依存があまりに大きいです. iOSアプリ開発は毎年変化がかなり激しいので,できるだけ依存は減らしたいのであえて採用していません. 一方で,スライドで紹介したMantleはやっていることが小さい(JSONからモデルへのマッピング)ので取り替えが難しくありません. そういうものは積極的に採用しています.

感想

長時間なだけでなく,発表の内容も幅広くバラエティに富んでいたので参加者のみなさんはさぞ疲れただろうとおもいます. にも関わらず,最後まで真剣に聞いていただきありがとうございました!

追記

Twitter経由で質問をもらったので追記します.

このようにHTTPのレスポンスを抽象化して,genericsで解決すると楽です.

https://github.com/cockscomb/Swift-APIClient-Sample/blob/060ba8e9cb4e9924a071d7b3adf533bbb2832bc3/APIClient/APIClient.swift#L60-L78

この戦略については,「はてなエンジニアアドベントカレンダー2014」でid:cockscombが詳しく紹介しています.