15 KiB
15 KiB
情绪博物馆技术架构完善建议
文档版本: v1.0
创建时间: 2025-07-12
基于: 现有代码架构分析
📋 目录
1. 当前架构分析
1.1 架构优势 ✅
清晰的分层结构
┌─────────────────────────────────────┐
│ SwiftUI Views │ ← 表现层
├─────────────────────────────────────┤
│ ViewModel/Manager │ ← 业务逻辑层
├─────────────────────────────────────┤
│ Services │ ← 服务层
├─────────────────────────────────────┤
│ Models │ ← 数据模型层
└─────────────────────────────────────┘
良好的状态管理
- ✅ 使用
@ObservableObject进行状态管理 - ✅
NavigationManager统一管理导航状态 - ✅
ThemeManager管理主题切换 - ✅
MockDataManager提供数据服务
模块化设计
- ✅ 清晰的文件组织结构
- ✅ 功能模块相对独立
- ✅ 可复用的UI组件
1.2 架构不足 ❌
数据持久化缺失
- ❌ 缺少真实的数据存储层
- ❌ 依赖模拟数据,无法持久化用户数据
- ❌ 没有数据同步机制
服务层不完整
- ❌ AI服务只有接口定义,缺少实现
- ❌ 缺少网络服务层
- ❌ 缺少错误处理机制
依赖注入不规范
- ❌ 大量使用
shared单例模式 - ❌ 组件间耦合度较高
- ❌ 测试困难
2. 架构完善建议
2.1 引入依赖注入容器
创建DI容器
// DIContainer.swift
class DIContainer: ObservableObject {
static let shared = DIContainer()
private var services: [String: Any] = [:]
func register<T>(_ type: T.Type, instance: T) {
let key = String(describing: type)
services[key] = instance
}
func resolve<T>(_ type: T.Type) -> T {
let key = String(describing: type)
guard let service = services[key] as? T else {
fatalError("Service \(key) not registered")
}
return service
}
}
服务注册
// ServiceRegistration.swift
extension DIContainer {
func registerServices() {
// 数据服务
register(DataServiceProtocol.self, instance: CoreDataService())
// AI服务
register(AIServiceProtocol.self, instance: OpenAIService())
// 位置服务
register(LocationServiceProtocol.self, instance: LocationService())
// 网络服务
register(NetworkServiceProtocol.self, instance: NetworkService())
}
}
2.2 完善服务层架构
网络服务层
// NetworkService.swift
protocol NetworkServiceProtocol {
func request<T: Codable>(_ endpoint: APIEndpoint) async throws -> T
func upload(data: Data, to endpoint: APIEndpoint) async throws -> UploadResponse
}
class NetworkService: NetworkServiceProtocol {
private let session: URLSession
private let baseURL: URL
init(session: URLSession = .shared, baseURL: URL) {
self.session = session
self.baseURL = baseURL
}
func request<T: Codable>(_ endpoint: APIEndpoint) async throws -> T {
let request = try buildRequest(for: endpoint)
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
200...299 ~= httpResponse.statusCode else {
throw NetworkError.invalidResponse
}
return try JSONDecoder().decode(T.self, from: data)
}
}
数据服务层
// DataService.swift
protocol DataServiceProtocol {
func save<T: Codable>(_ object: T) async throws
func fetch<T: Codable>(_ type: T.Type, predicate: NSPredicate?) async throws -> [T]
func delete<T: Codable>(_ object: T) async throws
}
class CoreDataService: DataServiceProtocol {
private let container: NSPersistentContainer
init() {
container = NSPersistentContainer(name: "EmotionMuseum")
container.loadPersistentStores { _, error in
if let error = error {
fatalError("Core Data error: \(error)")
}
}
}
func save<T: Codable>(_ object: T) async throws {
let context = container.viewContext
// 实现保存逻辑
try context.save()
}
}
2.3 引入Repository模式
数据仓库接口
// Repository.swift
protocol Repository {
associatedtype Entity
func create(_ entity: Entity) async throws
func read(id: UUID) async throws -> Entity?
func update(_ entity: Entity) async throws
func delete(id: UUID) async throws
func list(predicate: NSPredicate?) async throws -> [Entity]
}
// 具体实现
class ConversationRepository: Repository {
typealias Entity = Conversation
private let dataService: DataServiceProtocol
init(dataService: DataServiceProtocol) {
self.dataService = dataService
}
func create(_ conversation: Conversation) async throws {
try await dataService.save(conversation)
}
func list(predicate: NSPredicate? = nil) async throws -> [Conversation] {
return try await dataService.fetch(Conversation.self, predicate: predicate)
}
}
2.4 改进ViewModel架构
基础ViewModel
// BaseViewModel.swift
@MainActor
class BaseViewModel: ObservableObject {
@Published var isLoading = false
@Published var error: AppError?
protected func withLoading<T>(_ operation: () async throws -> T) async rethrows -> T {
isLoading = true
defer { isLoading = false }
return try await operation()
}
protected func handleError(_ error: Error) {
self.error = AppError(from: error)
}
}
具体ViewModel实现
// ConversationViewModel.swift
@MainActor
class ConversationViewModel: BaseViewModel {
@Published var conversations: [Conversation] = []
@Published var currentConversation: Conversation?
private let repository: ConversationRepository
private let aiService: AIServiceProtocol
init(repository: ConversationRepository, aiService: AIServiceProtocol) {
self.repository = repository
self.aiService = aiService
super.init()
}
func loadConversations() async {
await withLoading {
do {
conversations = try await repository.list()
} catch {
handleError(error)
}
}
}
func sendMessage(_ content: String) async {
guard let conversation = currentConversation else { return }
do {
// 添加用户消息
let userMessage = Message(
conversationId: conversation.id,
content: content,
type: .text,
sender: .user
)
// 获取AI回复
let aiResponse = try await aiService.sendMessage(content)
let aiMessage = Message(
conversationId: conversation.id,
content: aiResponse.content,
type: .text,
sender: .ai
)
// 更新对话
var updatedConversation = conversation
updatedConversation.messages.append(contentsOf: [userMessage, aiMessage])
try await repository.update(updatedConversation)
currentConversation = updatedConversation
} catch {
handleError(error)
}
}
}
3. 具体实施方案
3.1 阶段性重构计划
Phase 1: 基础设施搭建 (1周)
-
创建DI容器
- 实现依赖注入容器
- 注册核心服务
- 更新App启动流程
-
完善错误处理
- 定义统一错误类型
- 实现错误处理机制
- 添加用户友好的错误提示
-
网络服务层
- 实现基础网络服务
- 添加请求/响应拦截器
- 实现重试机制
Phase 2: 数据层重构 (1-2周)
-
Core Data集成
- 设计数据模型
- 实现数据服务
- 数据迁移策略
-
Repository模式
- 实现各个Repository
- 数据缓存策略
- 离线数据支持
Phase 3: 业务层重构 (1-2周)
-
ViewModel重构
- 重构现有ViewModel
- 添加单元测试
- 优化状态管理
-
服务集成
- AI服务真实实现
- 地图服务集成
- 推送服务集成
3.2 代码重构示例
重构前 (现状)
// RecordView.swift - 现状
struct RecordView: View {
@EnvironmentObject var mockData: MockDataManager
@EnvironmentObject var navigationManager: NavigationManager
var body: some View {
// 直接使用mockData
// 紧耦合,难以测试
}
}
重构后 (建议)
// RecordView.swift - 重构后
struct RecordView: View {
@StateObject private var viewModel: RecordViewModel
init(viewModel: RecordViewModel = DIContainer.shared.resolve(RecordViewModel.self)) {
self._viewModel = StateObject(wrappedValue: viewModel)
}
var body: some View {
// 使用viewModel
// 松耦合,易于测试
}
}
// RecordViewModel.swift
@MainActor
class RecordViewModel: BaseViewModel {
@Published var conversations: [Conversation] = []
@Published var currentMessage: String = ""
private let conversationRepository: ConversationRepository
private let aiService: AIServiceProtocol
init(conversationRepository: ConversationRepository, aiService: AIServiceProtocol) {
self.conversationRepository = conversationRepository
self.aiService = aiService
super.init()
}
}
4. 性能优化建议
4.1 内存管理优化
图片缓存策略
// ImageCache.swift
class ImageCache {
static let shared = ImageCache()
private let cache = NSCache<NSString, UIImage>()
private let fileManager = FileManager.default
func image(for url: String) async -> UIImage? {
// 内存缓存 -> 磁盘缓存 -> 网络下载
}
func store(_ image: UIImage, for url: String) {
// 存储到内存和磁盘缓存
}
}
数据分页加载
// PaginationManager.swift
class PaginationManager<T>: ObservableObject {
@Published var items: [T] = []
@Published var isLoading = false
@Published var hasMoreData = true
private var currentPage = 0
private let pageSize = 20
func loadNextPage() async {
guard !isLoading && hasMoreData else { return }
isLoading = true
defer { isLoading = false }
// 加载下一页数据
}
}
4.2 UI性能优化
LazyLoading实现
// LazyImageView.swift
struct LazyImageView: View {
let url: String
@State private var image: UIImage?
@State private var isLoading = true
var body: some View {
Group {
if let image = image {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
} else if isLoading {
ProgressView()
} else {
Image(systemName: "photo")
.foregroundColor(.gray)
}
}
.onAppear {
loadImage()
}
}
private func loadImage() {
Task {
image = await ImageCache.shared.image(for: url)
isLoading = false
}
}
}
5. 安全性增强
5.1 数据加密
// EncryptionService.swift
protocol EncryptionServiceProtocol {
func encrypt(_ data: Data) throws -> Data
func decrypt(_ encryptedData: Data) throws -> Data
}
class EncryptionService: EncryptionServiceProtocol {
private let key: SymmetricKey
init() {
// 从Keychain获取或生成密钥
self.key = SymmetricKey(size: .bits256)
}
func encrypt(_ data: Data) throws -> Data {
let sealedBox = try AES.GCM.seal(data, using: key)
return sealedBox.combined!
}
func decrypt(_ encryptedData: Data) throws -> Data {
let sealedBox = try AES.GCM.SealedBox(combined: encryptedData)
return try AES.GCM.open(sealedBox, using: key)
}
}
5.2 API安全
// APIKeyManager.swift
class APIKeyManager {
private let keychain = Keychain(service: "com.emotionmuseum.app")
func storeAPIKey(_ key: String, for service: String) {
keychain[service] = key
}
func getAPIKey(for service: String) -> String? {
return keychain[service]
}
}
6. 可维护性提升
6.1 代码规范
// SwiftLint配置
// .swiftlint.yml
rules:
- line_length
- function_body_length
- type_body_length
- cyclomatic_complexity
- force_cast
- force_try
line_length:
warning: 120
error: 150
function_body_length:
warning: 50
error: 100
6.2 单元测试框架
// ConversationViewModelTests.swift
@MainActor
class ConversationViewModelTests: XCTestCase {
var viewModel: ConversationViewModel!
var mockRepository: MockConversationRepository!
var mockAIService: MockAIService!
override func setUp() {
super.setUp()
mockRepository = MockConversationRepository()
mockAIService = MockAIService()
viewModel = ConversationViewModel(
repository: mockRepository,
aiService: mockAIService
)
}
func testLoadConversations() async {
// 测试加载对话功能
await viewModel.loadConversations()
XCTAssertFalse(viewModel.isLoading)
XCTAssertEqual(viewModel.conversations.count, mockRepository.mockConversations.count)
}
func testSendMessage() async {
// 测试发送消息功能
let testMessage = "Hello"
await viewModel.sendMessage(testMessage)
XCTAssertTrue(mockAIService.sendMessageCalled)
XCTAssertEqual(mockAIService.lastMessage, testMessage)
}
}
6.3 文档和注释规范
/// 对话管理的ViewModel
///
/// 负责管理用户对话的状态和业务逻辑,包括:
/// - 加载历史对话
/// - 发送和接收消息
/// - AI服务集成
/// - 错误处理
@MainActor
class ConversationViewModel: BaseViewModel {
/// 当前显示的对话列表
@Published var conversations: [Conversation] = []
/// 当前活跃的对话
@Published var currentConversation: Conversation?
/// 加载用户的所有对话
/// - Returns: 无返回值,结果通过 `conversations` 属性发布
func loadConversations() async {
// 实现逻辑
}
}
本文档提供了完整的技术架构完善建议,建议按阶段逐步实施