Swiftでの列挙型(enum)の気持ちを、クラスと構造体から考えてみた。
こんにちは、すいふぁー渡部です。
以前の「Swift入門者が覚えておきたい17項目」の列挙型でも書きましたが、Swiftの列挙型(enum)って凄い色々出来るんですよね。関数を記述できるし、値持ってるし。
で、ふと気がついた。
これってクラスとか構造体と同じようなクラス・型を定義するものなんじゃないかと。
という訳で、Swiftの列挙型をクラス、構造体と比較してみることで、新しい型を作る定義なのか、どうかを考えてみたいと思います。
検証環境は、Xcode6.2で行っています。
列挙型はクラスの夢を見るか?
全く関係ないですが、ブレードランナーの続編が2015年、今年の夏から作成が開始されるらしいですね。っと、楽しみにしていましたが、記事を書いてる最中に、出演を予定していたハリソン・フォード氏が事故ったそうで、どうなるんですかね。
それでは、今回の主役たちをご紹介したいと思います。
今回出場する3人を定義する。
1.クラス
クラスの定義はこう!
1 2 |
[アクセス修飾子] class [名称] { } |
今回は「TestClass」という名前で定義した。
1 2 3 |
public class TestClass { } |
2.構造体
構造体の定義はこれ!
1 2 |
[アクセス修飾子] struct [名称] { } |
今回は「TestStruct」という名前で定義する。
1 2 3 |
public struct TestStruct { } |
3.列挙型
列挙型の定義!
1 2 |
[アクセス修飾子] enum [名称] { } |
今回は主役を「TestEnum」という名前で定義します。
1 2 3 |
public enum TestEnum { } |
変数・定数・メソッドを定義してみる。
1.クラス
まぁ当たり前ですが、普通に定義可能です。
1 2 3 4 5 6 7 |
public class TestClass { public var hoge: Int = 0 public let HOGE: Int = 0 func hogehoge() -> Int { return HOGE } } |
2.構造体
これも当然ですが、定義可能です。
1 2 3 4 5 6 7 |
public struct TestStruct { public var hoge: Int = 0 public let HOGE: Int = 0 func hogehoge() -> Int { return HOGE } } |
3.列挙型
varやletの定義は出来ません(Xcode上でエラーになる)が、caseで定数定義、変数はcaseで定義されたいずれかの1つの値になりますが、定義できてると思います。
1 2 3 4 5 6 7 8 |
public enum TestEnum { // private var hoge: Int = 0 // コード記述時にエラーになる。 // let HOGE: Int = 0 // コード記述時にエラーになる。 case HOGE func hogehoge() -> Int { return 0 } } |
更に検証
「Swift入門者が覚えておきたい17項目」の構造体でも書いた、クラスと構造体を分ける4つの要素で検証してみる。
1.継承
1.クラス
普通に大丈夫。
1 2 |
public class TestClass2: TestClass { } |
2.構造体
構造体は継承が出来ない事になっているが、継承できないとはどういうことなのかを確認してみた。
継承は出来ない。
1 2 |
public struct TestStruct2: TestStruct { } |
プロトコルの継承は出来る。
1 2 3 4 5 |
public protocol hoge { } public struct TestStruct3: hoge { } |
クラスから継承は出来ない。
1 2 |
public struct TestStruct4: TestClass { } |
クラスへの継承も出来ない。
1 2 |
public class TestClass3: TestStruct { } |
つまり構造体の継承が出来ないとは、プロトコルの成約を受ける構造体は作成可能だが、構造体を基底クラスや、派生クラスにすることは出来ないという事のようです。
3.列挙型
列挙型からの継承は出来ない。
1 2 |
public enum TestEnum2: TestEnum { } |
リテラルな型からの継承?は可能。(記述的には継承っぽいけど、これを継承と呼ぶのか・・?)
1 2 3 |
public enum TestEnum3: Int { case HOGE = 0 } |
クラスからの継承?も可能。(ちょっと強引だけど)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class EnumValueClass : IntegerLiteralConvertible, Equatable { var value: Int = 0 required public init(integerLiteral value: Int) { self.value = value } } public func ==(lhs: EnumValueClass, rhs: EnumValueClass) -> Bool { return lhs.value == rhs.value } public enum TestEnum4: EnumValueClass { case HOGE = 1 } |
構造体からの継承も可能。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public struct EnumValueStruct : IntegerLiteralConvertible, Equatable { var value: Int = 0 public init(integerLiteral value: Int) { self.value = value } } public func ==(lhs: EnumValueStruct, rhs: EnumValueStruct) -> Bool { return lhs.value == rhs.value } public enum TestEnum5: EnumValueStruct { case HOGE = 1 } |
因みに、以下のいずれかのプロトコルとEquatableを継承することで、enumで使用することが出来ます。
整数値 : IntegerLiteralConvertible
浮動小数点 : FloatingPointLiteralConvertible
文字列 : StringLiteralConvertible
真偽値 : BooleanLiteralConvertible
プロトコルの継承は可能
1 2 3 4 5 6 |
public protocol hoge2 { } public enum TestEnum6: hoge2 { case HOGE } |
ここまで見た感じだと列挙型の定義は、プロトコルやリテラルな型を継承可能であり、構造体に近い定義のようです。
2.終了処理(deinit)が出来ない。
クラスは当然定義可能
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class TestClass { public var hoge: Int = 0 public let HOGE: Int = 0 func hogehoge() -> Int { return hoge + HOGE } init() { println("init") } deinit { println("deinit") } } |
構造体は当然エラーになる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public struct TestStruct { public var hoge: Int = 0 public let HOGE: Int = 0 func hogehoge() -> Int { return hoge + HOGE } init() { println("init") } // コード記述時にエラーになる。 // deinit { // println("deinit") // } } |
列挙型は・・・・?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public enum TestEnum: Int { // private var hoge: Int = 0 // コード記述時にエラーになる。 // let HOGE: Int = 0 // コード記述時にエラーになる。 case HOGE = 0 case HOGE2 = 1 func hogehoge() -> Int { return self.rawValue } public init?(rawValue: Int) { println("init") switch rawValue { case 0: self = .HOGE default: return nil } } // コード記述時にエラーになる。 // deinit { // println("deinit") // } } |
初期化は出来るが、終了処理は出来ない。構造体に似た動きをする事がわかる。
3.構造体で定義した値は値渡しになり、クラスで定義した値は参照渡しになる。
クラスは参照渡しになる。
1 2 3 4 5 6 7 8 |
var tc: TestClass = TestClass() tc.hoge = 10 funcTestClass(tc) println("class:\(tc.hoge)") // class:20 func funcTestClass(val: TestClass) { val.hoge = 20 } |
構造体は、書き換える記述時にエラーになる。ただし、inoutを使用すれば、値の書き換えは可能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var ts: TestStruct = TestStruct() ts.hoge = 10 funcTestStruct2(&ts) println("struct:\(ts.hoge)") // func funcTestStruct(val: TestStruct) { // val.hoge = 10 // コード記述時にエラーになる。 // // } func funcTestStruct2(inout val: TestStruct) { val.hoge = 20 } |
列挙型は、内部に書き換える構造が見つからないので、検証不可能…(出来る方法あるなら教えてください)
ということは、初期値で与えられた値以外の値は持てないと言うことだと思います。
4.型のキャストが出来ない
クラスは、継承関係があるから当然出来る。
1 2 |
var tc2: TestClass2 = TestClass2() var tc1: TestClass = tc2 |
構造体はそもそも継承が出来ないので、キャストが出来ないが、プロトコルへのキャスト可能。
1 2 |
var ts2: TestStruct3 = TestStruct3() var tsp: hoge = ts2 |
列挙型は、直接キャストは出来ないが、rawValueで継承元の値に変換可能。
1 2 |
var te3: TestEnum3 = TestEnum3.HOGE var tei: Int = te3.rawValue |
ここまで見てくると、当初考えていた列挙型は新しい型を作ると言うよりは、構造体の仕組みを利用した既存のリテラルの拡張表現の様な気がしてきました。
その他機能
列挙型に値を持たせる事が可能ですと、書くと何のことかわかりにくいと思いますでの、例を紹介します。
1 2 3 4 5 6 7 8 9 10 11 |
public enum TestEnum7 { case HOGE(var1: String, var2: String) } // 使用例 var te7: TestEnum7 = TestEnum7.HOGE(var1: "ほげ1", var2: "ほげ2") switch te7 { case let .HOGE(var1, var2): println( " \(var1) : \(var2)") } |
と、ジェネリック型の様な使い方出来ます。
まとめ
最後まで読んで頂き、ありがとうございました。
僕の結論としては、
Swiftにおける列挙型は、リテラル型を拡張し、初期値で与えられた一意な値を持つものにするための宣言である。
皆さんはどう思いました?
Swiftの列挙型は結構色んな事が出来るので、色々やりたくなりますが、やり過ぎるとソースの可読性が一気に下がりそうです。少なくても直感的には理解出来ないと思います。
仕事でプログラミングする場合は、可読性も大切な要素のうちの1つなので、コーディング規約でこの辺をしっかり定義しないと、チームとしてのパフォーマンスが下がると思います。
Swift 言語は、言語的には綺麗じゃないけど、おもしろいですね。
【Swiftに関連する記事】
・SwiftでECSlidingViewControllerを使ったドロワーメニューを実装してみた。
・UITableView のセルに配置された UIDatePicker の表示・非表示切り替えを行う。〜 Swiftの話 〜
・SwiftでStoryboardを使った簡単な画面遷移(メモ的なもの)