AI 辅助 iOS 开发防坑指南:在 Objective-C 老项目中打造“不漏网”的研发工作流

背景

在大模型(LLM)普及的今天,越来越多的开发者习惯于让 AI 分析需求、生成文档、编写代码甚至进行 Code Review。然而,在一些历史包袱较重、动态特性活跃的 Objective-C(OC)iOS 老项目中,许多人发现:即便每一步都让 AI 确认了,上线后依然会遗漏各种奇奇怪怪的小问题。

最典型的场景莫过于:

  • 新增了某个局部多语言挂件,结果因为 Category 的实现方式,导致首页语言切换时整个页面卡住或文案不刷新。
  • 为了解决并发问题修改了网络请求的 stop/cancel 逻辑,结果导致某些地方的回调(Completion Block)被静默拦截,UI 的 Loading 菊花转个不停,甚至引发了其他业务模块的逻辑雪崩。

为什么大模型明明通过了“最小、最优改动”和“影响范围”的自检,线上依然会翻车?在 OC 老项目中,我们应该如何重新设计与 AI 的协作工作流?本文将为你深度复盘技术根因,并提供一套可落地的 “六步质量门” AI 研发工作流


一、 为什么 AI 总是低估 OC 老项目的改动风险?

AI 的代码分析能力基于上下文,但 Objective-C 具有极强的运行时(Runtime)动态特性。许多老项目里充斥着“隐性依赖”与“非显式调用”,这些是 AI 很难一眼看穿的。

1. 致命的 Category 滥用与方法覆盖

Category 是 OC 的一大神器,也是老项目维护者的噩梦。

  • 同名覆盖风险:如果 AI 在 Category 里新增了一个方法,而该方法在原类或其他 Category 里已经存在,运行时会导致其中一个方法被无预警覆盖
  • 全局副作用:有些老项目为了偷懒,在 UILabelNSStringNSBundle 的 Category 里 Hook 或重写了系统方法(如 localizedStringForKey:...)。AI 在只看局部上下文时,根本不知道这个全局 Category 会在语言切换或资源读取时被意外触发,进而导致首页等多语言刷新链路断裂。

2. 被忽视的请求生命周期与回调契约

网络请求的取消(stop/cancel)看似简单,实则牵一发动全身。

  • 回调静默丢失:在许多业务逻辑中,UI 的状态恢复(如关闭菊花、恢复按钮点击)强依赖于请求的 completion 执行。如果底层执行了 stop 却直接 return 且没有调用 completion block,调用方就会永远处于“等待中”状态。
  • 共享状态污染:老项目底层往往存在单例或者共享的网络管理器。AI 修改了针对“第二个请求”的 stop 拦截,可能不小心把持有同一个 owner 或 task 标记的第一个正常请求也给误伤了。

二、 拒绝“事后一问”:重新设计 AI 的协作节奏

大部分人的 AI 协作流程是:
接收需求AI 分析生成文档人工确认AI 编写/修改代码人工验证新功能让 AI 确认是否是最小、最优改动及影响范围提交代码

这个流程最大的硬伤在于:影响面评估(Impact Analysis)发生得太晚了!

当代码已经修改完毕,AI 会倾向于基于当前的 diff 去做合理解释(即“证真”),而不是站在全项目调用关系和历史踩坑的角度去反证。为了解决这个问题,我们需要把 AI 从单纯的“代码实现者”升级为“架构审计员”,将质量控制前移。


三、 落地:构建“六步质量门” AI 工作流

我们要将单次的需求闭环,升级为系统级的变更闭锁机制。核心框架如下:

1
1. 风险分类识别 ➔ 2. 引导代码地图 ➔ 3. 约束禁止方案 ➔ 4. 固化回归测试 ➔ 5. 证据型 Diff 审查 ➔ 6. 事故沉淀复盘

第 1 步:变更分类(Classify Before Coding)

在开始写第一行代码前,先不要让 AI 给方案,而是把需求丢给它,让它判断是否命中高风险变更分类

📌 提示词模版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
你是一个资深 iOS Objective-C 研发专家。
请先不要写代码,根据以下开发需求,评估其在老 OC 项目中的潜在风险。

【粘贴开发需求】

请重点检查该需求是否涉及以下高风险技术点:
1. 是否修改或新增了 Category、涉及 Runtime、Method Swizzling 或全局单例?
2. 是否涉及多语言、全局 UI 主题、字体或暗黑模式的动态切换?
3. 是否涉及网络请求并发、stop/cancel 拦截、缓存管理或底层回调契约(Completion Block)?
4. 是否修改了公共组件、底层基类,或者可能间接影响首页、登录、支付等核心路径?

请输出:
- 风险等级:[低 / 中 / 高]
- 命中的风险分类与具体原因
- 在进行该开发前,必须阅读的项目关联文件列表
- 推荐在工程中全局搜索的关键词

第 2 步:构建代码地图(Map Out Dependencies)

AI 需要充足的上下文。通过第 1 步得到的关键词和关联文件,命令 AI 梳理出系统的隐性依赖链条

📌 针对多语言需求的提示词:

1
2
3
4
5
在进行代码修改前,请帮我梳理项目中的多语言切换链路:
1. 当前项目的多语言文案入口在哪里(例如 `NSLocalizedString` 还是自定义 Manager)?
2. 首页和新挂件在语言切换时,是如何接收通知或触发刷新的?
3. 现有的 UI 组件中,是否存在缓存已翻译字符串、导致动态语言切换不生效的情况?
4. 是否存在任何 Category 篡改了系统 `NSBundle` 或 `UILabel` 的默认行为?请帮我列出这些隐患。

📌 针对网络请求 stop 需求的提示词:

1
2
3
4
在修改请求 stop 逻辑前,请帮我梳理网络层回调契约:
1. 当前请求被 cancel/stop 后,底层的 Completion Block 是否确保一定会触发一次?
2. 连续并发发起多个请求时,底层是如何区分和管理请求 Token 的?是否存在全局 Bool 误杀的情况?
3. 请检索工程,列出有哪些调用方(如 Loading 菊花、提交按钮、页面状态机)强依赖于该请求的 success/failure/cancel 回调来恢复状态?

第 3 步:方案设计增加“禁止清单”(Define No-Go Zones)

不仅要问 AI“怎么做最好”,更要规定“绝对不能怎么做”。在 OC 开发中,应该定死以下硬性约束:

  • Category 铁律:业务需求严禁直接通过 Category 去重写或覆盖已有方法;Category 中新增的方法必须强制加业务前缀(如 xxx_method),杜绝同名冲突。
  • 请求契约铁律:只要调用方传入了 completion block,无论请求是成功、失败、超时还是被取消,底层都必须且只能执行一次回调,严禁默默 return。
  • 多语言铁律:UI 控件不要在初始化时直接将翻译后的字符串固化,必须保存 Key 或建立动态刷新监听。

第 4 步:把“痛点”固化为自动化测试

不要寄希望于人工每次都记得去测边缘 case。将曾经出过问题的逻辑写成防回滚(Anti-Regression)测试

示例 1:防止多语言切换失效的单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)testHomeWidgetTitleUpdatesAfterLanguageChanged {
// 1. 设置英文
[LanguageManager setLanguage:@"en"];
HomeWidgetView *view = [[HomeWidgetView alloc] init];
[view refreshLocalizedText];
NSString *englishTitle = view.titleLabel.text;

// 2. 模拟切换至中文
[LanguageManager setLanguage:@"zh-Hans"];
[[NSNotificationCenter defaultCenter] postNotificationName:LanguageDidChangeNotification object:nil];
[view refreshLocalizedText];
NSString *chineseTitle = view.titleLabel.text;

// 3. 断言两次文案一定不能相同(证明动态刷新生效)
XCTAssertNotEqualObjects(englishTitle, chineseTitle, @"语言切换后文案未更新!说明存在缓存或未响应刷新通知");
}

示例 2:确保网络请求取消后必定回调的测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)testCancelRequestStillCallsCompletionOnce {
XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
__block NSInteger callbackCount = 0;

RequestToken *token = [client startRequestWithCompletion:^(Response *response, NSError *error, RequestState state) {
callbackCount += 1;
XCTAssertEqual(state, RequestStateCancelled, @"状态应为已取消");
[expectation fulfill];
}];

// 触发取消
[token cancel];

[self waitForExpectationsWithTimeout:1 handler:nil];
XCTAssertEqual(callbackCount, 1, @"请求被取消后,Completion 竟然没有执行,这将导致 UI 永远卡死!");
}

第 5 步:提交前实施“证据型” Diff 审查

代码写完后,不要含糊地问 AI “这个改动安全吗”。你要逼它拿出“调用链路证据”来。

📌 提交前审查提示词:

1
2
3
4
5
6
7
8
9
请对本次代码改动的 Diff 进行安全审查。
注意:不要只看修改的代码行数是否少,我们需要评估“最小风险”。

请必须以【证据】形式回答以下问题:
1. 本次改动涉及的类和方法中,有哪些是公共底层、Category 或单例?
2. 请列出本次修改的所有方法,并在项目中反向搜索它们,找出所有直接或间接调用它们的业务代码(请一一列出文件名与行号)。
3. 如果其中某个调用方在请求被 cancel 时没有做状态恢复,会发生什么后果?
4. 本次改动,是否有可能引起多语言动态切换时的不刷新问题?
5. 你为本次改动推荐提供哪些自动化回归测试或手工验证 Checklist?

第 6 步:事故复盘沉淀到 AI_REVIEW.md

只要线上漏掉了一个问题,就把这个 Bug 的场景、成因、检索关键词、测试方法写进项目根目录的 AI_REVIEW.md 中。

下一次开发新需求时,直接把这个 Markdown 文件的内容作为系统上下文喂给 AI:

“这是我们项目历史上的 Bug 避坑清单,本次需求分析时,请严格对照其中的 Checklist,确保不重蹈覆辙。”


四、 总结:从“相信 AI” 到 “契约式协作”

AI 是极佳的生产力放大器,但它没有“历史记忆”,对 OC 这种高动态运行时语言的隐式风险也缺乏直觉。

解决线上遗漏的关键,不是禁用 AI,而是改变对齐方式

  1. 别让 AI 在改完代码后再评估影响面:在分析设计阶段就逼它做风险分类,梳理代码地图。
  2. 规范契约,拒绝野路子:确立“Category 加前缀”、“取消请求也必须回调”等硬性架构约束。
  3. 沉淀自动化防线:把每一次线上血泪教训,变成一行行 XCTest 代码与 AI_REVIEW.md 清单。

把大模型放到合理的防线位置上,它才会从一个“写 Bug 的实习生”,真正成长为帮你“守护老项目稳定”的资深技术专家。