diff --git a/AGENTS.md b/AGENTS.md index 41fec57..ca07ede 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,6 +36,25 @@ iOS 16+ · 仅 iPhone · 仅竖屏。不做 iPad / 横屏 / 低版本 API 适配 - 对照 Android **行为** 用 UIKit 重写,不翻译 Compose 语法 - 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 必须写 `///` 文档注释,说明职责与用途;复杂内部逻辑按需补充。不写废话注释、不写过时注释。 diff --git a/suixinkan.xcodeproj/project.pbxproj b/suixinkan.xcodeproj/project.pbxproj index 77e6228..9857b4a 100644 --- a/suixinkan.xcodeproj/project.pbxproj +++ b/suixinkan.xcodeproj/project.pbxproj @@ -264,7 +264,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_VERSION = 5.0; @@ -300,7 +300,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_VERSION = 5.0; @@ -443,7 +443,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_VERSION = 5.0; @@ -466,7 +466,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_DEFAULT_ACTOR_ISOLATION = nonisolated; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; SWIFT_VERSION = 5.0;