切换默认 Actor 隔离为 nonisolated 并补充并发约定

This commit is contained in:
2026-07-06 16:18:20 +08:00
parent b7bc62e94b
commit be18ecd841
2 changed files with 23 additions and 4 deletions

View File

@ -36,6 +36,25 @@ iOS 16+ · 仅 iPhone · 仅竖屏。不做 iPad / 横屏 / 低版本 API 适配
- 对照 Android **行为** 用 UIKit 重写,不翻译 Compose 语法 - 对照 Android **行为** 用 UIKit 重写,不翻译 Compose 语法
- ViewModel 属性供读取UI 更新由 View 在回调中主动触发 - ViewModel 属性供读取UI 更新由 View 在回调中主动触发
## Swift 并发与 Actor
工程 **Default Actor Isolation** 配置为 **`nonisolated`**`suixinkan``suixinkanTests` 均为 `SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated`)。新类型默认不隐式绑定 `@MainActor`,与命令式 UIKit + 显式刷新风格一致。
| Target | Default Actor Isolation |
|--------|-------------------------|
| `suixinkan` | `nonisolated` |
| `suixinkanTests` | `nonisolated` |
编写约定:
- **ViewModel / Model**:保持 `nonisolated`,不依赖默认 MainActor纯逻辑与校验可用 `nonisolated static`
- **需要主线程的类型**:在类型上**显式**标注 `@MainActor`(如 `APIClient``AuthAPI``ProfileAPI``NetworkServices``OSSUploadService``AppRouter`)。
- **UI 更新**:在 `UIViewController` 内通过 `Task { @MainActor in ... }` 或已在主线程的回调中刷新 UI不假设 ViewModel 调用方一定在主 Actor`onStateChange` 回调应切回主线程再改 UI。
- **网络与可发送类型**:请求/环境等跨边界类型保持 `nonisolated` + `Sendable`(如 `APIRequest`),避免无意引入 actor 隔离冲突。
- **单元测试**target 默认为 `nonisolated``MockURLSession` 等替身保持 `nonisolated`。需要构造 `@MainActor` 类型(`APIClient``AuthAPI` 等)的测试类应显式标 `@MainActor`
新增代码不要为「凑隔离」给 ViewModel 或普通 model 加 `@MainActor`;仅 UI 入口、共享网络单例等确需主线程协调处显式标注。
## 代码注释 ## 代码注释
`class` / `struct` / `enum`、协议及公开 API 必须写 `///` 文档注释,说明职责与用途;复杂内部逻辑按需补充。不写废话注释、不写过时注释。 `class` / `struct` / `enum`、协议及公开 API 必须写 `///` 文档注释,说明职责与用途;复杂内部逻辑按需补充。不写废话注释、不写过时注释。

View File

@ -264,7 +264,7 @@
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -300,7 +300,7 @@
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -443,7 +443,7 @@
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated;
SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -466,7 +466,7 @@
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated;
SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;