swiftでenumのstateパターン(contextをAssociatedValueで扱う版)

[swiftでenumでstateパターン](https://qiita.com/hikaruna/items/7d2e919be0bc93bf3f45)で紹介したものアレンジ版。
あっちはいちいち引数でcontextを渡す必要があったが、こっちは内部で持つようになったので、いちいち渡す必要はなくなった。
よりオブジェクト指向的だ。
あえて改良版とは言わないのは、こっちのほうがイケていると完全には思っていないからだ。デメリットも多い。ミスるとメモリリークする。

```swift:Mario.swift
class Mario {
    
    var description: String {
        return "\(state.description)マリオ\(isDead ? "の死体" : "")"
    }
    
    private(set) var isDead = false {
        willSet {
            if newValue {
                print("\(description)は死ぬ")
            }
        }
    }
    
    private var state: State! {
        willSet { print("\(description) -> ", terminator: "") }
        didSet { print("\(description)") }
    }
    
    init() {
        self.state = .normal(State.Context(self))
        print("\(description)が初期化された")
    }
    
    deinit {
        print("\(description)は開放された")
    }
    
    func onHit() {
        print("\(description)が敵に当たった")
        state.onHit()
    }
    
    enum State: MarioStateImplement {
        case normal(State.Context)
        case small(State.Context)
        
        var description: String { return delegate.description }
        
        /// 状態の実装の移譲という意味でのデリゲート
        /// UIKitで使われる狭義のデリゲートのことではない
        var delegate: MarioStateImplement {
            switch self {
            case .normal(let context):
                return Normal.shared(context)
            case .small(let context):
                return Small.shared(context)
            }
        }
        
        func onHit() {
            delegate.onHit()
        }
        
        /// MarioStateImpelementが動作するときのContext
        /// コイツには2つの役割がある
        /// 1.
        /// MarioStateImpelementがMarioのprivateなAPIにアクセスするための橋渡し
        /// 2.
        /// MarioStateImpelementがMarioを直接参照しないための緩衝材
        /// enumのAssociatedValuesは強参照であり、
        /// 仮にNormal.sharedなどがassociateValuesでMarioを直接参照すると
        /// 循環参照が発生してしまいメモリリークする。
        /// そうならないようにコイツが間に入り、Marioをweak参照させる
        class Context {
            var state: Mario.State {
                get { return mario.state }
                set { mario.state = newValue }
            }
            var isDead: Bool {
                get { return mario.isDead }
                set { mario.isDead = newValue }
            }
            
            private unowned let mario: Mario
            
            init(_ mario: Mario) {
                self.mario = mario
            }
        }
    }
}
```

```swift:MarioStateNormal.swift
extension Mario.State {
    enum Normal: MarioStateImplement {
        case shared(Context)
        
        var description: String { return "スーパー" }
        
        // AssociatedValuesを参照するためのもの
        // 強引な感じが否めない
        private var context: Context {
            switch self {
            case .shared(let context):
                return context
            }
        }
        
        func onHit() {
            context.state = .small(context)
        }
    }
}
```

```swift:MarioStateSmall.swift
extension Mario.State {
    enum Small: MarioStateImplement {
        case shared(Context)
        
        var description: String { return "チビ" }
        
        private var context: Context {
            switch self {
            case .shared(let context):
                return context
            }
        }
        
        func onHit() {
            context.isDead = true
        }
    }
}
```

```swift:MarioStateImplement.swift
protocol MarioStateImplement {
    var description: String { get }
    func onHit()
}
```

```swift:Main.swift
do {
    print("Game start")
    
    let mario = Mario()
    while !mario.isDead {
        mario.onHit()
    }
    
    print("Game over")
}
```

↓

```console:実行結果
Game start
スーパーマリオが初期化された
スーパーマリオが敵に当たった
スーパーマリオ -> チビマリオ
チビマリオが敵に当たった
チビマリオは死ぬ
Game over
チビマリオの死体は開放された
```


## メリット
- 引数を渡さなくなったので、onHitの引数が0担って楽になったね！
- 例えば```
protocol MarioStatefulBehavior {
  func onHit()
}
```をのようなprotocolを作りたくなったとして、MarioもStateもNormalもSmallもそれをImplementsできる

## デメリット
- Marioがメモリリークしないように気をつけなければならない
    - Mario#stateを暗黙アンラップしなくてはいけない
- Normal#contextの実装がイケてない

##　ちなみに
### 可視性を下げたいのと別ファイルで書きたいのは両立しない
今回の例だと、Mario.State.ContextをMario.swiftで定義しなくてはならない。そうしないと、```private var Mario#state ``` にアクセスできないのだ。
これは、AssociatedValueを使ったかどうかとは関係ない(前回の例ではそのへんは諦めてprivateを使ってないだけ)。
当該scope の extensionの内であれば、privateにもアクセスさせてほしいと感じるのは私だけだろうか。

### PlaygroundでCustomStringConvertible使うと死ぬのナンデ？
以下、推測

- 必ず死ぬわけじゃない
- Marioのdescriptionが動作するためにはstateの初期化が必要
- stateの初期化にはNormal初期化が必要
- Normalの初期化にはContextの初期化が必要
- Contextの初期化にはMarioの初期化(の内、全properyの初期化)が必要
- playgroundはMarioのinitの内、全propertyの初期化が済んだ時点ですぐにdescriptionを使う(推測)
- 全propertyの初期化に、暗黙アンラップ型のstateは含まれていない
- 死ぬ