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


アーキテクチャの設計

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

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

設計方針

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

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

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

こんな感じ。

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

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

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

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

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

CbObject class(基底クラス)

/// 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(派生コンテナ・クラス)

/// 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 にすると、イベント・ハンドラは呼ばれなくなる。

使用方法

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【シーケンスの設計】