今すぐObjective-CをやめてSwiftを使おう

  • 740
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

はじめに

この3週間ほど仕事でSwiftを使ってアプリ開発をしてみました。その感想として、すべての人に当てはまるとは言えませんが、 多くのiOSアプリ開発者にとっては今すぐにSwiftを使い始める価値があると感じました(Swift 1.0がリリースされてから1ヶ月ほど経った2014年10月時点での感想です)。

そこで、この投稿では Swiftの利点とよくある不安や疑問に対する回答 を書いていきます。

対象となる読者

この投稿は次のようなiOSアプリ開発者を対象としています。

  • Objective-Cで十分でしょと思っている人
  • なんとなくSwift良さそうだけど踏み出せない人
  • Swiftがいいのはわかったけどまだ実案件では使えないと思う人

どうしてObjective-CよりSwiftを使った方がいいの?

Optional Type(これだけでSwiftを使う価値あり)

私見ですが、Objective-CからSwiftに移行してすぐに得られる最も大きな利益は、Optional Typeが使えることだと思います。Optional TypeだけでもSwiftを使う価値があるんじゃないでしょうか。

Swiftでは普通の変数に nil を代入することができません。 nil を代入するにはOptional Typeを使います。

Swift
let stringOrNil: String? = nil // 型に?をつけるとOptionalになる/letは定数の定義

let uppercaseString = stringOrNil.uppercaseString // コンパイルエラー(Optionalなままメソッドやプロパティは呼べない)

if let string: String = stringOrNil { // Optionalがnilでないかをチェックして分岐
    // stringOrNilがnilでない場合のみ実行される
    let uppercaseString = string.uppercaseString
}

普通の変数に nil が代入できず、Optional Typeでも nil かどうかをチェックしなければメソッドが呼べないため、Swiftでは誤って nil に対するメソッドコールをしてしまうことがありません1

nil に対するメソッドコールがどうしてそんなに嫌なのでしょう?Objective-Cには、 nil に対するメソッドコールはエラーにならずに無視されるという悪しき仕様があります。これは一見便利で、次のようにメソッドチェーン(プロパティチェーン)の途中で nil が返ってきてもエラーとならずに nil を受け取ることができます。

Objective-C
baz = foo.bar.baz;

例えば、これをJavaで書いたら次のような感じになります。非常に面倒です。

Java
baz = foo == null ? null : (foo.getBar() == null ? null : foo.getbar().getBaz());

しかし、この便利さと引き換えにObjective-Cは恐ろしいリスクも背負っています。本来、 nil のチェックをしなければならない箇所で nil チェックを忘れてしまったけれども、たまたまうまく動いてしまうことがあります。そしてある日、特定の条件が重なったり、ちょっとした修正を行ったときにそのバグが顕在化するのです。

潜在的なバグは嫌です。テストを繰り返しても、特殊な条件が重なった時にだけ起こる潜在バグが残ります。それはどんなタイミングで顕在化するかわかりませんし、何が起こるかもわかりません(一番多いのはアプリがクラッシュだと思いますが、ファイルをぶっ壊してしまうようなこともあり得ます)。そのおそろしさに比べたら、Javaのように NullPointerException を投げてくれた方がマシです(開発時にバグを顕在化させてくれるという意味で)。

僕はObjective-CのコードをそのままJavaに移植したことがありますが、Objective-Cで nil チェックが漏れており(意図的に nil に対するメソッドコールを無視したわけではなく本来チェックすべき箇所でチェックが行われておらず) NullPointerException が多発しておどろいたことがあります。人間の注意力には限界があります。ミスを減らすには、人間が注意しないといけないことを減らすのが一番です。誤って nil が代入されていることをSwiftはコンパイル時に検出します。Javaは実行時にわかります。Objective-Cは、アプリ出荷後のいつかに問題を引き起こすのです。

もちろん、Optional Typeによってつぶせるのは潜在バグの一部だけです。しかし、 nil はプログラム内の広範囲で使われるので。それによる潜在バグの可能性をつぶせるのは価値のあることだと思います。

ところで、上の foo.bar.baz の例がSwiftで面倒になるのは嫌ですよね。Swiftには、次のようにOptionalな値に?を付けて、メソッドチェーンの途中で nil が発生した場合に途中で打ち切る機能があります(Optional Chainingと呼ばれます)。これなら楽ですね!

Swift
baz = foo?.bar?.baz

Optional Typeについてより詳しくは "SwiftのOptional型を極める" をご覧下さい。

Type Inference(これもとてもうれしい)

Type Inference(型推論) とは、本来

Swift
let s: String = "ABC"

と変数(や定数)の型を宣言しなければならないところを、

Swift
let s = "ABC"

のように型を省略しても、 "ABC" から sString 型だと推論してくれる機能です。

これによって、JavaScriptやRuby、Pythonなどの動的型付け言語のような書き心地でコードを書くことができます。特に、後述のGenericsがあると型宣言が面倒なので、Type Inferenceはうれしい機能です。

Objective-Cと比べると、Type Inferenceに加えてヘッダーファイルやプロトタイプ宣言が必要ないこともあり、内容にもよりますがコードの長さが3割くらいは短くなってるんじゃないかと思います。

let

Swiftには変数を宣言するときに var を、定数を宣言するときに let を使います。

Objective-Cでも次のように書くことができました。

Objective-C
const NSInteger a = 123;

しかし、これでは変数の場合と比べて、 const を付けるという余計なコスト(ここではタイプ数の意味です)が必要です。

基本的に、プログラム中で定数で良いものは定数として宣言すべきです。そうすることで、意図しない再代入を防ぐことができますし、コードを読むときに、その変数(定数)の中に入っている値がどこかで変化している可能性を考慮しなくて良くなります。

Swiftなら、変数と定数を宣言するコストが同じなので、 基本的に let を使い、どうしても変数にしなければならないときだけ var を使う というスタイルでコーディングしやすいです。

Swift
var a = 123 // 変数
let b = 456 // 定数

欲を言えば、変数よりも定数を使わせるために、変数の宣言の方がコストが大きい構文が望ましかったと思いますが。

Swift(変数宣言の方がコストが大きい構文の例)
// ※このコードは正しいSwiftのコードではありません。
var let a = 123 // 変数
let b = 456 // 定数

Operator Overload

Swiftでは演算子を定義することができます。

例えば、 CGPoint に + や - などの演算子を定義するととても便利です。その場合、直線のベクトル方程式から直線上の点を求める計算(方向ベクトル dk 倍してから p に足すという計算)は次のようになります。

Swift
d * k + p

これをObjective-C(というかC)でやろうとすると、 CGPoint 用の関数を用意しておいたとしてもとても面倒です。

Objective-C
CGPointAdd(CGPointMultiply(d, k), p);

Operator Overload は万人向けではないかもしれませんが、ベクトルや行列の演算を多用する人にはとてもうれしい機能です。

Extension

Operator OverloadだけならC++でできるかもしれません。しかし、既存のstruct(構造体)に対してメソッドやプロパティを足すことはできません。Swiftなら Extension を使ってメソッドやプロパティを追加することができます。

CGPointCGAffineTransform にメソッドやプロパティを追加すると、座標系Σaの点Aを座標系Σbに変換し、Σb上の点Bのと距離を求めるといった複雑な計算も簡単に書けます(ΣaでのAの位置ベクトルを a 、ΣbでのBの位置ベクトルを b_、Σb→Σaのアフィン変換をt` とします)。

Swift
let distance = (a * t.inverse - b).length

同じコードをObjective-Cで書くには、計算用の関数を用意しておいたとしても次のようになってしまいます。ぱっと見では何をやっているかわかりません。

Objective-C
CGFloat distance = CGPointGetLength(CGPointSubtract(CGPointApplyAffineTransform(a, CGAffineTransformInvert(t)), b));

なお、ここで挙げた CGPointCGAffineTransform のOperator OverloadやExtensionをライブラリ化したものはこちらにあります。

Generics

Objective-Cは静的片付けの言語ですが、コレクション( NSArray, NSDictionary, NSSet など)は id 型でごまかして(暗黙的なダウンキャストを許容して)おり、タイプセーフではありませんでした。

Swiftには Generics があるので ArrayDictionary を次のように書けます(型の部分はType Inferenceで省略できます)。

Swift
let array: Array<String> = ["ABC", "DEF", "GHI"] // Array<String>は[String]とも書ける
let dictionary: Dictionary<String, Int> = [ // Dictionary<String, Int>は[String: Int]とも書ける
    "ABC": 123,
    "DEF": 456,
    "GHI": 789,
]

let a: Integer = array[0] // arrayの要素はStringなのでコンパイルエラー
let b: String = dictionary["ABC"] // dictionaryの値はIntegerなのでコンパイルエラー

その他

Swiftにはその他にも、第一級関数やパターンマッチのような関数型言語風な機能もあります。しかし、それらの恩恵を受けるには新しいパラダイムを学び、経験を積む必要があります。

それらが使えなくとも、Objective-Cに親しんだ開発者がSwiftに乗り換えれば、ひとまずOptional TypeやType Inferenceが使えるだけで十分に大きな恩恵を得られるんじゃないかと思います。そして、 早めにSwiftに乗り換えることで、将来Objective-Cのコードをメンテする機会を減らすことができます

今はまだSwiftに移行できない?

Swiftのフレームワークやライブラリについて学習するのが大変そう

新しい言語を学ぶときに一番時間がかかるのは、言語そのものを学ぶことよりも、その言語を使って開発するときに必要なフレームワークやライブラリ、ツールの使い方を学ぶことではないかと思います。

SwiftでiOSアプリ開発をするときには、Objective-Cのとき同様にFoundationやUIKitなどのフレームワークを使います。開発環境もXcodeで、Objective-Cのときとほぼ同じ感覚で使えます。Interface Builderの挙動も同じです。

Swiftで書いたiOSアプリのコードは、例えば次のような感じです。iOS開発者であれば、Swiftを知らなくても何をやっているのかすぐにわかるのではないでしょうか。

Swift
class MyViewController: UIViewController, UIWebViewDelegate {
    @IBOutlet var webView: UIWebView!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: "onPanGestureRecognized:"))

        webView.delegate = self
    }

    func onPanGestureRecognized(gestureRecognizer: UIPanGestureRecognizer) {
        if gestureRecognizer.state == UIGestureRecognizerState.Ended {
            ...
        }
    }

    ...
}

実際、Swiftでまともにアプリを作れるようになるまでにかかった時間はせいぜい2日、多めに見積もっても3日くらいじゃないかと思います。

Objective-Cの資産がたくさんあるので無理

SwiftはObjective-Cのコードと共存し、Objective-Cのクラスやメソッドをシームレスに呼び出すことができます。実際に、Swiftで開発をする場合でもUIKit等のフレームワークを使いますが、ほぼストレスなくそれらを使うことができます。

例えば次のような感じです。

Swift
UIView.animateWithDuration(0.25, animations: {
    self.fooView.alpha = 0.0
    self.barView.frame.origin.y -= 100.0
})

僕も自社製のObjective-CフレームワークをSwiftから使ってみましたが、まったく問題なく使うことができました。

Swiftこわい

Swiftは平易で学びやすい言語です。Objective-Cが使える人なら、ほとんど新しい概念を学ぶことなく、書き方を覚えるだけで使い始められると思います。

公式の"The Swift Programming Language"がとてもよくできているので(Appleのその他のドキュメントと比べても質が高いと思います)、言語についてはこれを読めば十分です(iBooksからも入手できます)。とりあえず、A Swift Tourを眺めてみるだけでもどんな感じの言語かわかると思います。

Objective-Cとの連携についても、公式の"Using Swift with Cocoa and Objective-C"に詳しく説明されています。

一つわかりづらいことがあるとすれば、 ArrayDictionary がValue Types(値型)なことだと思います。それについては↓にまとめました。

Cと連携しづらそう

Objective-CはCと簡単に連携できるのが良いところでした。

SwiftからもCの関数をそのまま呼ぶことができます。Cで作ったstruct(構造体)もそのまま使えますし、ポインタでさえCに近い感じで操作できます。

↓はモノクロ画像を閾値 threshold で二値化するコードの比較です。そっくりじゃないですか?

C
UInt8 *pixels = ...;

UInt8 *pixelPointer = pixels;
for (NSUInteger i = 0; i < numberOfPixels; i++) {
    *pixelPointer = *pixelPointer < threshold ? 0 : 255;
    pixelPointer++;
}
Swift
let pixels: UnsafeMutablePointer<Byte> = ...

var pixelPointer = pixels
for _ in 0..<numberOfPixels {
    pixelPointer.memory = pixelPointer.memory < threshold ? 0 : 255
    pixelPointer++;
}

iOS 6以下のサポートが必要

SwiftはiOS 7以上にのみ対応しています。iOS 6以下をサポートする場合は使うことができません。

しかし、Appleの発表によると、iOS 7以上のシェアは94%に達しています。この状況でiOS 6以下をサポートすることにどれほどの意味があるでしょうか。

受託開発であったとしても、iOS6以下をサポートすることで

  • 開発/テスト工数が増える → 開発費が増える
  • 複数バージョン対応によりバグが生まれやすくなる

などのデメリットの方が大きいのではないかと思います。

僕も最近はお客さんにiOS 7以上のサポートにしましょうと話しています。

まとめ

Objective-CをやめてSwiftを使うと何がうれしいのか、まだSwiftに移行にできないと思ってるけど実は問題ないんじゃないかということについて書きました。

多くの人にとってSwiftを使い始めることの主な障害は最初にSwiftを学ぶわずかなコストだけだと思います。それを乗り越えて、モダンな機能を持ったSwiftによる快適なiOSアプリ開発を始めましょう!



  1. Forced Unwrappingを使った場合にはこの限りではありません。