Swiftの名前空間とは

Swiftの名前空間とは

今月の13、14日にペパボのお産合宿で音楽位置情報アプリ「Limu」を開発しました。というか、まだ完成していないので開発しています。
iPhoneアプリの本格的な開発は今回が初めてでわからないことばかりでしたが、@nakajijapanに色々教えてもらって、アプリの作り方がわかってきた今日このごろです。
LimuはSwiftで開発しており、その中で作った一部をライブラリに切り出したりしてるうちに(tsuchikazu/iTunesSwift)、
Swiftの暗黙的な名前空間(namespace)ってこういうことだったのか、と実感できたのでそれをまとめました。

名前空間とは

ここでいう名前空間とは

名前空間はソースコード上で冗長な命名規則を用いなくても名前の衝突が起こらないようにし、しかもそれを容易に記述できるようにするためだけの概念

のことを指しています。特にクラス名の衝突について触れていきます。
Objective-C時代は、この名前空間が存在しないためライブラリを作るときもAFNetworkingのAFみたいな感じで、
変なprefixをつけるしかありませんでした。これは、外部ライブラリとそれを使うアプリ側やライブラリ間でクラス名などが衝突しないようにするためです。

Appleの人曰く、Swiftではこれが解決されており、prefixを付ける必要はないとのことです。

これを読んでもよくわからないので、実際にライブラリを作って説明していきます。

Cocoa Touch Frameworkを作って使ってみる

名前空間を理解するために手っ取り早いのは、ライブラリを作って自分で使ってみることです。

ライブラリを作ってみる

Swiftでライブラリを作るには、Projectの新規作成でCocoa Touch Frameworkを選択します。
Cocoa Touch Framework

Product Nameにはライブラリ名を入力します。LanguageはSwiftを選択してください。今回はNamespaceSwiftで作成しました。
NamespaceSwift

新規でTest.swiftファイルを作り、Testクラスを定義してみます

import Foundation
public class Test {

    public init() {
    }

    public func hoge() {
        println("hoge!!!")
    }
}

Test.swift

ライブラリとしてはこれだけで終わりで、一旦閉じておきます。
次に、このライブラリを使うiPhoneアプリを作っていきます。

ライブラリを使ってみる

適当にSingle View Applicationで作ります。
SingleViewApplication

Product NameはNamespaceSwiftDemoで作成しました。
NamespaceSwiftDemo

このアプリで先ほどのライブラリを使うために、ライブラリのxcodeprojをドラッグ&ドロップで持っていきます。
NamespaceSwiftDrag

すると NamespaceSwiftDemo > NamespaceSwiftDemo Target > General > Linked Frameworks and Librariesの「+」ボタンで、NamespaceSwift.frameworkが選択出来るようになります。
LibrarySelect

これで、ライブラリを使う準備が整いましたので、実際に使ってみます

// importしてframeworkを使えるようにします
import NamespaceSwift

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // 先ほど作成したTestクラスを使うことが出来ます
        Test().hoge()

    }
}

UseLibrary

お疲れ様でした。これで準備が整いました。

Swiftの名前空間(namespace)を試す

さて、ようやく本題に入ります。

Swiftの名前空間は、JavaのパッケージやPHPのnamespaceのように明示的に指定する方法ではなく、暗黙的に定義されています。Pythonも同じような感じで、暗黙的に定義されるらしいです。
名前空間は、Xcode targetごと(モジュールと呼ぶのが正しいのでしょうか)に定義されるというか、モジュール名がそのまま名前空間になっています。
自分もモジュールとか理解が浅いのですが、簡単に言えばProjectを作成するときのProduct Nameが名前空間になります。

つまり先程のTestクラスは、NamespaceSwiftプロジェクトのTestクラスなので、名前空間を指定してこう書くことができます。

// ライブラリのTestクラス
NamespaceSwift.Test().hoge()

一方、アプリ側(ライブラリを使っているNamespaceSwiftDemoプロジェクト)で、classを定義すると、NamespaceSwiftDemoが名前空間になります。
そのため、名前空間が別になるので、ライブラリと同じクラス名のTestクラスを作成することができます。

import Foundation
public class Test {

    public init() {
    }

    public func fuga() {
        println("fuga!!!!!!!")
    }
}

TestClassDuplicate

名前空間を指定することで、クラス名が衝突しても同時に使うことができるのです。

// ライブラリのTestクラス
NamespaceSwift.Test().hoge()

// アプリのTestクラス
NamespaceSwiftDemo.Test().fuga()

// 名前空間指定しない場合は、自分の名前空間から探してあればそれ、無ければ別の名前空間を探しにいく
// アプリのTestクラスになる
Test().fuga()

Use

ハマリポイント

個人的にものすごくハマったポイントがありました。
名前空間(モジュール名)と同じクラス名を定義すると、名前空間として解釈されずにクラスとして解釈されてしまうという点です。

// 名前空間と同じクラス
class NamespaceSwift {

}

// 名前空間と同じクラス
class NamespaceSwiftDemo {

}

これは、個人的にはバグなんじゃないかと思っているんですけど、モジュール名は非常に気を使う必要がありそうです。

まとめ

ということで、Swiftには暗黙的に名前空間が存在しており、知らずのうちにその中でclassを作っていたのでした。
これがあるお陰で、今後はライブラリに変なprefixをつける必要がなくなりました。
名前の衝突を気にせず、本来あるべき名前をつけていきましょう。
紹介したプログラムはGitHubにあげていますので、ぜひ試してみてください。tsuchikazu/NamespaceSwiftDemo
(認識違いなどあると思いますので、詳しい人教えてください)