自定义导航栈类骨架:接口定义、泛型支持、线程安全基础

导航栈是路由框架的核心运行时容器,负责以栈结构管理页面实例的生命周期、转场顺序及历史回退。一个健壮的自定义导航栈类需要明确三件事:接口契约(提供哪些操作)、类型通用性(如何支持任意页面类型)、线程安全(多线程环境下操作的原子性与可见性)。本章围绕这三个维度构建导航栈骨架,为后续的 push/pop 动画、嵌套栈、状态保存打下基础。

3.1 导航栈接口定义

导航栈对外暴露的操作应覆盖页面入栈、出栈、查看栈顶、栈大小、空判断、清空等基础行为。下面是一份最小完备接口清单:

方法签名示意说明
pushvoid push(T page)将页面压入栈顶,触发对应生命周期(如页面出现)
popstd::optional<T> pop()弹出栈顶页面并返回,若栈为空则返回空值(或抛出异常)
peekconst T& peek() const返回栈顶引用,不弹出;调用前需确保栈非空
sizesize_t size() const返回当前栈内页面数量
isEmptybool isEmpty() const判断栈是否为空
clearvoid clear()清空整个栈,并释放所有页面资源

在移动端场景下通常还会额外提供 popToRoot(弹出到根)、replace(替换栈顶)、insertAtIndex 等方法,这里暂不作为骨架核心,后续章节逐步扩展。

3.2 泛型支持

为了让导航栈能够容纳任意类型的页面(UIViewController、Fragment、Widget、甚至自定义 Page 对象),必须使用泛型(C++ 模板 / Java 泛型 / Dart 泛型)。骨架以 C++ 模板为例:

template <typename T>
class NavigationStack {
public:
    // 构造函数与析构
    NavigationStack() = default;
    ~NavigationStack();

    // 禁止拷贝,允许移动
    NavigationStack(const NavigationStack&) = delete;
    NavigationStack& operator=(const NavigationStack&) = delete;
    NavigationStack(NavigationStack&&) noexcept = default;
    NavigationStack& operator=(NavigationStack&&) noexcept = default;

    // 核心接口
    void push(T page);
    std::optional<T> pop();
    const T& peek() const;
    size_t size() const noexcept;
    bool isEmpty() const noexcept;
    void clear();

private:
    std::deque<T> container_;
    // ... 线程安全相关成员
};

使用 std::deque 而非 std::stack 是为了支持未来可能的中间插入、替换操作,同时保持双端操作性能。模板参数 T 可以是智能指针(如 std::shared_ptr<Page>),从而自动管理页面生命周期。

设计提示:如果项目使用 Java 或 Kotlin,泛型写法类似 class NavigationStack<T>,内部使用 ArrayDeque<T> 或 ArrayList<T>;Swift 中使用泛型结构体 struct NavigationStack<T>,内部用 Array<T>。关键思想一致:将类型决策延迟到实例化点。

3.3 线程安全基础

导航栈可能被多个线程同时访问:主线程 push/pop 页面,后台线程可能执行预加载、状态保存、手势驱动等操作。如果不加保护,栈容器将面临数据竞争(data race)、迭代器失效、崩溃等风险。以下是被多数生产级导航栈采用的线程安全策略:

  • 互斥锁(Mutex):对容器操作加锁,保证同一时刻只有一个线程访问共享数据。
  • RAII 锁(Lock Guard):使用 std::lock_guard 或 std::unique_lock 自动管理锁生命周期,避免异常安全漏洞。
  • 条件变量(Condition Variable):用于实现阻塞式操作,如“等待栈不为空后弹出”。
  • 读写锁:若 peek/size 等只读操作频繁,可考虑使用 std::shared_mutex 提升并发度。

下面是将互斥锁集成到栈类中的骨架:

#include <mutex>
#include <condition_variable>

template <typename T>
class NavigationStack {
public:
    void push(T page) {
        std::lock_guard<std::mutex> lock(mutex_);
        container_.push_back(std::move(page));
        cv_.notify_one(); // 通知等待的线程
    }

    std::optional<T> pop() {
        std::unique_lock<std::mutex> lock(mutex_);
        if (container_.empty()) {
            return std::nullopt;
        }
        T top = std::move(container_.back());
        container_.pop_back();
        return top;
    }

    const T& peek() const {
        std::lock_guard<std::mutex> lock(mutex_);
        if (container_.empty()) {
            throw std::out_of_range("NavigationStack is empty");
        }
        return container_.back();
    }

    size_t size() const noexcept {
        std::lock_guard<std::mutex> lock(mutex_);
        return container_.size();
    }

    bool isEmpty() const noexcept {
        std::lock_guard<std::mutex> lock(mutex_);
        return container_.empty();
    }

    void clear() {
        std::lock_guard<std::mutex> lock(mutex_);
        container_.clear();
    }

private:
    std::deque<T> container_;
    mutable std::mutex mutex_;              // mutable 允许 const 方法加锁
    std::condition_variable cv_;
};
注意:peek 返回引用,但锁在函数结束时释放,调用方继续持有引用可能导致悬空。实际生产代码中,建议返回智能指针或拷贝,或在内部使用读写锁并明确引用有效期协议。这里作为骨架演示仅展示加锁模式,后续章节会提供更安全的方案。

3.4 总体骨架结构

下图展示了导航栈类的完整结构关系,包含接口方法、内部容器、互斥锁和条件变量:

NavigationStack 类骨架结构图 NavigationStack<T> 骨架结构 NavigationStack<T> T (泛型) 私有成员 - container_: deque<T> - mutex_: mutable mutex - cv_: condition_variable 公共接口 + push(T) + pop(): optional<T> + peek(): const T& + size(): size_t + isEmpty(): bool + clear(): void 骨架提供了泛型与线程安全最小实现,后续章节将添加动画、手势、嵌套栈等能力
关键要点:
  • 接口设计遵循最小原则:push / pop / peek / size / isEmpty / clear,后续按需扩展。
  • 泛型参数 T 解耦页面类型,支持智能指针以自动管理生命周期。
  • 互斥锁保护所有公共方法,条件变量用于阻塞式等待(如手势驱动的异步 pop)。
  • mutable 关键字让 const 方法也能加锁,保持接口语义正确。

这一骨架可以直接编译运行,作为导航栈的底层容器。下一阶段将围绕它实现 push/pop 的动画机制、转场状态管理以及页面生命周期回调——这些内容将在第 4 章中详细展开。