【入門】iOS アプリ開発　#4【アーキテクチャの設計】

# アーキテクチャの設計

今回はパックマンのゲームを構築するにあたり、全体のアーキテクチャを設計する。

仕様書から画面モードは４つあり、それぞれのモード内にはキャラクタがいて移動処理などがある。これらを１つ１つのオブジェクトとして管理し扱っていく。

![Image2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678332/2a570726-a266-ca09-2a2e-ab180a14be1b.png)

# 設計方針

各画面モードをオブジェクトとして扱い、簡単に切り替え操作したい。

```swift
attractMode.startSequence()
// ・・・アトラクトモード実行中・・・

// アトラクトモードからクレジットモードへ切り替える
attractMode.stopSequence()
creditMode.startSequence()
// ・・・クレジットモード実行中・・・
```
こんな感じ。

またイベント・メッセージを画面モード内のオブジェクトにも簡単に通知したい。

```swift
attractMode.sendEvent(message: .Update, parameter: [16])
attractMode.sendEvent(message: .Touch, parameter: [x,y])
```

イベント・メッセージは表示更新タイミング(Update)やタッチイベントなどで、画面モードが無効のものには通知しないようにする。

まとめると、以下のようなオブジェクトの階層構造が作れるようにする。

![Image4.png]
(https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678332/69e5f872-eea9-d0b3-9f27-f18ab6a4d332.png)

これらの基本コンポーネントのクラスを作成する。

# CbObject class（基底クラス）

```swift
/// Based object class
class CbObject {

    /// Kind of Message ID to handled events in object.
    enum EnMessage: Int {
        case None
        case Update
        case Timer
        case Touch
        case Swipe
        case Accel
    }
    
    /// When it is true, object can handle events.
    var enabled: Bool = true

    /// For accessing parent objects.
    private var parent: CbObject?
    
    /// Initialize self without parent object
    init() {
        parent = nil
    }
    
    /// Initialize self with parent object
    /// - Parameter object: Parent object
    init(binding object: CbObject) {
        parent = object
        parent?.bind(self)
    }
    
    /// Bind self to a specified object
    /// - Parameter object: Object to bind self.
    func bind( _ object: CbObject) {
        // TO DO: override
        // (This is pure virtual method.)
    }

    // Send event messages
    /// - Parameters:
    ///   - id: Message ID
    ///   - values: Parameters of message.
    func sendEvent(message id: EnMessage, parameter values: [Int]) {
        receiveEvent(sender: self, message: id, parameter: values)
    }
    
    /// Handler called by sendEvent method to receive events
    /// - Parameters:
    ///   - sender: Message sender
    ///   - id: Message ID
    ///   - values: Parameters of message
    func receiveEvent(sender: CbObject, message: EnMessage, parameter values: [Int]) {
        guard enabled else { return }
        if message == .Update {
            update(interval: values[0])
        } else {
            handleEvent(sender: sender, message: message, parameter: values)
        }
    }
    
    /// Event handler
    /// - Parameters:
    ///   - sender: Message sender
    ///   - id: Message ID
    ///   - values: Parameters of message
    func handleEvent(sender: CbObject, message: EnMessage, parameter values: [Int]) {
        // TO DO: override
        // (This is pure virtual method.)
    }

    /// Update handler
    /// - Parameter interval: Interval time(ms) to update.
    func update(interval: Int) {
        // TO DO: override
        // (This is pure virtual method.)
    }

}
```

基底クラスの CbObject は、init で上位のオブジェクトとの関連付けを行える。sendEventメソッドでメッセージを送信でき、メッセージが、.Update なら、updateハンドラが呼ばれ、それ以外は、handleEvent が呼ばれる。ハンドラは派生クラスでオーバーライドして扱う。プロパティの enabled を false にすると、ハンドラは呼ばれなくなる。

# CbContainer class（派生コンテナ・クラス）

```swift
/// Container class that bind objects.
class CbContainer : CbObject {
    
    private var objects: [CbObject] = []

    /// Bind self to a specified object
    /// - Parameter object: Object to bind self.
    override func bind( _ object: CbObject) {
        objects.append(object)
    }
    
    /// Handler called by sendEvent method to receive events
    /// It sends messages to all contained object.
    /// - Parameters:
    ///   - sender: Message sender
    ///   - id: Message ID
    ///   - values: Parameters of message
    override func receiveEvent(sender: CbObject, message: EnMessage, parameter values: [Int]) {
        guard enabled else { return }

        super.receiveEvent(sender: sender, message: message, parameter: values)

        for t in objects {
            t.receiveEvent(sender: self, message: message, parameter: values)
        }
    }
}
```

派生コンテナ・クラスの CbContainer は、sendEventメソッドで受け取ったメッセージを、関連付けしている全てのオブジェクトに送信する。同様にプロパティの enabled を false にすると、イベント・ハンドラは呼ばれなくなる。

# 使用方法

```swift
class MyObject: CbObject {
    override func update(interval: Int) {
        print("Update!")
    }
}
    
func test() {
    root  = CbContainer()
    node1 = CbContainer(binding: root)
    node2 = MyObject(binding: node1)
        
    root.sendEvent(message: .Update, parameter: [16])
}
```
root に送ったメッセージが末端の node2 のオブジェクトに届くようになる。

# まとめ

全体のオブジェクトの管理、イベント・メッセージの仕組みを設計した。

高々100行程度のコードだが、最初にこれらを決めておかないと、後々、苦労しそうな気がする。

次は、CbContainerクラスを継承して画面モード・クラスを作成していく。

# 次の記事
[【入門】iOS アプリ開発　#5【シーケンスの設計】]
(https://qiita.com/KIKU_CHU/items/0448daadb09ff581d483)