1つの型を許す配列はもちろん初心者でもすぐ出来ると思います。
では例えば、 Int と Double のみを許す数値の配列を用意したい場合、どうすればいいでしょう。
手段と長短をまとめました。
手段
Enum を利用
enum EnumNumber {
case int(Int)
case double(Double)
}
var enumNumbers: [EnumNumber] = [.int(10), .double(2.0)]
Protocol を利用
protocol ProtocolNumber {}
extension Int: ProtocolNumber {}
extension Double: ProtocolNumber {}
var protocolNumbers: [ProtocolNumber] = [Int(10), Double(2.0)]
長所と短所
switch で値を取り出す
Protocol を使用した場合だと、全パターンを書いたとしても default 行を書かなくてはならないので、少し安全ではないコードになってしまうかも。
// Enum
switch enumNumbers.first! {
case .int(let value): print(value)
case .double(let value): print(value)
}
// Protocol
switch protocolNumbers.first! {
case let value as Int: print(value)
case let value as Double: print(value)
default: print("exception")
}
型が分かっている物の値を取得
Protocol だとキャストするだけで値が取れる( optional もしくは forced unwrap ですが )のに対し、
Enum だとおそらく if case 文を書かないといけないのではと思います。となるとインデントが深くなってしまいます。
インデントが深くなるのを回避するために パターン2 のように computed property を生やす手がありますが、もしこうするならば型の数だけ用意しなければなりません。
// Enum パターン1
if case .int(let value) = enumNumbers[0] {
let enumInt = value
}
// Enum パターン2
extension EnumNumber {
var integerValue: Int? {
if case .int(let value) = enumNumbers[0] {
return value
}
return nil
}
}
let enumInt = enumNumbers[0].integerValue
// Protocol
let protocolInt = enumNumbers[0] as? Int
生成する時
大きな違いは、 Protocol の場合は Int か Double か分かる必要が無い という点です。
Enum の場合は Int か Double か把握した上で Enum を生成 する必要があります。
明らかに Enum のほうが面倒くさくなると思います。
let unknownValue: Any = Int(123)
// Enum パターン1
// このパターンだと、unknownValue が Double の場合、追加されない
if let intValue = unknownValue as? Int {
enumNumbers.append(.int(intValue))
}
// Enum パターン2
extension EnumNumber {
init?(_ value: Any) {
switch value {
case let value as Int:
self = .int(value)
case let value as Double:
self = .double(value)
default:
return nil
}
}
}
if let value = EnumNumber(unknownValue) {
enumNumbers.append(value)
}
// Protocol
if let value = unknownValue as? ProtocolNumber {
protocolNumbers.append(value)
}
ネストしたい
Swift の設計上 Protocol はネストできません。
class Sample {
// OK
enum EnumNumber {}
// error: protocol 'ProtocolNumber' cannot be nested inside another declaration
protocol ProtocolNumber {}
}
まとめ
主観で ○ × つけました。
| Enum | Protocol | |
|---|---|---|
| switch で値を取り出す | ○ | × |
| 型が分かっている物の値を取得 | × | ○ |
| 生成する時 | × | ○ |
| ネストしたい | ○ | × |
まあこんな色々書きましたが、基本的には Protocol を使うべきですね笑
追記: 折衷案
@herara_ofnir3 さんにコメントをいただきました!
上記の表で見ても良いとこ取りできてます。
enum EnumNumber {
case int(Int)
case double(Double)
}
protocol EnumNumberConvertible {
var enumNumber: EnumNumber { get }
}
extension Int : EnumNumberConvertible {
var enumNumber: EnumNumber { return .int(self) }
}
extension Double : EnumNumberConvertible {
var enumNumber: EnumNumber { return .double(self) }
}
// 配列を用意
var numbers: [EnumNumberConvertible] = [10, 0.2]
// 1. switch で値取り出し
for number in numbers {
switch number.enumNumber {
case .int(let value): print(value)
case .double(let value): print(value)
}
}
// 2. 型が分かっている物の値を取得
let value = numbers[0] as? Int
// 3. 生成する時
let unknownValue: Any = Int(123)
if let value = unknownValue as? EnumNumberConvertible {
numbers.append(value)
}
参考
環境
- Swift 5.0
他にもなにかありましたら教えてください!