Swift 中的 MVVM 架构深度解析与最佳实践
一、Swift MVVM 核心架构
数据绑定
请求/响应
数据更新
状态更新
API/数据库
导航
View
ViewModel
Model
Service
Router
二、核心组件实现
1. Model 层设计
struct User : Codable {
let id: Int
var name: String
var email: String
func isValidEmail ( ) - > Bool {
let regex = #"^\S+@\S+\.\S+$" #
return email. range ( of: regex, options: . regularExpression) != nil
}
}
2. ViewModel 层实现
final class UserViewModel : ObservableObject {
@Published var user: User
@Published var isLoading = false
@Published var errorMessage: String ?
private let apiService: APIServiceProtocol
private let storage: UserStorageProtocol
init ( user: User ,
apiService: APIServiceProtocol = APIService ( ) ,
storage: UserStorageProtocol = UserStorage ( ) ) {
self . user = user
self . apiService = apiService
self . storage = storage
}
func saveUser ( ) {
guard user. isValidEmail ( ) else {
errorMessage = "Invalid email format"
return
}
isLoading = true
apiService. updateUser ( user) { [ weak self ] result in
DispatchQueue . main. async {
self ? . isLoading = false
switch result {
case . success ( let updatedUser) :
self ? . user = updatedUser
self ? . storage. saveUser ( updatedUser)
case . failure ( let error) :
self ? . errorMessage = error. localizedDescription
}
}
}
}
}
3. View 层实现 (SwiftUI)
struct UserView : View {
@StateObject var viewModel: UserViewModel
@EnvironmentObject var router: Router
var body: some View {
Form {
Section ( header: Text ( "Personal Info" ) ) {
TextField ( "Name" , text: $viewModel. user. name)
TextField ( "Email" , text: $viewModel. user. email)
. keyboardType ( . emailAddress)
. autocapitalization ( . none )
}
Button ( "Save" ) {
viewModel. saveUser ( )
}
. disabled ( viewModel. isLoading)
if let error = viewModel. errorMessage {
Text ( error)
. foregroundColor ( . red)
}
}
. navigationTitle ( "Profile" )
. overlay {
if viewModel. isLoading {
ProgressView ( )
}
}
}
}
三、数据绑定机制
1. SwiftUI 绑定方式
TextField ( "Name" , text: $viewModel. user. name)
. onReceive ( viewModel. $errorMessage) { error in
if error != nil {
showAlert = true
}
}
Button ( "Save" , action: viewModel. saveUser)
2. Combine 高级绑定
class UserViewModel : ObservableObject {
private var cancellables = Set < AnyCancellable > ( )
init ( ) {
$user
. debounce ( for : . seconds ( 0.5 ) , scheduler: RunLoop . main)
. filter { $0 . name. count > 3 }
. sink { [ weak self ] _ in
self ? . autoSave ( )
}
. store ( in : & cancellables)
}
}
四、依赖注入方案
1. 构造函数注入
struct UserView_Previews : PreviewProvider {
static var previews: some View {
let mockUser = User ( id: 1 , name: "Test" , email: "test@example.com" )
let mockService = MockAPIService ( )
let viewModel = UserViewModel ( user: mockUser, apiService: mockService)
UserView ( viewModel: viewModel)
}
}
2. 依赖容器
class DIContainer {
static let shared = DIContainer ( )
lazy var apiService: APIServiceProtocol = {
return ProcessInfo . processInfo. isUITesting ?
MockAPIService ( ) : APIService ( )
} ( )
lazy var storage: UserStorageProtocol = {
return UserDefaultsStorage ( )
} ( )
}
let viewModel = UserViewModel (
user: user,
apiService: DIContainer . shared. apiService,
storage: DIContainer . shared. storage
)
五、路由导航实现
1. 路由协议
enum Route : Hashable {
case userDetail ( User )
case settings
case logout
}
class Router : ObservableObject {
@Published var path = NavigationPath ( )
func navigate ( to route: Route ) {
path. append ( route)
}
func popToRoot ( ) {
path. removeLast ( path. count )
}
}
2. ViewModel 中的导航
class UserListViewModel : ObservableObject {
@Published var users: [ User ] = [ ]
private let router: Router
init ( router: Router ) {
self . router = router
}
func selectUser ( _ user: User ) {
router. navigate ( to: . userDetail ( user) )
}
}
六、测试策略
1. ViewModel 单元测试
class UserViewModelTests : XCTestCase {
var sut: UserViewModel !
var mockService: MockAPIService !
override func setUp ( ) {
mockService = MockAPIService ( )
let user = User ( id: 1 , name: "John" , email: "test@test.com" )
sut = UserViewModel ( user: user, apiService: mockService)
}
func testValidEmailSavesSuccessfully ( ) {
mockService. updateUserResult = . success ( User ( id: 1 , name: "John" , email: "valid@email.com" ) )
sut. user. email = "valid@email.com"
sut. saveUser ( )
XCTAssertFalse ( sut. isLoading)
XCTAssertNil ( sut. errorMessage)
XCTAssertEqual ( sut. user. email, "valid@email.com" )
}
func testInvalidEmailShowsError ( ) {
sut. user. email = "invalid-email"
sut. saveUser ( )
XCTAssertEqual ( sut. errorMessage, "Invalid email format" )
}
}
2. UI 快照测试
func testUserViewLoadingState ( ) {
let viewModel = UserViewModel ( user: . mock)
viewModel. isLoading = true
let view = UserView ( viewModel: viewModel)
assertSnapshot ( of: view, as : . image ( layout: . device ( config: . iPhone13) ) )
}
七、性能优化技巧
1. 高效数据绑定
class UserViewModel : ObservableObject {
@Published private ( set ) var user: User
@Published private ( set ) var isLoading = false
private var temporaryData: String = ""
}
2. 资源管理
func loadData ( ) {
Task { @MainActor in
isLoading = true
do {
user = try await apiService. fetchUser ( )
} catch {
errorMessage = error. localizedDescription
}
isLoading = false
}
}
八、常见问题解决方案
1. 循环引用处理
func fetchData ( ) {
apiService. getData { [ weak self ] result in
guard let self = self else { return }
}
}
func fetchData ( ) {
apiService. getData { [ unowned self ] result in
}
}
2. 复杂状态管理
enum UserState {
case loading
case loaded ( User )
case error ( Error )
case editing
}
class UserViewModel : ObservableObject {
@Published var state: UserState = . loading
func loadUser ( ) {
state = . loading
apiService. fetchUser { result in
switch result {
case . success ( let user) :
state = . loaded ( user)
case . failure ( let error) :
state = . error ( error)
}
}
}
}
九、SwiftUI 最佳实践
1. 视图优化
struct UserView : View {
@ObservedObject var viewModel: UserViewModel
var body: some View {
VStack {
if case . loaded ( let user) = viewModel. state {
UserProfileView ( user: user)
}
SaveButton ( viewModel: viewModel)
}
}
}
struct SaveButton : View {
@ObservedObject var viewModel: UserViewModel
var body: some View {
Button ( "Save" ) {
viewModel. saveUser ( )
}
. disabled ( viewModel. isSaving)
}
}
2. 预览优化
struct UserView_Previews : PreviewProvider {
static var previews: some View {
Group {
UserView ( viewModel: . previewLoaded)
UserView ( viewModel: . previewLoading)
UserView ( viewModel: . previewError)
}
}
}
extension UserViewModel {
static var previewLoaded: UserViewModel {
let vm = UserViewModel ( user: . mock)
vm. state = . loaded ( . mock)
return vm
}
}
十、架构演进建议
1. 中型项目增强
View
ViewModel
UseCase
Repository
API
Database
2. 大型项目方案
App /
├── Feature /
│ ├── User /
│ │ ├── Presentation / # View + ViewModel
│ │ ├── Domain / # Model + UseCase
│ │ └── Data / # Repository + DTO
│ └── Settings /
├── Core /
│ ├── Networking /
│ ├── Storage /
│ └── DI /
└── Shared /
├── Utilities /
└── DesignSystem /
十一、性能监控工具
1. Instruments 关键指标
指标
正常范围
警告阈值
优化方案
内存占用
<50MB
>100MB
检查强引用循环
CPU使用率
<30%
>70%
优化数据绑定
刷新频率
60fps
<50fps
简化复杂视图
启动时间
<1s
>2s
延迟加载VM
2. 自定义监控
class PerformanceMonitor {
static func track< T> ( _ operation: String , _ block: ( ) throws - > T) rethrows - > T {
let start = CFAbsoluteTimeGetCurrent ( )
defer {
let diff = CFAbsoluteTimeGetCurrent ( ) - start
print ( "⏱ $operation) took $diff)s" )
if diff > 0.1 {
}
}
return try block ( )
}
}
func loadData ( ) {
PerformanceMonitor . track ( "UserFetch" ) {
viewModel. loadUser ( )
}
}
十二、Swift 生态工具链
1. 推荐库
库名
用途
集成方式
Combine
响应式编程
原生支持
Factory
依赖注入
SPM
Quick/Nimble
单元测试
SPM
ViewInspector
SwiftUI 测试
SPM
2. 模板生成
brew install mint
mint install swiftmvvm/template
swiftmvvm generate UserFeature
总结:Swift MVVM 最佳实践
严格分层:
View:只处理UI展示
ViewModel:处理视图逻辑
Model:封装业务逻辑
单向数据流:
用户操作
View
ViewModel
Model
测试策略:
ViewModel:100%单元测试
View:快照测试
Model:业务逻辑测试
性能优化:
使用@Published精确控制
避免在View中处理业务逻辑
使用Task管理异步
架构演进:
小型项目:基础MVVM
中型项目:MVVM+UseCase
大型项目:模块化MVVM
黄金法则:保持View的"笨拙",让ViewModel成为唯一的真相源,让Model保持纯净的业务逻辑封装。