07/09/2018, 15:37

Ứng dụng State Machine trong Swift

State Machine là một kỹ thuật không mới trong lập trình nói chung và rất hữu dụng để giải quyết các bài toán về trạng thái. Trước đây đã có 1 bài viết của @huydx trong series hard-core nói về State Machine khá chi tiết. Lần này mình sẽ giới thiệu một ứng dụng trong lập trình IOS (Swift) khi ứng ...

State Machine là một kỹ thuật không mới trong lập trình nói chung và rất hữu dụng để giải quyết các bài toán về trạng thái. Trước đây đã có 1 bài viết của @huydx trong series hard-core nói về State Machine khá chi tiết. Lần này mình sẽ giới thiệu một ứng dụng trong lập trình IOS (Swift) khi ứng dụng có biểu đồ di chuyển phức tạp.

State Machine là gì

State Machine là ... một cái máy tính đơn giản, bao gồm một tập nhiều trạng thái (state) và các di chuyển giữa các trạng thái đó với nhau. Mỗi khi có 1 sự kiện (event) xảy ra, cái máy của chúng ta sẽ chuyển từ trạng thái này sang trạng thái khác.

Ở hình bên trên trạng thái nguyên thuỷ là State A.Với sự kiện T1 diễn ra, trạng thái State A sẽ di chuyển đến trạng thái State B v.v...

Vậy nếu liên tưởng mỗi màn hình trong ứng dụng là 1 trạng thái, và sự kiện để chuyển màn hình là input thì chúng ta sẽ có 1 State Machine để quản lý toàn bộ quá trình di chuyển màn hình.

    |--------------------|      |--------------------|      |--------------------|      |--------------------|
    |                    |      |                    |      |                    |      |                    |
    |                    |      |                    |      |                    |      |                    |
    |                    |      |                    |      |                    |      |                    |
    |                    |      |                    |      |                    |      |                    |
    |      initial       | -->  |      greeting      | -->  |       login        | -->  |       home         |
    |                    |      |                    |      |                    |      |                    |
    |                    |      |                    |      |                    |      |                    |
    |                    |      |                    |      |                    |      |                    |
    |                    |      |                    |      |                    |      |                    |
    |--------------------|      |--------------------|      |--------------------|      |--------------------|

                          launch                       start                     loggedIn

Điều này có lợi gì so với cách làm thông thường ?

Khi số lượng màn hình của 1 tính năng (từ đây sẽ gọi là 1 flow) tăng đến tầm 20~30 màn hình, chúng ta thường sẽ không muốn cho tất cả vào trong 1 storyboard mà chia làm các storyboard riêng biệt rồi kết nối lại với nhau bằng code. Các đoạn kết nối nói trên sẽ bị chia lẻ tẻ vào nhiều file source khác nhau, khó quản lý và điều chỉnh khi có thay đổi về tính năng.

Dùng State Machine sẽ quản lý được toàn bộ các dữ kiện lưu chuyển của flow bằng một machine duy nhất.

Implement State Machine

Trước hết là implement phiên bản đơn giản cho một State Machine. Ở đây mình dùng một từ điển 2 tầng routes để lưu tham chiếu từ một trạng thái, 1 sự kiện để dẫn đến 1 trạng thái mới.

import Foundation

protocol StateType: Hashable {}
protocol EventType: Hashable {}

struct Path<S: StateType, E:EventType> {
    let from: S
    let to: S
    let by: E
}

class StateMachine<S: StateType, E: EventType> {
    var currentState: S
    var routes: [S: [E: S]] = [:]

    init(_ initState: S, _ config: (StateMachine) -> Void) {
        currentState = initState
        config(self)
    }

    func addPath(_ p: Path<S, E>) {
        var dict = routes[p.from] ?? [:]
        dict[p.by] = p.to
        routes[p.from] = dict
    }

    func transition(from: S, by: E) -> Bool {
        let next = routes[from].flatMap { $0[by] }
        guard let existed = next else { return false }
        currentState = existed
        return true
    }
}

Tạo ra 1 State Machine cụ thể

Đến bây giờ mình có thể định nghĩa tất cả các trạng thái và sự kiện thành kiểu enum, mà chỉ cần thoả mãn StateType và EventType bên trên:

enum ScreenState: StateType {
    case initial
    case greeting
    case login
    case home
}

enum MovingEvent: EventType {
    case launch
    case start
    case loggedIn
}

Và cùng với đó là quản lý đồ thị di chuyển tập trung tại 1 chỗ

let graph: [Path<ScreenState, MovingEvent>] = [
    Path(from: .initial, to: .greeting, by: .launch),
    Path(from: .greeting, to: .login, by: .start),
    Path(from: .login, to: .home, by: .loggedIn)
]

class StateMachineManager {
    static let machine = StateMachine<ScreenState, MovingEvent>(.initial, { machine in
        for p in graph {
            machine.addPath(p)
        }
    })
}

Nối các màn hình vào vào các trạng thái tương ứng

Để sử dụng State Machine ở trên, mình sẽ tạo 1 protocol chuyên biệt để tiện sử dụng cho các ViewController mong muốn

 protocol StatefulViewController {
    var state: State { get }
    func moveNext(event: Event) -> UIViewController?
}

extension StatefulViewController {
    func moveNext(event: Event) -> UIViewController? {
        let machine = StateMachineManager.machine
        guard machine.transition(from: state, by: event) else { return nil }

        switch machine.currentState {
        case .initial: // Return correcsponding View Controller
        case .greeting: // Return correcsponding View Controller
        case .login: // Return correcsponding View Controller
        case .home: // Return correcsponding View Controller
        }
    }
}

Xong! Đơn giản hơn mọi người vẫn nghĩ phải không nào :smile:

0