自定义导航栈类骨架:接口定义、泛型支持、线程安全基础
导航栈是路由框架的核心运行时容器,负责以栈结构管理页面实例的生命周期、转场顺序及历史回退。一个健壮的自定义导航栈类需要明确三件事:接口契约(提供哪些操作)、类型通用性(如何支持任意页面类型)、线程安全(多线程环境下操作的原子性与可见性)。本章围绕这三个维度构建导航栈骨架,为后续的 push/pop 动画、嵌套栈、状态保存打下基础。
3.1 导航栈接口定义
导航栈对外暴露的操作应覆盖页面入栈、出栈、查看栈顶、栈大小、空判断、清空等基础行为。下面是一份最小完备接口清单:
| 方法 | 签名示意 | 说明 |
|---|---|---|
push | void push(T page) | 将页面压入栈顶,触发对应生命周期(如页面出现) |
pop | std::optional<T> pop() | 弹出栈顶页面并返回,若栈为空则返回空值(或抛出异常) |
peek | const T& peek() const | 返回栈顶引用,不弹出;调用前需确保栈非空 |
size | size_t size() const | 返回当前栈内页面数量 |
isEmpty | bool isEmpty() const | 判断栈是否为空 |
clear | void 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>),从而自动管理页面生命周期。
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 总体骨架结构
下图展示了导航栈类的完整结构关系,包含接口方法、内部容器、互斥锁和条件变量:
- 接口设计遵循最小原则:push / pop / peek / size / isEmpty / clear,后续按需扩展。
- 泛型参数
T解耦页面类型,支持智能指针以自动管理生命周期。 - 互斥锁保护所有公共方法,条件变量用于阻塞式等待(如手势驱动的异步 pop)。
mutable关键字让 const 方法也能加锁,保持接口语义正确。
这一骨架可以直接编译运行,作为导航栈的底层容器。下一阶段将围绕它实现 push/pop 的动画机制、转场状态管理以及页面生命周期回调——这些内容将在第 4 章中详细展开。