3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Swiftで特定の複数の型のみを許容するArray

3
Last updated at Posted at 2019-04-11

1つの型を許す配列はもちろん初心者でもすぐ出来ると思います。
では例えば、 IntDouble のみを許す数値の配列を用意したい場合、どうすればいいでしょう。
手段と長短をまとめました。

手段

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

他にもなにかありましたら教えてください!

3
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?