iOSアプリのコードをいくつか見ていて、名前空間の扱いがKotlinとSwiftで異なり困惑する場面があったので、その解決方法に関する備忘録です。
名前空間とは
名前空間についてはe-wordsの説明が非常に分かりやすかったので、そのまま引用させていただきます。
名前空間とは、各要素に一意の異なる名前をつけなければ識別できない範囲のこと。 また、名前の集合全体を小さな空間に区切り、それぞれに異なる識別名を与えることで、その空間内では他の空間に含まれる名前の競合・衝突を意識しなくて良いようにしたもの。
名前空間の例として「中央区」が挙げられていました。こちらもそのまま引用させていただきます。
例えば、「中央区」という行政区名は全国のいくつかの自治体に存在し、それだけではどこを指すのか分からないが、「東京都中央区」「大阪市中央区」と表記すればそれぞれを識別することができる。 この「東京都」や「大阪市」が「中央区」に対する名前空間の役割を果たしている。
自治体や住所のように階層的な名前空間によって全体が区切られていれば、大阪市に文脈が限られている場合は「大阪府大阪市中央区」とすべて書き下さなくても「中央区」のみでこれを指し示すことができ、東京の中央区を示したければ「東京都中央区」と名前空間を指定して対象を表すこともできる。
Swiftではグループによるクラス名の識別はできない
以降では、ViewModel
のように同一名で何度も宣言するであろうクラス名の衝突について考えます。
以下のようなファイル構成のプロジェクトがあるとします。
project ├ hoge │ │ HogeView.kt (or HogeView.swift) │ └ HogeViewModel.kt (or HogeViewModel.swift) └ fuga │ FugaView.kt (or FugaView.swift) └ FugaViewModel.kt (or FugaViewModel.swift)
Kotlinでは、パッケージというファイル管理の仕組みによってクラス名を識別することができます。
// HogeViewModel.kt package project.hoge data class ViewModel(...)
// FugaViewModel.kt package project.fuga data class ViewModel(...)
このとき、HogeView
、FugaView
側でそれぞれ使用したいViewModel
の所属するパッケージ名をimport
で指定することで、クラス名を識別することができます。
(今回の例ではデフォルトで追加されるパッケージ名を変更しない限りは同一のパッケージに所属することになるので、import
を明示的に指定する必要はありません。)
// HogeView.kt package project.hoge import android.content.Context import android.util.AttributeSet import android.view.View class HogeView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : View(context, attrs, defStyleAttr) { ... // HogeView.ViewModelと同義 fun configure(item: ViewModel) { ... } ... }
Swiftでは、ファイル管理の仕組みとしてグループというものがありますが、グループを利用してもKotlinのような方法でクラス名の識別をすることはできません。
// HogeViewModel.swift struct ViewModel { ... }
// FugaViewModel.swift struct ViewModel { ... }
上記のような構成にしようとすると、いずれかのViewModel
に対してInvalid redeclaration of 'ViewModel'
と表示され、コンパイルエラーとなります。
Nested Typeによる解決
このようなとき、SwiftではNested Type、つまり型を入れ子にして宣言することでクラス名を識別することができます。
// HogeView.swift import UIKit class HogeView: UIView { ... // HogeView.ViewModelと同義 func configure(_ item: ViewModel) { ... } ... struct ViewModel { ... } }
しかし、上記の方法では大元となるHogeView
、FugaView
の中に入れ子にしてクラスを追加していくことになるので、場合によっては1ファイルあたりのコード量がどんどん増えてしまいます。
extensionの利用による解決
この問題はextensionを利用することで解決できます。
以下のようにすることで、大元となるHogeView
、FugaView
とは別のファイルの中にクラスを追加することができます。
// HogeViewModel.swift extension HogeView { struct ViewModel { ... } }
// FugaViewModel.swift extension FugaView { struct ViewModel { ... } }
上記のクラスは先ほどのNested Typeと同じ形で扱うことができます。
extensionを利用したNested Typeの宣言については、公式ドキュメントにも記載があります。
// HogeView.swift import UIKit class HogeView: UIView { ... // HogeView.ViewModelと同義 func configure(_ item: ViewModel) { ... } ... }