欢迎回来,这一章节你会了解到MVC-N设计模式。让我们快速回顾一下MVC设计模式,MVC把文件按照models、views 、controllers.分类
但事实不会这么简单,比如:谁来处理点击事件、谁来处理数据加载、谁来负责把数据展示在View上?
啊哈!这三种类型实际上是重叠的。那么,重叠代码实际上在哪里呢?处理点击事件看起来是controller的任务,所以在controller处理它;数据加载谁来负责呢,好像还是控制器,把model的数据给View赋值呢?好吧,这些工作controller好像都能做,对么?
在你意识到控制器已经很大的时候,控制器已经有几千行代码了,这就是所谓的 massive view controller problem
幸好,我们还有办法解决它,当当当当!~~
MVC-N闪亮登场!
MVC-N看起来比MVC更好,毕竟他按类型分成了四部分,四比三多,所以四个比三个更好,对吧,哈哈。
相对于现在demo中使用的复制网络请求代码(这当然是不太好的),MVC—N创建了一个 network client来处理网络请求
Network clients要做的事情用开发术语来说就是:发起网络请求,把相应数据的json数据处理成model模型,然后通过闭包(block)回调传回view controller
在接下来你要对demo开动了,要完成上述内容:创建一个network client,然后把现有的网络请求逻辑放到network client里,最后重构现有的controller来使用我们的network client。
打开demoStart,模拟器运行,点击Get Started,然后你会看到一个Cleaning Services table view controller。
这里有两个选项Home Services和Business Services,我们选第一个(Home Services)。
点击后会presents出来 Home Products View Controller,这个controller会发起一个网络请求来获取Home products信息。
如果网络很快你看不到加载过程的话,不要担心,下拉一下你就会在状态栏看到那个可爱的小菊花了(这当然表示正在进行网络通讯)
我们返回然后选择Business Services同样会presents出Business Products View Controller并通过网络加载Business products数据
让我们打开代码来看看项目现在的情况吧,选择Cleaning Services—> Controllers 组.可以看到Business Products View Controller和Home Products View Controller
这两个类都有load products方法且内容基本一致,唯一的不同就是请求URL的末端不同:Home Products View Controller末端是home Business Products View Controller末端是Business。
错误处理逻辑和json解析逻辑也近乎一摸一样,这样看来重复代码太多太多了。所以我们要使用Model-View-Controller Networking设计模式,并创建一个network client来处理网络请求,使用network client来消除这两个控制器的重复代码。
首先我们要创建一个新的组叫networking,在这之下还要创建两个组Extensions和 Models。
然后打开你下载的课件的根目录--->Resources目录,你会发现几个相关的网络文件,但他们现在还不适用于MVC-N设计模式,需要我们一会稍作修改。现在直接把这些文件拖进project就好了。
首先把Int+HTTPStatusCode和UIImageView+URL拖进Extensions组中不要忘记勾选Copy Items if needed。
然后把NetworkError放到Models中,最后把NetworkClient直接拖进Networking组,搞定!
让我们快速把这几个文件过一眼,Int+HTTPStatusCode提供了一个简单的判断HTTP状态码是否在200-300之间。(即服务器返回请求成功状态)
UIImageView+URL提供了一个通过URL快速设置imgView图片的方法(类似于大家常用的SDImage)如果你看不懂这些代码,don’t panic,这一点也不耽误你学习MVC-N。
注释:discardableResult :在Swift3中,如果没有使用方法的返回值,会报出警告,使用@discardableResult关键字取消警告
Network Error是一个枚举,作用是把 HTTP Status Code error处理成一个简单的模型,方便我们后续使用。
extension NetworkError: Equatable {
public static func ==(lhs: NetworkError, rhs: NetworkError) -> Bool {
return lhs.statusCode == rhs.statusCode
}
}
注释:基本的枚举类型无需实现==就可以就行比较,但是对于非基本enum的 需要重写==运算符。
当然我们最感兴趣的还是network client,因为我们要把现有的网络请求逻辑放在这里
现在这个类只有一个初始化方法shared(),这里通过读取server environments plist获取root url。如果这个地址要经常改变的话,这样比直接写死要好一些,当然这只是个人喜好。实际上你怎么处理都无所谓。
shared()是一个singletons方法,这里不去讨论程序员们对singletons的看法,或好或坏,至少singletons用在这里是没错的。
下一步我们要把重复的网络请求移动到network client里来,首先我们创建一个新的方法
public func getProducts(forType type: Product.ProductType,
success _success: @escaping ([Product]) -> Void,
failure _failure: @escaping (NetworkError) -> Void) {
let success: ([Product]) -> Void = { products in
DispatchQueue.main.async { _success(products) }
}
let failure: (NetworkError) -> Void = { error in
DispatchQueue.main.async { _failure(error) }
}
let url = baseURL.appendingPathComponent("products/\(type.rawValue)")
let task = session.dataTask(with: url, completionHandler: { (data, response, error) in
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode.isSuccessHTTPCode,
let data = data,
let jsonObject = try? JSONSerialization.jsonObject(with: data),
let json = jsonObject as? [[String: Any]] else {
if let error = error {
failure(NetworkError(error: error))
} else {
failure(NetworkError(response: response))
}
return
}
let products = Product.array(jsonArray: json)
success(products)
})
task.resume()
}
需要传入一个product的枚举类型,用来表明我们需要请求哪种数据,是Business还是Home。然后把成功或失败的结果通过闭包(block)在主线程返回。
network client不会关心请求的结果是什么,只是单纯的把数据通过成功或失败的闭包传递出去
让我们快速的过一下这个方法,事实上,我们首先要确保HTTPURLResponse存在,然后验证httpResponse返回了成功的状态码,最后要确保返回的json数组格式正确,如果有任何一项不成立,我们认为网络请求失败,返回请求错误。
最后task.resume()启动任务
OK,到现在我们最后需要做的就是去Business Products View Controller和Home Products View Controller使用我们新写好的这个方法了。
打开 Business Products View Controller,添加一个network client作为属性。然后更新 load products()方法
同理,接下来修改Home Products View Controller,唯一的不同是传入的type由business变为product
最后重新运行程序,没有出现任何问题,完美!
还有一些其他小问题留在challenge(在课件文件里)里面你自己去实现吧!
从controller中移除网络请求是解决massive view controller问题一个好的开始,但这样还不能完全解决这个问题,
关注我们接下来的教程,你会学到其他设计模式来继续解决这个问题。
拜拜。