关于 Block 与异步重试的设计模式
背景 在开发中,实现一个带重试机制的异步任务是一个常见需求。最近,我在实现一个启动页数据加载的功能时,对重试逻辑的实现方式进行了深入的思考和实践,并踩了一些典型的坑。本文旨在记录两种截然不同的实现思路,分析其优劣,并反思为什么开发者有时会选择一个更复杂甚至错误的方案。 两种实现方案 我们以一个通用的重试辅助方法 executeRetryableTask 为例,对比两种实现。 方案一:复杂的 Block 自引用(有缺陷) 这种方案试图将重试逻辑封装在一个自包含的 Block 单元内,通过 “weak/strong dance” 的变体来避免循环引用。 // 方案一:复杂的 Block 自引用 - (void)executeRetryableTask:(RetryableApiCallBlock)apiCall maxRetries:(NSInteger)maxRetries retryInterval:(NSTimeInterval)retryInterval completion:(RetryCompletionBlock)completion { __block NSInteger currentRetryCount = 0; void (^__block __weak weakAttemptBlock)(void); // 弱引用指针 void (^attemptBlock)(void); // 局部变量,存储在栈上 attemptBlock = ^{ void (^strongAttemptBlock)(void) = weakAttemptBlock; // 试图从弱引用恢复强引用 apiCall(^(id responseObject) { /* ... 成功 ... */ }, ^(NSError *error) { currentRetryCount++; if (currentRetryCount >= maxRetries) { /* ... 最终失败 ... */ } else { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(retryInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (strongAttemptBlock) { // Bug 所在:strongAttemptBlock 在此会是 nil strongAttemptBlock(); } }); } }); }; weakAttemptBlock = attemptBlock; attemptBlock(); } 方案二:简单的异步递归 这种方案利用简单的递归调用,通过参数传递状态,代码直观且健壮。 ...