基于Swift 4.0 的MVVM框架

Swift 4.0 下的 MVVM 实践

  • 基于SwiftMVVM框架
  • 仿照MoyaAlamofire进行更轻量级的封装,网络请求返回实体或者实体数组(双刃剑,缺点在于对于简单数据结构也需要创建相应的实体对象,优点是保持项目结构统一,后续会优化)
  • 基于当下流行的Realm数据库,封装了一套方便开发者,对数据库CRUD操作的数据库工具类
  • 另外通过灵活使用Swift的语言特性,封装了其他更方便的工具类,使开发者的工作更轻松

Tips: 因为项目涉及的功能较多,部分功能测试不够充分,如果有不合理的地方,请多多指教,后续我也将不断完善这个框架,努力打造一套合理、简洁、实用的项目

项目结构

  • Controllers
  • ViewModels
  • Views
  • Utils

    • Extension
      • UIColorExtension
      • NSObjectExtension
      • NotificationCenterExtension
      • UIApplicationExtension
      • NSUserDefaultsExtension
      • UIViewControllerExtension
      • UIStoryboardExtension
      • UIViewExtension
      • StringExtension
      • UIImageExtension
      • FoundationExtension
    • Configure
    • ThirdPart
    • Custom

      • Managers

        • NetworkManager(仿照Moya的轻量级实现)
        • CacheManager (待实现)
        • AppManager
        • ThemeManager
        • DateManager
        • FileManager
        • LocationManager
        • DBManager(基于RealmSwift)
      • UITools

        • CustomProgressHUD
        • WebView(UIWebView&WKWebView)
  • Resources
    • Interface
    • Assets.xcassets
    • Images
    • Documents

      架构工作流程

      流程图

Utils中部分工具介绍

一、 NetworkManager

  • Model 中实现方式,遵循RealmSwift的写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import UIKit
import RealmSwift

class Province: DBModel {

@objc dynamic var id: Int = 0

@objc dynamic var firstletter: String = ""

@objc dynamic var name: String = ""


/// 设置与Contry的一对多关系
let owner = LinkingObjects(fromType: Country.self, property: "provinces")


/// 设置与cities的一对多关系, 保持字段名称与服务端返回一致
let citys = List<City>()

}
  • API 中实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
enum UserApi {
case login
case Image(count:Int,page:Int)
case regester
case xxx
case download
}

extension UserApi: Request {


/// 请求返回实体类型
typealias EntityType = Country


/// host 如果项目中所有host可以保持一致,直接在Request中设置默认Host,在具体的Api中可省略
var baseURL: String {

switch self {

case .xxx:

return "http://www.baidu.com"

default:

return "http://comm.app.autohome.com.cn"

}
}


/// 具体业务对应的path
var path: String {

switch self {

case .login:

return "/news/province-pm2-ts0.json"

case .regester:

return "/regester"

case .download:

return "http://7xoyls.com1.z0.glb.clouddn.com/%E9%99%88%E5%A5%95%E8%BF%85-%E4%B8%80%E4%B8%9D%E4%B8%8D%E6%8C%82%20%28Live%29.mp3"

default:

return ""

}
}

/// 参数
var parameters: [String: Any]? {

switch self {

case .Image(let X, let Y):

return ["x": X, "y": Y]

default:

return [:]

}
}
}
  • ViewModel 中实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import UIKit

class UserViewModel: BaseViewModel<UserApi> {

func login(pwd: String?, account: String?, complete: @escaping ((Country)->Void)) {

provider.request(.login, responseHandler: { response in

DebugPrint("value = \(String(describing: response.value))")

DebugPrint("values = \(String(describing: response.values))")

DebugPrint(response.message)

if let province = response.value {

complete(province)

}

})
}
}

二、 NotifcationCenterExtension

通过对NotificationCenter封装后,开发者在定义通知名称的时候,只需要在枚举NotificationName 中添加对应的case就可以了,方便简单

1
2
3
4
5
6
7
8
9
enum NotificationName: String {

// 用户登录成功
case loginSuccess

// 用户退出登录
case logout

}

调用时代码可以更加简洁了, 而且可以和调用系统通知格式保持一致:

1
2
3
4
5
// 调用自定义的通知名称
NotificationCenter.post(name: .loginSuccess, object: nil, userInfo: nil)

// 调用系统的通知名称
NotificationCenter.post(name: .UIKeyboardDidHide, object: nil, userInfo: nil)