
网罗开发 (小红书、快手、视频号同名)
大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验 。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索"展菲",即可纵览我在各大平台的知识足迹。
📣 公众号"Swift社区",每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友"fzhanfei",与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
文章目录
-
- 前言
- 异步和生命周期的通病
-
- [Flutter 中的典型场景](#Flutter 中的典型场景)
- [前端 React 中的类似问题](#前端 React 中的类似问题)
- [iOS 中的类似问题](#iOS 中的类似问题)
- [Flutter 的 mounted 检查机制](#Flutter 的 mounted 检查机制)
-
- [mounted 属性的作用](#mounted 属性的作用)
- [mounted 检查的最佳实践](#mounted 检查的最佳实践)
- [mounted 的局限性](#mounted 的局限性)
- [React Native 的 isMounted 检查](#React Native 的 isMounted 检查)
-
- [React Native 的 isMounted 问题](#React Native 的 isMounted 问题)
- [React 18 的 StrictMode 双重渲染问题](#React 18 的 StrictMode 双重渲染问题)
- [为什么 iOS 原生更安全](#为什么 iOS 原生更安全)
-
- [Swift 的结构化并发](#Swift 的结构化并发)
- [Task 的取消机制](#Task 的取消机制)
- [@MainActor 的线程安全保证](#@MainActor 的线程安全保证)
- 强类型系统的帮助
- 一套跨端通用的异步防御模式
- 实际场景中的应用
- 总结
- [完整可运行 Demo 代码](#完整可运行 Demo 代码)
前言
最近在做一个 Flutter 项目的时候,遇到了一个经典的异步问题:页面销毁后还在调用 setState,导致应用崩溃。这个问题让我想起了前端开发中经常遇到的 Promise race condition,还有 iOS 开发中的异步任务管理。仔细一想,这三个平台的异步问题其实高度相似,都是异步操作和生命周期管理之间的矛盾。
今天我们就来聊聊这三个平台的异步问题,看看它们为什么这么相似,以及如何用一套通用的防御模式来解决这些问题。
异步和生命周期的通病
不管是 Flutter、前端还是 iOS,异步操作都有一个共同的特点:它们不会立即完成,而是在未来的某个时间点才返回结果。这就带来了一个问题:当异步操作还在进行中的时候,用户可能已经离开了当前页面,或者组件已经被销毁了。
Flutter 中的典型场景
在 Flutter 中,最常见的场景是这样的:
dart
class UserProfilePage extends StatefulWidget {
@override
_UserProfilePageState createState() => _UserProfilePageState();
}
class _UserProfilePageState extends State<UserProfilePage> {
String? userName;
@override
void initState() {
super.initState();
// 发起异步请求获取用户信息
fetchUserInfo();
}
Future<void> fetchUserInfo() async {
// 模拟网络请求
await Future.delayed(Duration(seconds: 2));
// 问题来了:如果用户在这 2 秒内离开了页面,这里调用 setState 就会报错
setState(() {
userName = "张三";
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("用户信息")),
body: Center(
child: Text(userName ?? "加载中..."),
),
);
}
}
这段代码看起来没什么问题,但实际上有一个严重的 bug:如果用户在 fetchUserInfo 还没完成的时候就离开了页面,setState 就会在已经销毁的 State 对象上调用,导致应用崩溃。
前端 React 中的类似问题
在前端开发中,这个问题同样存在:
javascript
import React, { useState, useEffect } from 'react';
function UserProfile() {
const [userName, setUserName] = useState(null);
useEffect(() => {
// 发起异步请求
fetchUserInfo();
}, []);
async function fetchUserInfo() {
// 模拟网络请求
await new Promise(resolve => setTimeout(resolve, 2000));
// 问题:如果组件已经卸载,这里调用 setState 就会报错
setUserName("张三");
}
return (
<div>
<h1>{userName || "加载中..."}</h1>
</div>
);
}
React 会警告你:"Can't perform a React state update on an unmounted component",但不会直接崩溃。不过这个问题在 React Native 中会更严重,因为 RN 的错误处理机制和 Web 不太一样。
iOS 中的类似问题
在 iOS 开发中,这个问题同样存在,但表现形式略有不同:
swift
class UserProfileViewController: UIViewController {
var userNameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
fetchUserInfo()
}
func fetchUserInfo() {
// 发起异步请求
Task {
// 模拟网络请求
try? await Task.sleep(for: .seconds(2))
// 问题:如果视图控制器已经被销毁,这里更新 UI 可能会有问题
userNameLabel.text = "张三"
}
}
}
虽然 Swift 的 async/await 机制相对更安全一些,但如果你不检查视图控制器的状态,同样可能出现问题。
Flutter 的 mounted 检查机制
Flutter 提供了一个 mounted 属性来检查 State 对象是否还在 widget 树中。这是 Flutter 的防御机制,但需要开发者手动检查。
mounted 属性的作用
mounted 是一个布尔值,表示当前的 State 对象是否还在 widget 树中。当 widget 被销毁时,mounted 会变成 false。
dart
class _UserProfilePageState extends State<UserProfilePage> {
String? userName;
@override
void initState() {
super.initState();
fetchUserInfo();
}
Future<void> fetchUserInfo() async {
await Future.delayed(Duration(seconds: 2));
// 关键:在调用 setState 之前检查 mounted
if (!mounted) {
return; // 如果已经销毁,直接返回
}
setState(() {
userName = "张三";
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("用户信息")),
body: Center(
child: Text(userName ?? "加载中..."),
),
);
}
}
这样就能避免在已销毁的 State 对象上调用 setState 了。
mounted 检查的最佳实践
在实际开发中,我们应该在每次异步操作完成后都检查 mounted:
dart
Future<void> fetchUserInfo() async {
try {
// 第一步:发起网络请求
final response = await http.get(Uri.parse('https://api.example.com/user'));
// 检查 mounted
if (!mounted) return;
// 第二步:解析数据
final data = json.decode(response.body);
// 再次检查 mounted(因为解析可能也需要时间)
if (!mounted) return;
// 第三步:更新状态
setState(() {
userName = data['name'];
});
} catch (e) {
// 错误处理也要检查 mounted
if (!mounted) return;
setState(() {
// 显示错误信息
});
}
}
mounted 的局限性
虽然 mounted 能解决大部分问题,但它也有一些局限性:
- 需要手动检查:每次异步操作后都要记得检查,容易遗漏
- 不能防止所有问题 :如果异步操作中还有其他副作用(比如更新全局状态、发送日志等),
mounted检查也帮不上忙 - 代码冗余 :每个异步方法都要写
if (!mounted) return;,代码会变得很冗长
React Native 的 isMounted 检查
React Native 也有类似的机制,但历史上有一些变化。
React Native 的 isMounted 问题
在早期的 React Native 版本中,有一个 isMounted 方法,但后来被移除了,因为 React 团队认为这个方法容易被误用。不过在实际开发中,我们仍然需要类似的检查机制。
javascript
import React, { useState, useEffect, useRef } from 'react';
function UserProfile() {
const [userName, setUserName] = useState(null);
const isMountedRef = useRef(true);
useEffect(() => {
// 组件挂载时设置为 true
isMountedRef.current = true;
fetchUserInfo();
// 组件卸载时设置为 false
return () => {
isMountedRef.current = false;
};
}, []);
async function fetchUserInfo() {
await new Promise(resolve => setTimeout(resolve, 2000));
// 检查组件是否还在挂载状态
if (!isMountedRef.current) {
return;
}
setUserName("张三");
}
return (
<div>
<h1>{userName || "加载中..."}</h1>
</div>
);
}
React 18 的 StrictMode 双重渲染问题
在 React 18 中,还有一个新问题:StrictMode 会在开发环境下双重渲染组件,这会导致异步请求被执行两次。这虽然不是生命周期问题,但也和异步操作有关。
javascript
useEffect(() => {
let cancelled = false;
async function fetchUserInfo() {
const response = await fetch('/api/user');
const data = await response.json();
// 检查是否被取消
if (!cancelled) {
setUserName(data.name);
}
}
fetchUserInfo();
// 清理函数
return () => {
cancelled = true;
};
}, []);
为什么 iOS 原生更安全
iOS 的异步机制相对来说更安全一些,主要有以下几个原因:
Swift 的结构化并发
Swift 5.5 引入的 async/await 和结构化并发机制,让异步代码更容易管理:
swift
class UserProfileViewController: UIViewController {
var userNameLabel: UILabel!
private var fetchTask: Task<Void, Never>?
override func viewDidLoad() {
super.viewDidLoad()
fetchUserInfo()
}
func fetchUserInfo() {
// 取消之前的任务(如果有)
fetchTask?.cancel()
// 创建新任务
fetchTask = Task { @MainActor in
do {
// 模拟网络请求
try await Task.sleep(for: .seconds(2))
// 检查任务是否被取消
try Task.checkCancellation()
// 更新 UI(@MainActor 确保在主线程)
userNameLabel.text = "张三"
} catch {
// 任务被取消或其他错误
print("任务被取消或出错: \(error)")
}
}
}
deinit {
// 视图控制器销毁时取消任务
fetchTask?.cancel()
}
}
Task 的取消机制
Swift 的 Task 提供了内置的取消机制,可以通过 Task.checkCancellation() 检查任务是否被取消:
swift
func fetchUserInfo() async throws {
// 第一步:发起网络请求
let (data, _) = try await URLSession.shared.data(from: url)
// 检查任务是否被取消
try Task.checkCancellation()
// 第二步:解析数据
let user = try JSONDecoder().decode(User.self, from: data)
// 再次检查任务是否被取消
try Task.checkCancellation()
// 第三步:更新 UI
await MainActor.run {
userNameLabel.text = user.name
}
}
@MainActor 的线程安全保证
Swift 的 @MainActor 注解可以确保代码在主线程执行,这对于 UI 更新非常重要:
swift
@MainActor
class UserProfileViewController: UIViewController {
// 所有属性和方法都在主线程执行
var userNameLabel: UILabel!
func updateUI() {
// 这里不需要手动切换到主线程
userNameLabel.text = "张三"
}
}
强类型系统的帮助
Swift 的强类型系统可以在编译时发现一些问题,比如类型不匹配、可选值未处理等,这可以减少运行时错误。
一套跨端通用的异步防御模式
虽然三个平台的机制不同,但我们可以总结出一套通用的防御模式,让异步代码更安全。
模式一:生命周期检查
在异步操作完成后,检查组件/视图是否还在生命周期内:
Flutter 版本:
dart
Future<void> fetchUserInfo() async {
await Future.delayed(Duration(seconds: 2));
if (!mounted) return; // 生命周期检查
setState(() {
userName = "张三";
});
}
React/React Native 版本:
javascript
useEffect(() => {
let isMounted = true;
async function fetchUserInfo() {
await new Promise(resolve => setTimeout(resolve, 2000));
if (!isMounted) return; // 生命周期检查
setUserName("张三");
}
fetchUserInfo();
return () => {
isMounted = false; // 清理
};
}, []);
iOS 版本:
swift
func fetchUserInfo() {
fetchTask = Task { @MainActor in
do {
try await Task.sleep(for: .seconds(2))
try Task.checkCancellation() // 任务取消检查
userNameLabel.text = "张三"
} catch {
// 任务被取消
}
}
}
deinit {
fetchTask?.cancel() // 清理
}
模式二:任务取消机制
在组件/视图销毁时,主动取消正在进行的异步任务:
Flutter 版本:
dart
class _UserProfilePageState extends State<UserProfilePage> {
Future<void>? _fetchTask;
@override
void initState() {
super.initState();
_fetchTask = fetchUserInfo();
}
Future<void> fetchUserInfo() async {
await Future.delayed(Duration(seconds: 2));
if (!mounted) return;
setState(() {
userName = "张三";
});
}
@override
void dispose() {
// 取消任务(虽然 Future 不能直接取消,但可以通过检查 mounted 来模拟)
_fetchTask = null;
super.dispose();
}
}
React/React Native 版本:
javascript
useEffect(() => {
const controller = new AbortController();
async function fetchUserInfo() {
try {
const response = await fetch('/api/user', {
signal: controller.signal // 使用 AbortSignal
});
const data = await response.json();
setUserName(data.name);
} catch (error) {
if (error.name === 'AbortError') {
console.log('请求被取消');
}
}
}
fetchUserInfo();
return () => {
controller.abort(); // 取消请求
};
}, []);
iOS 版本:
swift
private var fetchTask: Task<Void, Never>?
func fetchUserInfo() {
fetchTask?.cancel() // 取消之前的任务
fetchTask = Task {
// 异步操作
}
}
deinit {
fetchTask?.cancel() // 清理
}
模式三:使用状态管理库
使用状态管理库(如 Provider、Riverpod、Redux 等)来管理异步状态,这些库通常内置了生命周期管理:
Flutter + Riverpod 版本:
dart
final userProvider = FutureProvider<User>((ref) async {
// Riverpod 会自动处理生命周期
final response = await http.get(Uri.parse('https://api.example.com/user'));
return User.fromJson(json.decode(response.body));
});
class UserProfilePage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(userProvider);
return userAsync.when(
data: (user) => Text(user.name),
loading: () => CircularProgressIndicator(),
error: (error, stack) => Text('错误: $error'),
);
}
}
React + React Query 版本:
javascript
import { useQuery } from 'react-query';
function UserProfile() {
// React Query 会自动处理生命周期和缓存
const { data, isLoading, error } = useQuery('user', fetchUserInfo);
if (isLoading) return <div>加载中...</div>;
if (error) return <div>错误: {error.message}</div>;
return <div>{data.name}</div>;
}
模式四:封装通用工具函数
我们可以封装一些通用的工具函数,让异步操作更安全:
Flutter 版本:
dart
extension SafeSetState on State {
void safeSetState(VoidCallback fn) {
if (mounted) {
setState(fn);
}
}
}
// 使用
Future<void> fetchUserInfo() async {
await Future.delayed(Duration(seconds: 2));
safeSetState(() {
userName = "张三";
});
}
React 版本:
javascript
function useSafeState(initialState) {
const [state, setState] = useState(initialState);
const isMountedRef = useRef(true);
useEffect(() => {
return () => {
isMountedRef.current = false;
};
}, []);
const safeSetState = useCallback((newState) => {
if (isMountedRef.current) {
setState(newState);
}
}, []);
return [state, safeSetState];
}
// 使用
function UserProfile() {
const [userName, setUserName] = useSafeState(null);
useEffect(() => {
fetchUserInfo().then(setUserName);
}, []);
}
iOS 版本:
swift
extension UIViewController {
func safeAsyncUpdate<T>(_ task: Task<T, Error>, update: @escaping (T) -> Void) {
Task { @MainActor in
do {
let result = try await task.value
if !isViewLoaded || view.window == nil {
return // 视图未加载或不在窗口中
}
update(result)
} catch {
// 处理错误
}
}
}
}
// 使用
func fetchUserInfo() {
let task = Task {
// 异步操作
return "张三"
}
safeAsyncUpdate(task) { name in
userNameLabel.text = name
}
}
实际场景中的应用
让我们看看这些防御模式在实际项目中的应用。
场景一:列表页面的数据加载
在列表页面中,用户可能会快速滚动,导致多个异步请求同时进行。我们需要确保只有最新的请求结果才会更新 UI。
Flutter 版本:
dart
class ProductListPage extends StatefulWidget {
@override
_ProductListPageState createState() => _ProductListPageState();
}
class _ProductListPageState extends State<ProductListPage> {
List<Product> products = [];
int currentPage = 1;
bool isLoading = false;
Future<void>? _loadTask;
@override
void initState() {
super.initState();
loadProducts();
}
Future<void> loadProducts() async {
if (isLoading) return;
setState(() {
isLoading = true;
});
_loadTask = _fetchProducts();
await _loadTask;
if (!mounted) return;
setState(() {
isLoading = false;
});
}
Future<void> _fetchProducts() async {
// 模拟网络请求
await Future.delayed(Duration(seconds: 1));
if (!mounted) return;
final newProducts = List.generate(10, (i) => Product(id: i));
if (!mounted) return;
setState(() {
products.addAll(newProducts);
currentPage++;
});
}
@override
void dispose() {
_loadTask = null;
super.dispose();
}
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
return ListTile(title: Text(products[index].name));
},
);
}
}
React 版本:
javascript
function ProductList() {
const [products, setProducts] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [isLoading, setIsLoading] = useState(false);
const abortControllerRef = useRef(null);
useEffect(() => {
loadProducts();
return () => {
// 清理:取消正在进行的请求
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
};
}, []);
async function loadProducts() {
if (isLoading) return;
setIsLoading(true);
// 取消之前的请求
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
// 创建新的 AbortController
abortControllerRef.current = new AbortController();
try {
const response = await fetch(`/api/products?page=${currentPage}`, {
signal: abortControllerRef.current.signal
});
const data = await response.json();
setProducts(prev => [...prev, ...data.products]);
setCurrentPage(prev => prev + 1);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('加载失败:', error);
}
} finally {
setIsLoading(false);
}
}
return (
<div>
{products.map(product => (
<div key={product.id}>{product.name}</div>
))}
</div>
);
}
场景二:搜索功能的防抖处理
在搜索功能中,用户输入时会触发多个搜索请求,我们需要确保只处理最新的搜索结果。
Flutter 版本:
dart
class SearchPage extends StatefulWidget {
@override
_SearchPageState createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
String _searchQuery = '';
List<String> _searchResults = [];
Timer? _debounceTimer;
Future<void>? _searchTask;
void onSearchChanged(String query) {
_searchQuery = query;
// 取消之前的防抖定时器
_debounceTimer?.cancel();
// 设置新的防抖定时器
_debounceTimer = Timer(Duration(milliseconds: 500), () {
performSearch(query);
});
}
Future<void> performSearch(String query) async {
if (query.isEmpty) {
setState(() {
_searchResults = [];
});
return;
}
// 取消之前的搜索任务
_searchTask = null;
_searchTask = _fetchSearchResults(query);
await _searchTask;
}
Future<void> _fetchSearchResults(String query) async {
// 模拟网络请求
await Future.delayed(Duration(seconds: 1));
if (!mounted) return;
// 检查查询是否还是最新的
if (_searchQuery != query) return;
final results = ['结果1', '结果2', '结果3'];
if (!mounted) return;
setState(() {
_searchResults = results;
});
}
@override
void dispose() {
_debounceTimer?.cancel();
_searchTask = null;
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
TextField(
onChanged: onSearchChanged,
decoration: InputDecoration(hintText: "搜索"),
),
Expanded(
child: ListView.builder(
itemCount: _searchResults.length,
itemBuilder: (context, index) {
return ListTile(title: Text(_searchResults[index]));
},
),
),
],
),
);
}
}
React 版本:
javascript
function SearchPage() {
const [searchQuery, setSearchQuery] = useState('');
const [searchResults, setSearchResults] = useState([]);
const abortControllerRef = useRef(null);
useEffect(() => {
const timer = setTimeout(() => {
performSearch(searchQuery);
}, 500);
return () => {
clearTimeout(timer);
};
}, [searchQuery]);
async function performSearch(query) {
if (!query) {
setSearchResults([]);
return;
}
// 取消之前的请求
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
abortControllerRef.current = new AbortController();
try {
const response = await fetch(`/api/search?q=${query}`, {
signal: abortControllerRef.current.signal
});
const data = await response.json();
// 检查查询是否还是最新的
if (abortControllerRef.current.signal.aborted) return;
setSearchResults(data.results);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('搜索失败:', error);
}
}
}
return (
<div>
<input
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="搜索"
/>
<ul>
{searchResults.map((result, index) => (
<li key={index}>{result}</li>
))}
</ul>
</div>
);
}
总结
Flutter、前端和 iOS 的异步问题确实高度相似,核心都是异步操作和生命周期管理之间的矛盾。虽然每个平台的解决方案略有不同,但我们可以总结出一套通用的防御模式:
- 生命周期检查:在异步操作完成后,检查组件/视图是否还在生命周期内
- 任务取消机制:在组件/视图销毁时,主动取消正在进行的异步任务
- 使用状态管理库:利用成熟的状态管理库,它们通常内置了生命周期管理
- 封装通用工具函数:创建一些通用的工具函数,让异步操作更安全
在实际开发中,我们应该根据项目的具体情况选择合适的方案。对于简单的场景,手动检查生命周期就足够了;对于复杂的场景,使用状态管理库会更省心。
最重要的是,我们要时刻记住:异步操作是有延迟的,在异步操作完成之前,用户可能已经离开了当前页面。所以,每次异步操作完成后,都要检查一下组件/视图是否还在生命周期内,这样才能避免崩溃和内存泄漏。
希望这篇文章能帮助你更好地理解跨平台的异步问题,写出更安全的异步代码!
完整可运行 Demo 代码
下面是一个完整的 Flutter Demo,展示了如何正确处理异步操作和生命周期:
dart
import 'package:flutter/material.dart';
import 'dart:async';
// 工具类:安全的 setState 扩展
extension SafeSetState on State {
void safeSetState(VoidCallback fn) {
if (mounted) {
setState(fn);
}
}
}
// 用户信息模型
class User {
final String name;
final String email;
User({required this.name, required this.email});
}
// 用户信息页面
class UserProfilePage extends StatefulWidget {
@override
_UserProfilePageState createState() => _UserProfilePageState();
}
class _UserProfilePageState extends State<UserProfilePage> {
User? user;
bool isLoading = false;
String? errorMessage;
Future<void>? _fetchTask;
@override
void initState() {
super.initState();
fetchUserInfo();
}
Future<void> fetchUserInfo() async {
safeSetState(() {
isLoading = true;
errorMessage = null;
});
_fetchTask = _performFetch();
await _fetchTask;
}
Future<void> _performFetch() async {
try {
// 模拟网络请求延迟
await Future.delayed(Duration(seconds: 2));
// 检查生命周期
if (!mounted) return;
// 模拟数据解析
await Future.delayed(Duration(milliseconds: 500));
// 再次检查生命周期
if (!mounted) return;
// 模拟获取到的用户数据
final fetchedUser = User(
name: "张三",
email: "zhangsan@example.com",
);
// 更新 UI
safeSetState(() {
user = fetchedUser;
isLoading = false;
});
} catch (e) {
// 错误处理也要检查生命周期
if (!mounted) return;
safeSetState(() {
errorMessage = "加载失败: $e";
isLoading = false;
});
}
}
@override
void dispose() {
// 清理:虽然 Future 不能直接取消,但可以通过检查 mounted 来模拟
_fetchTask = null;
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("用户信息"),
),
body: Center(
child: _buildContent(),
),
);
}
Widget _buildContent() {
if (isLoading) {
return CircularProgressIndicator();
}
if (errorMessage != null) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
errorMessage!,
style: TextStyle(color: Colors.red),
),
SizedBox(height: 16),
ElevatedButton(
onPressed: fetchUserInfo,
child: Text("重试"),
),
],
);
}
if (user == null) {
return Text("暂无数据");
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"姓名: ${user!.name}",
style: TextStyle(fontSize: 20),
),
SizedBox(height: 16),
Text(
"邮箱: ${user!.email}",
style: TextStyle(fontSize: 16),
),
],
);
}
}
// 搜索页面示例
class SearchPage extends StatefulWidget {
@override
_SearchPageState createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
String _searchQuery = '';
List<String> _searchResults = [];
Timer? _debounceTimer;
Future<void>? _searchTask;
void onSearchChanged(String query) {
setState(() {
_searchQuery = query;
});
// 取消之前的防抖定时器
_debounceTimer?.cancel();
// 设置新的防抖定时器
_debounceTimer = Timer(Duration(milliseconds: 500), () {
performSearch(query);
});
}
Future<void> performSearch(String query) async {
if (query.isEmpty) {
safeSetState(() {
_searchResults = [];
});
return;
}
_searchTask = _fetchSearchResults(query);
await _searchTask;
}
Future<void> _fetchSearchResults(String query) async {
try {
// 模拟网络请求
await Future.delayed(Duration(seconds: 1));
// 检查生命周期
if (!mounted) return;
// 检查查询是否还是最新的
if (_searchQuery != query) return;
// 模拟搜索结果
final results = List.generate(
5,
(index) => "搜索结果 ${index + 1} for '$query'",
);
// 更新 UI
safeSetState(() {
_searchResults = results;
});
} catch (e) {
if (!mounted) return;
safeSetState(() {
_searchResults = [];
});
}
}
@override
void dispose() {
_debounceTimer?.cancel();
_searchTask = null;
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("搜索"),
),
body: Column(
children: [
Padding(
padding: EdgeInsets.all(16),
child: TextField(
onChanged: onSearchChanged,
decoration: InputDecoration(
hintText: "输入搜索关键词",
border: OutlineInputBorder(),
),
),
),
Expanded(
child: _searchResults.isEmpty
? Center(
child: Text(
_searchQuery.isEmpty
? "请输入搜索关键词"
: "搜索中...",
),
)
: ListView.builder(
itemCount: _searchResults.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(_searchResults[index]),
);
},
),
),
],
),
);
}
}
// 主应用
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '异步防御模式 Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("异步防御模式 Demo"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UserProfilePage(),
),
);
},
child: Text("用户信息页面"),
),
SizedBox(height: 16),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SearchPage(),
),
);
},
child: Text("搜索页面"),
),
],
),
),
);
}
}
这个 Demo 展示了:
- 安全的 setState 扩展 :通过
safeSetState方法,自动检查mounted状态 - 用户信息页面:展示了如何在异步操作中正确检查生命周期
- 搜索页面:展示了防抖处理和生命周期检查的结合使用
- 任务管理 :通过
_fetchTask和_searchTask来跟踪异步任务
你可以运行这个 Demo,快速切换页面来测试异步操作的防御机制是否正常工作。