読者です 読者をやめる 読者になる 読者になる

『Swift実践入門』2月7日発売

 海外事業向けのiOSアプリケーション開発を担当している西山(@yuseinishiyama)です。より海外事業に注力するため、今年度から、海外事業の拠点であるイギリス、Bristolのオフィスに出向しています。クックパッドは現在、15言語、58カ国以上を対象にサービスを展開しています。

 先日、ヴァンサンが国内向けのアプリケーションのSwift 3化に関する記事を投稿しました。同じく、海外向けのアプリケーションも、昨年12月にSwift 3化した最初のバージョンをリリースしました。以前、Swift移行の記事で説明したとおり、このプロジェクトはほぼSwiftによって実装されているため、Swift 3化によってほぼ全てのコードが影響を受けました。幸いにも、大きなトラブルは起きませんでした。

 この度、こうした業務での経験を活かして、『Swift実践入門』という書籍を技術評論社のWEB+DB PRESS plusシリーズから2月7日に発売することになりましたので、この場を借りて宣伝させていただきます。APIKitなどで著名な@_ishkawaさんとの共著となります。

Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 (WEB+DB PRESS plus)

Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 (WEB+DB PRESS plus)

 また、刊行記念のイベントも開催することになりましたので、興味のある方はぜひご参加ください。

執筆の動機 〜Swiftのwhyとwhenの解消〜

 SwiftがAppleから発表されたのは2014年です。安全かつ簡潔な文法という触れ込みが印象的でした。iOS開発者としては当然居ても立ってもいられず、発表後すぐ、beta版の公式ドキュメントの全てに目を通しました。そこで強く感じたことは、「簡潔ではあるが簡単ではない」ということです。Objective-Cのユニークな記法と比較すると、フレンドリーな見た目にはなりましたが、その豊富な言語仕様を適切に使いこなすのは容易ではないと思いました。

 Appleの公式ドキュメントをはじめとして、どんな(what)言語仕様があり、それらをどのように(how)使うかに関しては早い段階から豊富な情報源がありましたが、それらがなぜ(why)存在し、いつ(when)使うべきかについてまとまった情報があるとは言えない状況でした。『Swift実践入門』はこうした状況を解消することを主眼としています。

Swiftの難しさ 〜Swiftらしいコードを書くために必要な知識〜

 単にSwiftでコードを書くことと、Swiftらしいコードを書くことは別のことです。ここで言うSwiftらしさとは、Swiftがその言語仕様を持って実現したい世界に倣ったコードのことです。Swiftは豊富な言語仕様を持っていますが、それは同時に、豊富な選択肢があるということを意味します。全てのケースに当てはめれるような解があるわけではなく、それぞれの仕様の存在意義を明確に把握し、都度適切な判断を下さなければなりません。

 初学者がSwiftの言語仕様をある程度理解した後に躓きやすい典型的な箇所として、次のようなものが挙げられます(「使い分け」という言葉が繰り返し登場することから、同じことをするにも複数の方法があることが見て取れるでしょう)。

  • Optional<Wrapped>の使い所
  • 高機能なパターンマッチ
  • モジュールのアクセスレベル
  • プロトコルと継承の使い分け
  • キャプチャリストの使い分け(weakunowned、何もつけない)
  • 値型と参照型使い分け
  • ifguardの使い分け
  • 定数と変数の使い分け
  • エラー処理の使い分け(Optional<Wrapped>do-catchResult<T, Error>)

 例えば、「Optional<Wrapped>の使い所」を説明するために、書籍内では次のようなコードが登場します。どちらにどのようなメリットがあるか適切に説明できるでしょうか。

// 全てのプロパティがOptional<Wrapped>型のケース
struct User {
    let id: Int?
    let name: String?
    let mailAddress: String?

    init(json: [String : Any]) {
        id = json["id"] as? Int
        name = json["name"] as? String
        mailAddress = json["mailAddress"] as? String
    }
}

// Failable Initializerを使うケース
struct User {
    let id: Int
    let name: String
    let mailAddress: String?

    init?(json: [String : Any]) {
        guard let id = json["age"] as? Int,
              let name = json["name"] as? String else {
            return nil
        }

        self.id = id
        self.name = name
        self.mailAddress = json["email"] as? String
    }
}

 『Swift実践入門』は、これらのこと1つ1つ言及し、読者のwhywhenを解消します。

おわりに

 『Swift実践入門』はその名の通り、単なる入門書や言語仕様の解説にとどまらない、実践的な内容を扱っています。著者2人で450ページ強というなかなかのボリュームですが、これからSwiftをはじめようという方から、よりその知識を深めたいという方にまで、ぜひ手にとっていただきたい一冊です。

 一方、『Swift実践入門』は実践的な書籍ではありますが、やはり本当の意味での「実践」からしか得られないこともたくさんあります。例えば、クックパッドでは長期間に渡って大量のユーザーを支えることができるアプリケーションを構築する必要がありますが、そのためにはさらに進んだトピックに取り組まなければいけません。例えば、チームに最適化されたStyle Guideを制定したり、肥大化するビルド時間に対処することがそれにあたります。

 クックパッドでは、こうしたAdvancedな課題に対処したいSwiftエンジニアも募集しています。

国内事業: https://recruit.cookpad.com/jobs/career_recruitment/ios-android/

海外事業: TokyoBristol

Swift 3 マイグレーション

技術部モバイル基盤グループの ヴァンサン です。

西山が 以前紹介したように 、クックパッドでは 2014 年から Swift を使っています。長い間、海外向けのアプリや みんなのお弁当 だけに使われていましたが、去年の5月から、 クックパッド iOS アプリ の開発にも Swift を使うようになりました。歴史のある iOS アプリなので Objective-C でのコードの方がまだ多いのですが、いまは既存の画面の変更を除いて新しいコードが Swift で書かれています。既存の画面を Swift で書き直すこともあります。

Xcode 8.0 がリリースされてから数ヶ月 Swift 2 を使っていましたが、去年の12月のリリース直後に Swift 3 へのマイグレーションをしてから、開発で Swift 3 を使っています。2017年2月1日にリリースされた 17.1.1.0 が Swift 3 を使ってビルドされた最初のバージョンです。

Swift 3 で変わったことを紹介するブログ記事は他に多くあるので、この記事では主にマイグレーションに焦点をあてて書こうと思います。

Swift 3 へのマイグレーションでは、殆どの Swift で書かれたコードが変わります。ですので、同じアプリでマイグレーションと並行して別の開発をすることは難しいです。そのため、年末年始辺りに開発のペースが落ちているのを利用して、2日間、他の開発を中断してマイグレーションを行いました。もちろん必要な期間はアプリのコードやその複雑さによりますし、早めに終わらせるために事前準備が必要ですね。

参考として、クックパッドiOSアプリのマイグレーションを行った時点では、ライブラリを含まなければ、 Swift のコードはファイル数264個(テストを含まなければ213個)、行数2.1万行(空行除けば1.7万)くらいでした。

事前準備

まず、アプリに使われる Swift でのフレームワークが Swift 3 に対応している必要があります。クックパッド iOS アプリでそれが問題になる可能性を見越していたのもあって、 Swift でのフレームワークの導入を抑えていました。マイグレーションの時点で社外 Swift フレームワークは HimotokiResult の2つだけだったので、社外フレームワークの Swift 3 対応に関して問題は特になかったです。

すべてのフレームワークが Swift 3 に対応していたら、試しに手元でマイグレーションをやってみることを強く推奨します。ビルド完成までいかなくても、何時間かやってみれば、色々見られると思います。特にマイグレーション中他の開発が進まないので、本番はできるだけスムーズに進みたいですよね。

僕が試しにマイグレーションをやって分かったことをいくつか紹介しましょう。

  • マイグレーションツールが全部やってくれると思わない方がいいですね。マイグレーションツールを掛けた後に修正がかなり必要になるでしょう。
  • マイグレーションツールが変な変更をすることがあります。例えば、クックパッドアプリでは setTitle(buttonTitle, forState: .Normal) がなぜかツールに setTitle(buttonTitle, forState: UIControlState()) に変換されていました。そういう時、いつでもマイグレーション前のコードと比較できる状態でいる必要があります。あと、それをメモしておいて、次回ツールを掛けた直後にプロジェクトのファイルにそれを検索してすぐ直した方が早いですね。
  • #if / #else / #endif の間に挟まれているコードはマイグレーションツールに無視されて、 Swift 2 のままです。なので、マイグレーションツールを掛ける前にできるだけ #if / #else / #endif をコメントアウトして、ツールを掛け終わったら戻しておきましょう。
  • Carthage とかで入れたフレームワークはマイグレーションがアプリのコードと同時に行われたわけではないので、ツールが何が変わったのか把握しきれません。なので、フレームワークの変更の一部を自分でコードに適用する必要があります。試しにやったマイグレーションでどういう変更が必要そうなのかメモして、本番当日に一部を「全て置換」とかで変えられるようになった方がいいかもしれません。

また、マイグレーションの間に他の開発者が読める Swift 3 の仕様変更のドキュメントを用意してもいいですね。

マイグレーション本番

マイグレーション済みのコードがマージされるまで同じアプリで他の開発を中断しているので、本番は早めに終わらせる必要がありますね。本番の流れは基本的に以下の通りです。

  • 上記に書いた通り、 #if はできるだけコメントアウトします。
  • マイグレーションツールを掛けます: Xcode で Edit → Convert → To Current Swift Syntax
  • 事前準備でメモしたものを利用しながらビルドできるまでコンパイラーが出しているエラーを修正します。

事前準備でやるべきことをだいたい分かったはずなので、早いペースで進められるはずです。

ビルドできて、テストが通って、アプリを実行してみても問題なさそうな状態にもっていくのが重要です。少し不自然なコードは軽く修正できるならすぐ直してもいいのですが、それ以外の修正やもっと Swift 3 っぽくするのも後日でいいはずです。他の開発者が開発をできるだけ早く再開できてほしいですからね。

コードレビューされてからマージするのですが、変更が多いし、開発者がまだ Swift 3 に慣れていないのもあるでしょうし、しっかりしたコードレビューは難しいですね。あとで不具合があってもリリースまで気づく可能性を高めるため、マイグレーションを新しいバージョンの開発の開始時点で行いました。

マイグレーション後

折角 Swift 3 にしたので、 Swift 3 っぽくしたくなりますね。弊社では少し前から Swift 3 の API Design Guidelines をある程度使っていたので、既存の Swift コードが割りと Swift 3 っぽかったです。とはいえ、できることがいくつかありました。

enum

Swift 3 では、 enum の頭文字が小文字になっていて、マイグレーションツールは自動的に多くのを変えてくれるのですが、 StringrawValue を持つ enum (enum MyEnum: String で定義されたもの)は変えてくれません。値名を変えると値変わりますからね(実際値が明確に指定されているとしてもツールが変えてくれませんが)。そういう enum を1つずつ確認して、変えても挙動が変わらなければ頭文字を小文字にした方がいいと思います。

クラスプロパティ

Swift 3 に伴って、 Objective-C でクラスプロパティを使えるようになりましたね。使うと Swift で余計な () が必要じゃなくなります。例えば

// 変更前
NS_ASSUME_NONNULL_BEGIN
@interface CKDActivityLogger : NSObject
+ (instancetype)defaultLogger;
// (省略)
@end
NS_ASSUME_NONNULL_END

// 変更後
NS_ASSUME_NONNULL_BEGIN
@interface CKDActivityLogger : NSObject
// クラスプロパティは instancetype を使えないので、型は明確に
@property (nonatomic, class, readonly) CKDActivityLogger *defaultLogger;
// (省略)
@end
NS_ASSUME_NONNULL_END

それに合わせて Swift 3 側で CKDActivityLogger.default()CKDActivityLogger.default 変える必要があります。そういうところで () がない方が Swift っぽくて読みやすいと思います。

新しい Swift Foundation クラスのブリッジング

Swift 3 では、 Swift から見る Objective-C メソッドの引数の一部の型が変わりました。 NSDateDate に、 NSURLURL に、 NSIndexPathIndexPath に、 NSErrorError に、など。

Objective-C 用のクラスに生やしていたヘルパーメソッドは一時的に as で元の型にキャストしないと使えません。一番ややこしいのは Error です。 Error には、 NSError と違って codedomainuserInfo というプロパティがないので、 as NSError がけっこう必要になります。

マイグレーション後、 Swift のコードに Objective-C Foundation の型(NSDate, NSURL, NSError, …)が残ることありますが、 Swift でできるだけ Swift Foundation の型(Date, URL, Error, …)に変えた方がコードがもっと Swift っぽくて読みやすい気がします。 Objective-C 用のクラスに生やしていたヘルパーメソッドを Swift 用の型にも生やした方が良いと思いますね(Objective-C を使わない場合、 Objective-C用のクラスにまだ生やす必要もないですね)。

Any

Swift 2 で AnyObject になっていたところの多くが Swift 3 では Any になります。マイグレーション後、それに合わせてアプリ内(ディクショナリの値とかで)残っている一部の AnyObjectAny に変えた方が、多くの as AnyObject が消せてコードがもっとシンプルになる場合が多い気がします(実はマイグレーションツールも多くの as AnyObject を入れたりします)。ただし、 AnyAnyObject と違って別の型にキャストしないとメソッドを呼べないので要注意です。

マイグレーション後に気づいた問題

現時点で Swift 3 にした影響で起きた問題は iOS 8 でしか起きない1つの問題だけです。

具体的に、 UITableViewDelegatetableView(_:heightForRowAt:) で起きる問題です。 iOS 8 では、テーブルビューを使う画面から別の画面に遷移する時、 tableView(_:heightForRowAt:) が呼ばれて、それに渡されている IndexPathrow が間違っています。4行あるテーブルの場合、row が 0, 1, 2, 3 ではなく、 0, 0, 1, 2 になってしまいます。

もう少し調べてみると、 iOS 8 では、遷移の時だけ、渡された index path のクラスが UIMutableIndexPath (NSIndexPath のサブクラス)になっています。 Swift のソースを見たら 分かりますが、 NSIndexPath を Swift 用の IndexPath への変換に getIndexes:range: というメソッドが使われています。 iOS 8 では -[UIMutableIndexPath getIndexes:range:] で取得される値が間違っているため、 IndexPath に変換されて間違っている値になります。因みに、 -[UIMutableIndexPath indexAtPosition:] は正しい値を返しているようなので、上記にリンクした IndexPathinit(nsIndexPath:) を以下のコードに変えたら動きそうですね(スピードとかに影響あるか分かりませんが)。

fileprivate init(nsIndexPath: ReferenceType) {
    let count = nsIndexPath.length
    if count == 0 {
        _indexes = []
    } else {
        _indexes = (0..<count).map { nsIndexPath.index(atPosition: $0) }
    }
}

結局その問題が起きたビューコントローラは同じセクションでは高さ2種類だけだったので、2つのセクションを分けて回避できました。でもそういったバグが他にないか心配なので iOS 8 対応をまだしているアプリは Swift 3 が推奨しづらいですね。

最後に

クックパッド iOS アプリで Swift 3 のマイグレーションはしっかり準備した結果 iOS 8 での問題を除いて大きな問題は無かったです。 Swift 3 は変更が多いのですが、言語は色々改善されていると思いますので、 Swift 3 のマイグレーションをしてよかったと思っています。

そもそも近いうちに出る Xcode 8.3 はもう Swift 2.3 に対応しないので、 Swift で iOS の新しい機能を使いたかったら Swift 3 にしない選択肢はないですね。 Xcode 8 の Swift 2 対応にコードエディター関連問題がいくつかありますし。

最後に、 iOS 8 対応をしていない Swift アプリは早めに Swift 3 へのマイグレーションをした方がいい気がします。 iOS 8 対応をしているアプリは少し悩ましいですね。 iOS 8 対応を切るまで Xcode 8.2.1 で我慢するか、 Swift 3 にするけど iOS 8 での動作確認がしっかりするか、ですかね。後者は一部のコードを Objective-C で書く必要が出てくるかもしれませんが。

クックパッドでは Swift 3 で開発したいモバイルエンジニアを募集しています

Cookpad TechConf 2017 提供 Wi-Fi の裏側

f:id:sora_h:20170121093202j:plain

インフラ部 id:sora_h です。

先週開催された Cookpad TechConf 2017 如何でしたでしょうか。わたしは TechConf において Wi-Fi を担当していて、こちらも好評いただいたようでなによりでした。

というわけで、この記事では TechConf 2017 における Wi-Fi についての詳細を紹介します。

ネットワーク機器設定・サーバー mitamae レシピ等の公開

https://github.com/cookpad/techconf2017-network

今回の紹介する構成のうち、ネットワーク機器およびサーバ側の設定等、ほとんどを GitHub で公開しています。参考までにどうぞ。

TechConf 2017 NOC メンバー

実は外注などはしておらず、社内 IT と SRE グループのメンバーで構成されていました。

  • メイン (設計・運用・設営)
    • @sora_h (SRE)
    • 社内 IT ネットワーク担当 1 人
  • サポート (設営・調達等)

どのメンバーも特にカンファレンス Wi-Fi 運用に手慣れた訳ではありません。

Wi-Fi 運用に必要なこと

わたしとしては、最低でも以下が必要という認識です。

続きを読む
/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/