MVC
- Model:数据模型,一个存储数据的对象
- View:负责展示用户的界面
- Controller:定义Model如何展示给View,并且View接受到事件后反馈到Model,承担Model和View的同步工作
Controller直接引用View和Model,View并不知道Controller,通过delegate或者闭包来进行传递。Model也不知道Controller,当Model发生改变时,通过Notification的方式来传递给Controller去更新View
在这个过程中,我们可以发现Controller承担了大量的工作
- 网络请求
- 数据读写
- 数据处理
- UI布局
- 界面跳转
- 处理View的回调
- 监听Model
由于上述工作都在Controller里,Controller就成了一个超级大的巨无霸,为了给Controller瘦身,我们就引入MVVM
MVVM
MVVM则变成了Model - ViewModel - View,View持有对ViewModel的引用,反之没有,ViewModel持有对Model的引用,反之没有
通过ViewModel,我们把Controller中的数据和逻辑处理部分抽离出来,从而实现瘦身的目的
Controller瘦身
工厂模式
在一些UI控件的代码中,往往有很多重复的代码,这里我们拿UIlabel来举例
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 16)
label.textColor = .black
label.numberOfLines = 2
label.lineBreakMode = .byTruncatingTail
在App中我们通常使用的是几种固定的字体,那么我们就可以通过工厂模式来生产不同的字体来实现代码复用
//定义类型
enum LabelStyle {
case title
case subTitle
}
//生产不同字体
static func with(style initalStyle: LabelStyle) -> UILabel {
switch initalStyle {
case .title:
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 16)
label.textColor = .black
label.numberOfLines = 2
label.lineBreakMode = .byTruncatingTail
return label
case .subTitle:
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 12)
label.textColor = .gray
return label
}
}
甚至我们还可以加一个方法,让它直接链式的调用
@discardableResult
func added(into superView:UIView) -> UILabel{
superView.addSubview(self)
return self
}
UILabel.with(style: .title).added(into: contentView)
初始化一个控价就有了更加好的方法,当然我们也要结合实际来设计这些方法
ViewModel
Controller解耦中,引入ViewModel,把Controller中对应与View相关的逻辑抽离出来,这样Controller需要做的就是
- 从DB/网络中获取数据,转换成ViewModel
- 把ViewModel装载给View
- View的属性与ViewModel值绑定在一起
这里我是将ViewModel和Cell绑定在了一起,当ViewModel改变时,Cell的也会发生改变
class ViewModel {
var model: Obserable<NewsData>
required init(model: NewsData) {
self.model = Obserable(value: model)
}
}
class Obserable<T> {
typealias ObserableType = (T) -> Void
var value: T {
didSet {
observer?(value)
}
}
var observer: (ObserableType)?
func bind(to observer: @escaping ObserableType) {
self.observer = observer
observer(value)
}
init(value: T) {
self.value = value
}
}
拓展Cell,将起绑定
extension NewsTableViewCell {
var obModel: Obserable<NewsData>.ObserableType {
return { value in
self.configUI(with: value)
}
}
}
//这段代码就是绑定
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! NewsTableViewCell
viewModel[indexPath.row].model.bind(to: cell.obModel)
网络层封装
这里的封装就是对Alamofier进行上层封装,实际上我们还可以再对其进行一层封装,只暴露出具体的请求类型接口,让parameters等相关信息对于Controller是透明的,类似于透明传输这个概念
typealias NetworkSuccessBlock = (_ response: Any) -> Void
typealias NetworkFailureBlock = (_ error: Any) -> Void
struct NetModel: HandyJSON {
var reason = "" //失败或者成功
var result: AnyObject?
var error_code = 0 // 错误码
}
class NetworkTool: NSObject {
static let shared = NetworkTool()
static let api = ".."
override private init() {
}
}
extension NetworkTool {
public func requestWith(params: [String: Any]? = nil, success: @escaping NetworkSuccessBlock, error: @escaping NetworkFailureBlock) {
getRequestWith(url: NetworkTool.api, params: params) { json in
guard let model = NetModel.deserialize(from: json as? Dictionary) else { return }
self.handelData(model: model, success: success, error: error)
} error: { err in
error(err)
}
}
}
extension NetworkTool {
//最接近Alamofire
private func getRequestWith(url: String, params: [String: Any]? = nil, success: @escaping NetworkSuccessBlock, error: @escaping NetworkFailureBlock) {
Alamofire.request(url, method: .get, parameters: params, encoding: URLEncoding.default).responseJSON { response in
switch response.result {
case let .success(value):
success(value)
case let .failure(err):
error(err)
}
}
}
private func handelData(model: NetModel, success: @escaping NetworkSuccessBlock, error: @escaping NetworkFailureBlock) {
switch model.error_code {
case 0:
success(model.result as Any)
default:
error(model.reason)
}
}
}
路由
界面直接的跳转是一个无法回避的问题,你会发现在Controller中,绝对有如下代码
if indexPath.secion == 0{
if indexPath.row == 0{
}else if....
}else if indexPath.section == 1{
}
然后不断的引用不同的Controller,直到这段代码长的不能再长。
在Controller的结偶中,最常见的就是加一个中间层路由进行不同的跳转,我也小小的实现了一个
具体的流程如下:
- ControllerA发起跳转请求Request
- Router解析Request,轮询问各个Module,看看各个Module是否支持对应的Requst。
- Router根据Module的Response,合成跳转
- 导航根据command进行跳转,并且返回feedBack给Router
- Router返回feedback给ControllerA
代码如下
protocol RouterProtocol {
static func targetWith(pa: [String: Any]) -> RouterProtocol?
func isPush() -> Bool
}
extension RouterProtocol {
func isPush() -> Bool {
return true
}
}
class Router {
private let httpString = "http,httpss"
var targetDic = [String: RouterProtocol.Type]()
let appScheme = "router"
static let shared = Router()
private init() {}
func registerRouter(target: RouterProtocol.Type, key: String) {
targetDic.updateValue(target, forKey: key)
}
func targetWith(url: String, externParameter: [String: Any]? = nil) -> RouterProtocol? {
let encodeUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
if let urlComponents = URLComponents(string: encodeUrl) {
let scheme = urlComponents.scheme ?? ""
let host = urlComponents.host ?? ""
let path = urlComponents.path
var parameter = [String: Any]()
if let queryItems = urlComponents.queryItems {
for query in queryItems {
parameter.updateValue(query.value ?? "", forKey: query.name)
}
}
if let externDic = externParameter {
for (key, value) in externDic {
parameter.updateValue(value, forKey: key)
}
}
if scheme == appScheme {
return targetWith(key: host + path, parameter: parameter)
} else if httpString.contains(scheme) {
if let target = targetWith(key: host + path, parameter: parameter) {
return target
}
}
}
return nil
}
func targetWith(key: String, parameter: [String: Any]) -> RouterProtocol? {
if let router = targetDic[key] {
return router.targetWith(pa: parameter)
}
return nil
}
}
class RouterManager {
static func registerRouters() {
let router = Router.shared
router.registerRouter(target: ViewController.self, key: "home/")
router.registerRouter(target: WebViewController.self, key: "web/")
}
static func openUrl(url: String, externParameter: [String: Any]? = nil) {
if let target = Router.shared.targetWith(url: url, externParameter: externParameter) {
let isPush = target.isPush()
if let vc = target as? UIViewController {
self.openVC(vc: vc, isPush: isPush)
} else {
print("没有此模块")
}
}
}
static func openVC(vc: UIViewController, isPush: Bool) {
if let topVC = UIViewController.topViewController() {
if isPush {
if topVC.navigationController != nil {
topVC.routerPushToVC(toVC: vc)
} else {
let navi = UINavigationController(rootViewController: vc)
topVC.routerPresent(navi, animated: true, completion: nil)
}
} else {
topVC.routerPresent(vc, animated: true, completion: nil)
}
}
}
}
总结
上面这些方法,我参考了不少大牛的博客。这些方法都是为了代码能够便于理解和拓展,当然肯定还有我没见到过的方法,这正是我需要学习的。
这里附上项目地址,大家感兴趣的可以直接下载Demo