CodeZine(コードジン)

特集ページ一覧

【デブサミ2016】18-A-4レポート
肥大化したObjective-Cコードは設計から見直す! ヤフーのiOSアプリ事例で学ぶSwift対応のポイント

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2016/03/25 14:00

 iOSおよびMac OS XアプリのためにAppleが開発したプログラミング言語「Swift」。MacはもちろんiPhoneやiPad、Apple TV、Apple Watchなど多彩なApple端末のアプリケーションを手軽に開発できるものとして、多くの注目を集めている。その魅力およびObjective-Cからの効率的な移行方法、そしてSwiftに導入されたパラダイムの考え方やアプリ設計への適用について、ヤフー株式会社 メディアカンパニー検索事業本部 アプリ開発部の佐野岳人氏が解説した。

ヤフー メディアカンパニー検索事業本部 アプリ開発部 佐野岳人氏
ヤフー メディアカンパニー検索事業本部 アプリ開発部 佐野岳人氏

Objective-Cの紆余曲折から急激に進化 “モダンで安全な言語”になった「Swift」

 Swiftは、WWDC 2014の基調講演において突如彗星のごとく発表されたAppleの新言語だ。それまで使っていたObjective-Cに比べ、“モダンで安全な言語”になったと言われている。言語仕様/開発環境共に急速な進化を続け、すぐ翌2015年には2.0が登場。年末にはオープンソース化され、コンパイラやコアフレームワークも公開。Linuxもサポートされた。Appleだけでなく、外部の多彩なデベロッパーを巻き込みながら、Swift 3.0 に向けて進化を続けているといったところだろう。

 もともとiOSなどで使われていたObjective-Cは、1983年に開発され、ジョブズとともに紆余曲折を経て2001年のMac OS XのCocoaフレームワークのコア言語として採用されたという歴史を持つ。佐野氏は「C言語をオブジェクト指向化したものだが、オブジェクトはただの構造体へのポインタであり、メソッドコールもCの関数呼び出しに変換される。つまり、C言語を“ラッピング”したようなもの」と解説する。

 そのため、古い言語として非効率で難解、ランタイム時処理においてはオーバーヘッド、さらに柔軟性が保たれる分、コンパイル時の最適化ができないことや、型付けが弱くて実行した際にエラーが落ちるなど、様々な弱点が指摘されていた。それが2007年に2.0でプロパティ等が加わり、2012年の段階にModern Objective-Cとなった際に、Appleが独自に開発したコンパイラ「LLVMシステム」が加わったことで急速に進化し、Swiftの誕生へとつながった。

Swiftに至るまでの進化の背景
Swiftに至るまでの進化の背景

 佐野氏は言語としてのSwiftを、モダンで読みやすい言語仕様、型安全であり、関数型でプロトコル仕様、Objective-Cとの互換性などと説明。Swiftに対応するメリットとして、Objective-Cよりも安全なコードを効率的に書くことができること、コンパイル時最適化でパフォーマンスの改善が期待できること、そして、iOSアプリ開発においてはSwiftが標準となることは明らかであり、3rdパーティ製ライブラリも徐々にObjective-Cサポートを切るであろうことをあげた。

「Yahoo!ショッピング」などヤフー製アプリの約30%がSwift対応

 それでは、ヤフーのSwift対応はどこまで進んでいるのか。佐野氏によると、ヤフー製の59アプリのうち20アプリ、3分の1が導入済みだという(2016年2月18日時点)。現在、会社をあげての推進活動により、主力アプリは2016年度中には導入される予定だ。

 既にSwiftに対応した事例として、まず「Yahoo!ショッピング」iOSアプリが紹介された。2010年に開発を開始し、2011年にver1.0.0がリリースされたが、機能追加やビジネス案件の対応により徐々にコード量が増加し、さらに古いコードが多く、ビューとロジックが分離されていないなどから、新機能追加が高コストになっていた。

 そこで、昨年の6月よりSwift導入対応プロジェクトを開始した。しかし、通常の開発を止め、フルスクラッチで作り直すことはビジネス判断でNGとされ、少数の推進メンバーが中心となって、画面/機能単位で段階的に対応を行っていった。なお、その際にはSwift対応によるメンテナンス性向上とともに、iPad対応も併せて進めた。

 ホーム、商品検索、商品詳細と、通信やユーティリティ部分の共通ライブラリに分けて、引き出し可能な部分や頻繁に手が入る部分から対応を進めていった。その詳細については「Yahoo! JAPAN Tech Blog」にて掲載しているという。

Swift対応は頻繁に手が入る部分から進めた
Swift対応は頻繁に手が入る部分から進めた

 この対応によって、クラッシュ率がほぼ半分に大幅に低下し、実装コストも大きく削減できた。さらに、同時に進めたiPadアプリが想像以上にヒットすることになった。問題もないわけではなかったが、たとえばSwiftに慣れるまで一時的に開発速度が落ちたことは、長い目で見ればプラスであり、新言語の学習コストは自分たちで自主的に勉強会を開催してカバーしたという。ただ、Objective-Cよりビルドに時間がかかったこと、頻繁なSwift自体の仕様変更でその度に対応が必要だったことなどが、苦労点としてあげられた。

Swift対応のカギとなるMVVMアーキテクチャのメリット

 続いて「ヤフオク!」iOSアプリの事例が紹介された。こちらも2010年にver1.0.0がリリースされてから、長年に渡って多くの開発者の手にかかり、新旧のコードが入り混じって肥大化していた。簡単な機能追加でも影響範囲が分かりづらく、かなりコストがかかっていたという。

 そこで昨年末よりSwift対応プロジェクトが始動した。こちらも開発を止めずにアプリを根本的に再設計するために、少数の推進メンバーが先行して導入をはかった。特にコンポーネント間の依存関係を断ち切るため、MVCからRxSwiftによるMVVMアーキテクチャへと変更したという(RxSwift:リアクティブプログラミングを行うためのライブラリ「ReactiveX」のSwift実装)。

MVCアーキテクチャからMVVMアーキテクチャへ
MVCアーキテクチャからMVVMアーキテクチャへ

 「二者の違いは、ViewControllerからViewを管理するViewModelを分離したこと。MVVMでは、お互いの依存関係をつくらないために、メソッド単位で結合するのではなく、自分が責任を持っている範囲の変更をするだけで、必要な人が必要に応じて受け取り、半自動的に切り替わるというような作りになっている」(佐野氏)

 MVVMアーキテクチャのメリットとしては、コンポーネントが疎結合になり、安全性や再利用性が高まること、コンポーネント間の連携の仕方が規定されており、不用意に肥大化することが避けられること、そしてコードの書き方が統一されるため、メンバーの能力差による品質のバラツキが抑えられることなどがある。

 一方、外部フレームワークの採用のリスクとしては、提唱するアーキテクチャを理解しなければならないこと、進化するフレームワークに適応していく必要があるなどがある。最もリスクとして大きいのは、外部フレームワークへの依存性が強すぎると、それが廃れたときに抜け出せなくなる危険があるということだ。佐野氏はかつてのThree20ではしごを外された例を紹介し、「それなりの覚悟と未来のビジョンがないと、いざ状況が変わった時に困ってしまう。なので、手離しでは推奨しないが、それでもMVVMアーキテクチャを活用する価値はある」と語った。

 現状では画面単位での再設計を進めており、本格的な開発が始まれば、外部のエンジニアも参加することから、事前に設計のガイドラインやコーディング規約等も用意しつつあるという。そして、5〜6月にはフルリニューアルを完了させる予定だ。

 なお、3つめの事例も紹介されたが、Swiftに自動変換するソフトウェアを自分たちでつくり、ほぼ100%置き換えられているが、設計の部分をObjective-Cのまま引き継いだため、早くも肥大化・複雑化が進んでいるという。早々のMVVMアーキテクチャの導入を検討しているそうだ。

 こうした事例を振り返り、佐野氏の気づきとして「ゼロからSwiftアプリを作るのは楽しい。しかし、既存アプリをSwift対応するのは実に大変。特に多くのユーザを抱えて稼働しているアプリの場合は、不利益を出さずに移行できるよう計画が肝心だ。経営層にアピールするには、ユニバーサル化や設計の見直しなどと併せてSwift導入をすると説得しやすい」と語った。

Swift脳になるための3つのポイント

 よりSwift風なコードを書くために、佐野氏は「書き方以外にもコツがある」と語る。まずWWDCで語られていた「Swift=“Objective-C without the C”」ではなく、Objective-Cにはなかった新しい考え方、特にSwiftの癖を理解する必要がある。そこを踏まえて、次のようなことを乗り越えると「Swiftと仲良くなれる」という。

 最初のコツは「Optional型と仲良くなる」こと。Appleのドキュメントでは、「Optional型は値の存在しないことをハンドルできるもので、値があるか、ないかのどちらかを表す。SwiftでOptional型を使うのは、Objective-Cでnilを使うようなもの」と書かれているが、そのつもりでSwiftに臨むと、nilの扱いを厳しく言われて「中身がある場合」の分岐が多数生まれて、期待した通りの美しいコードが書けなくなる。

 つまり、nilが入れられると考えるのではなく、中身の仕組みを理解する方がよいだろう。Optionalは任意の型を持てるジェネリックなenumで、Int?はOptionalと同じ。任意の値はOptional型への代入によってOptional型に暗黙キャストされる。これが便利でもあり、Optional型の、型の存在を分からなくする点でもある。そこを理解することがカギとなる。

 またSwiftのnilは、Objective-Cのnilとは異なりただのリテラルで、実態はOptional型の値Noneである。つまり「値を入れられる(入れなくてもいい)箱」というわけだ。佐野氏はさらに「もっと単純に考えて、Optionalは長さか0または1のArrayと思おう」と語る。

Optional型は「値を入れられる(入れなくてもいい)箱」
Optional型は「値を入れられる(入れなくてもいい)箱」

 つまり、SwiftはOptionalに対して限定的に暗黙キャストを認めており、Int?に代入された1は Optional.Some(1)、さらにnilはただのリテラルで、実態は Optional.None Obj-Cのnil(NULLポインタ)とは完全に別物と認識すべきだという。なお、if let/guard letは便利だが冗長になることが多い。そこで、関数型言語の特徴を利用すると、美しいコードが書けるというわけだ。

 というわけで、コツの二つ目は、多くの人は敬遠する傾向にある「関数型」を怖がらないことだ。関数をといっても、変数とは別のものと考えるのではなく一つのオブジェクトと考え、関数を変数に代入する、引数にとる、戻り値で返すことができるということである(このことを「第1級クラス」という)。たとえば、佐野氏がユニークな例としてあげたのが次のコードだ。

左:Swiftでの関数の代入の例、右:左の処理をわかりやすく書いたもの
左:Swiftでの関数の代入の例、右:左の処理をわかりやすく書いたもの

 左のコードは、Objective-Cでは見ない書き方だが、演算子そのものが関数として定義されており、sortは、引数に「ある型のものを2つ受け取って真か偽かを返す」という関数の型をとる関数をとっている。sortに>(大なり)など関数である演算子を入れるだけでうまくソートされる。右のコードは左と同じ処理であるが、lowerThanという関数を作り、何が行われているかをわかりやすく書いたものだ。関数を変数に代入し、関数の引数に渡していることがわかるだろう。

 佐野氏は「Objective-Cでは、関数・メソッド・ブロック・演算子は全て別のものだったが、Swiftでは全て関数で統一されている。ぜひ、mapやfilterを使いこなそう」と語る。

 ただし、mapする関数がOptionalを返す場合は二重に包まれてしまう。その際にはmap/flatMap を上手く使うとif letを多用せずに済み、構造として分かりやすくできる。

 そして三つ目のコツは「値型・不変性・プロトコル指向」である。まず、Swiftには2つの型があり、struct、enumで定義される型は値型、classで定義される型は参照型となる。Objective-Cではintなどのプリミティブ型、CGRectなどの構造体は値型となり、NSArray、NSStringなどオブジェクト型は参照型(ポイント)となる。一方、SwiftではInt、Array、Stringは全て値型であり、Objective-Cベースのクラスは参照型だ。

 また、Objective-Cではどんな変数でも再代入可能だった。しかし、Swiftの変数は、letでの宣言で不変、varで可変というように制約を持つ。この「不変な値型」は関数型言語と相性がいい。同じオブジェクトを別々に参照しているうちに勝手に書き換わりトラブルになることがよくあるが、しかし、不変な値型については参照の共有による副作用がない。つまり、変数を直接書き換えるのではなく、操作は全て「関数」で実現されるため、何が起きているか構造的に分かる。プログラミングを勉強し始めによく見る記述「a = a + 1」は、不変な値型の世界では使われないのだ。

 ここにSwiftでは「プロトコル指向」というパラダイムが持ち込まれている。それと対比する「クラス指向」が継承によってデータと機能が増えていくのに対し、「プロトコル指向」では具体型が必要最小のデータを持ち、プロトコルによって機能を足していく。

「プロトコル指向」というパラダイム
「プロトコル指向」というパラダイム

 「値型・不変性・プロトコル指向」について、佐野氏は「かちっとしたコードが書ける、というのでテンションが上がる。ただし、そうそうは甘くない」として、その理由を次のように述べる。

 「Foundation/UIKit は Objective-Cベースなので、クラスは参照型となり可変な値を持つ。さらにPure-SwiftなメソッドはSelectorで呼び出したりできないので、target-actionパターンと相容れない。さらに、Swiftのジェネリクスが未熟で、複雑な値型を作ろうとするとすぐ無理が出てくる」

 そこで、無駄な副作用のない設計を常に意識しつつ、そのために可読性を犠牲にしないことが大切だという。そしてSwiftがさらに進化し、UIフレームワーク自体がPure-Swift化されない限りは、「関数型プロトコル指向」を徹底するのは無理がある。「ほどほどに取り入れつつ、Scala、Haskellなどを学んで知見を広げよう」と佐野氏は語った。

 そして、Objective-CからSwiftに書き換える、ヤフーが作成したSwiftコンバータ「objc2swift」が簡単に紹介された。2015年末にオープンソースで公開されている。これからSwift対応したいと考えている人は試してみるとよいだろう。

 最後に佐野氏は「Objective-CからSwiftに移行することで、より効率的で安全なコードを書くことができる。そして、この先もAppleはSwiftを推進するのは明白なので、できるだけ早くキャッチアップしたい。ただし、大きなアプリをSwift対応するには、メリットとリスクを見極め、計画的に進めることが肝心。単にコードを書き換えるだけでなく Swiftらしい書き方を習得してメリットを最大化しよう」と結び、セッションを終えた。

お問い合わせ

 ヤフー株式会社

  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • CodeZine編集部(コードジンヘンシュウブ)

    CodeZineは、株式会社翔泳社が運営するソフトウェア開発者向けのWebメディアです。「デベロッパーの成長と課題解決に貢献するメディア」をコンセプトに、現場で役立つ最新情報を日々お届けします。

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5