LIFULL Creators Blog

LIFULL Creators Blogとは、株式会社LIFULLの社員が記事を共有するブログです。自分の役立つ経験や知識を広めることで世界をもっとFULLにしていきます。

右も左も分からない新卒が純正Objective-CのiOSアプリにSwiftを突っ込んでみた話

こんにちは。HOME'SのiOSアプリチームの新卒1年目の塙です。
新卒入社して早9ヶ月、時間が過ぎるのは早いですね。

Swiftを導入するまで

タイトルからお察し頂けるかと思いますが、つい最近まで弊社のiOSアプリは全てObjective-Cという"最先端の言語"で書かれていました。

しかし昨年Swiftが公に発表され、今年にはメジャーアップデート、さらにはオープンソース化されました。

swift.org

私はこの波に乗っている新しい言語で書きたい気持ちが一杯でした。

f:id:nextdeveloper:20151221113822j:plain:w500

ただ、4年間に渡ってユーザの皆様のご要望に基づいて様々な機能を追加していった秘伝のタレに、アップデートの激しい新言語を取り入れるのは、メンテナンスの手間を考えるとGOサインを出しにくかったと思います。

そんな中、我々のリーダーが勇気ある決断をしてくれました。
「塙君、次の機能なんだけど、新しい機能だからSwiftで書いてみない?」
この言葉を聞いた時に、NOという選択肢は頭の中にありませんでした。

Objective-Cとの共存

まず、既存のプロジェクトにSwiftを共存させる上での必要な作業をざっと調べました。

SwiftからObjective-Cを使用する場合

$(PRODUCT_MODULE_NAME)-Bridging-Header.hを作成
② 上記ファイルにSwiftで使用するObjective-Cのヘッダファイルをimport

#import <GameFeatKit/GFController.h>
#import "NSObject+Empty.h"

③ ターゲットの[Build Settings] → [Objective-C Bridging Header]に上記ファイルを指定
※ 初めてSwiftファイルを追加する際には、上記1〜3の設定が全て自動で行われます

Objective-CからSwiftを使用する場合

$(PRODUCT_MODULE_NAME)-Swift.hを対象の.mファイルにimport(ヘッダファイルにインポートするとコンパイルエラーになる)

#import "YourProduct-Swift.h"
  • $(PRODUCT_MODULE_NAME)はデフォルトで$(PRODUCT_NAME)
  • $(PRODUCT_NAME)のデフォルト値は$(TARGET_NAME)
  • もし$(PRODUCT_NAME).-がある場合は_にリプレイス

$(PRODUCT_MODULE_NAME)-Swift.hはXcodeが自動生成し、ファイル内の変更は一切必要ありません

互換を保つために気をつけること

サブクラスと属性の付与

Objective-CからSwiftクラスを呼び出す場合

  • Objective-Cクラスのサブクラスとして定義
  • 上記以外の場合は@objc属性を付与すれば名前の変更も可能
    これらを行うことで自動的に$(PRODUCT_MODULE_NAME)-Swift.hに追加される
class MySwiftClass: NSObject {
    // implementation
}

@objc(MySwiftClass)
class 私のスイフトクラス: NSObject {
    // implementation
}

※ パフォーマンスとSwift移行の為にも必要な場合以外はネイティブベースを使用する方が良いです

コンバージョン

Objective-Cの変数を型変換してSwiftで使用する場合

// CGFloat型をFloat型に代入するにはコンバージョンが必要
myFloat = Float(cgfloat)

ダウンキャスト

Objective-Cの変数を型変換してSwiftで使用する場合(派生クラス)

// NSDictionary型をDictionary型に代入するにはas演算子でダウンキャストが必要
myDictionary = nsdictionary as Dictionary<String , AnyObject>

Enum

Objective-C側でNS_ENUMを使用すれば、Swiftからenumとして呼び出せる

typedef NS_ENUM(NSInteger, Fruit) {
    FruitApple,
    FruitOrange,
    FruitGrapes
};

Swiftで呼び出す際には以下の内容に変換されます

enum Fruit: Int {
    case .Apple
    case .Orange
    case .Grapes
} 

※ メンバの接頭語はSwiftで使用する際に自動で省略されます

互換性のないもの

  • Swiftで定義したジェネリクス
  • タプル
  • Int以外のrawValueを使用したEnum
  • Swiftで定義した構造体
  • Swiftでトップレベルで宣言された関数
  • Swiftで定義したグローバル変数
  • タイプエイリアス
  • Swiftで定義した可変引数(var)
  • ネストされた型
  • カリー化関数

ユニットテスト

  • Objective-Cで書かれたユニットテストは$(PRODUCT_MODULE_NAME)-Swift.hをインポート不可(ターゲット内で使用可能の為)
  • SwiftコードのユニットテストはSwift(別ファイル)で記述(Objective-Cのフレームワークを使用する場合はインポート可能)

上記を頭に入れ、Swiftでプログラミングを行うのは特に労力のいるものではなく、案外すんなり動いてくれたので拍子抜けでした。
※ 実際のプログラミングではSwiftで書く時間よりも、既存のObjective-Cで書かれた箇所の改修に時間がかかりました(泣)

Swiftの恩恵

現在では、基本的に新しい機能の追加をする際にはSwiftで書くようになっています。Swiftを使用することで下記のような恩恵を受けることができます。

  • 型安全なコードによってnilが入るなどのよくあるクラッシュをコンパイル時に防止
  • 記述方法がシンプルになったことでコード量の削減
  • Swiftネイティブ(enum、struct)の型を使用することで柔軟なプログラミングが可能

実際の実装の中で感じたのは、enumにprotocolを準拠させたり、getterでパターンマッチさせる方法はとても便利でした。

protocol Color {
    var color: String { get }
}

enum Fruit: String, Color {
    case Apple = "apple"
    case Orange = "orange"

    var color: String {
        switch self {
        case .Apple:
            return "Red"
        case .Orange:
            return "Orange"
        }
    }
}

最後に

書き方に慣れない、コンパイル時間が長いなど少なからず不満はあるとは思いますが、それ以上にSwiftという新しい言語に挑戦できる環境というものはエンジニアにとってこの上なく嬉しいものだと思います。

是非、この記事を読んだiOSアプリエンジニアの皆さんは自社のプロジェクトがObjective-Cで進んでいた場合、とりあえず新しい機能や、書き直せるものからSwiftを導入してみることをお勧めします。

参考文献

Using Swift with Cocoa and Objective-C (Swift 2.1): Swift and Objective-C in the Same Project

Using Swift with Cocoa and Objective-C (Swift 2.1): Writing Swift Classes with Objective-C Behavior

SwiftのENUM - Qiita

SwiftとObjective-Cの相互利用する際の注意 - Qiita