原创文章转载请注明出处
最近受《IF YOU'RE SUBCLASSING, YOU'RE DOING IT WRONG》的影响,思考自己的代码中是否有类似的场景,可以将父类的方法抽象出来,以面向协议的编程(protocol oriented programming既POP)方式来思考问题。
文中提到了传统的OOP会带来的各种问题,其中就有本文中要优化的上帝类(God classes - 承担着很多subclasses需要的重要高层级代码的所有责任),Blobs(有过多职权的classes),Lava Flow(因为含有太多的非法代码导致任何人都不敢碰的classes)等等这些种反面模式(anti patterns)。
ProgressHUD是每个应用都会使用的控件,在GitHub上搜索HUD,至少可以找到100种以上相似的Swift/Objective-C开源控件,他们功能类似,只是展现效果不同。starts比较高的有:MBProgressHUD
、SVProgressHUD
、JGProgressHUD
、MMProgressHUD
等。
1.0版本
自11年底开始从事iOS开发以来,类似的控件尝试过很多,最早是MBProgressHUD
,后来感觉其接口封装方式不好,使用时不是很方便,又陆续尝试了许多同类型的控件。每次进行替换的时候,就要将散落在各处的调用方法统一进行替换,虽然IDE提供的replace功能很方便,但是最后我还是将接口进行了统一抽象,梳理出常用的方法,最后放到了项目中的BaseViewController中。
//类似Android toast的纯文本提示框
func showTextOnlyView(message: String!) {
...
}
//等待框
func showWaitingView(message: String?) {
...
}
//隐藏等待框
func hideWaitingView() {
...
}
//n秒后隐藏等待框,可传入回调函数
func hideWaitingViewDelay(interval: Double = 0.3, completion: ((Void) -> Void)? = nil ) {
...
}
//成功提示框,可传入回调函数
func hideWaitingViewSuccess(message: String? = nil, completion: ((Void) -> Void)? = nil) {
...
}
//失败提示框,可传入回调函数
func hideWaitingViewError(message: String? = nil, completion: ((Void) -> Void)? = nil) {
...
}
上述方法基本上满足了我在App中的使用需求,只要是继承自BaseViewController的UIViewController,都可以直接调用以上方法,无需import第三方库。而当我哪天又看上某个漂亮的第三方库的时候,我只需要修改基类中的方法,无需对全工程的代码进行替换。
问题来了,如果UIViewController不是继承自我自定义的BaseViewController呢?如果我想在UIView中使用这些方法呢?聪明如我想到了将代码抽离出来,放到独立的类中,又或者对第三方库做一个扩展。
2.0版本
将上述方法封装到一个独立的类和直接对第三方库做扩展有什么区别吗?区别大概就是做扩展的话,在我调用方法的类中需要import对应的库,而我实在不想在替换第三方库的时候还要满世界去修改import代码,那我们就选择封装一个ProgressHUD的类吧。
import SomeProgressHUD
class ProgressHUD {
class func showTextOnlyView(message: String!) {
...
}
class func showWaitingView(message: String?) {
...
}
class func hideWaitingView() {
...
}
class func hideWaitingViewDelay(interval: Double = 0.3, completion: ((Void) -> Void)? = nil ) {
...
}
class func hideWaitingViewSuccess(message: String? = nil, completion: ((Void) -> Void)? = nil) {
...
}
class func hideWaitingViewError(message: String? = nil, completion: ((Void) -> Void)? = nil) {
...
}
}
现在我们可以随便使用了,只需要import ProgressHUD,就可以通过ProgressHUD.xxx
这样的形式进行调用。同样的,当我哪天又看上某个漂亮的第三方库的时候,我只需要修改ProgressHUD
类中的实现,无需对全工程的代码进行替换。
从封装的角度来讲,现在这个版本已经找不出什么太大的问题了。通过封装类适配了第三方库,代码中对于使用哪个库并不关心,方便了代码维护。
前文说了,最近受《IF YOU'RE SUBCLASSING, YOU'RE DOING IT WRONG》的影响,心里又长草了,因为不喜欢每次调用接口都要加ProgressHUD.
这样的前缀,怀疑自己不是双子座♊️而是处女座♍️。
3.0版本
好吧,既然这样不如就尝试一下协议扩展。
import Foundation
import SomeProgressHUD
// MARK: - ProgressHUDable
protocol ProgressHUDable {
func showTextOnlyView(message: String!)
func showWaitingView(message: String?)
func hideWaitingView()
func hideWaitingViewDelay(interval: Double, completion: ((Void) -> Void)?)
func hideWaitingViewSuccess(message: String?, completion: ((Void) -> Void)?)
func hideWaitingViewError(message: String?, completion: ((Void) -> Void)?)
}
extension ProgressHUDable where Self: UIViewController {
func showTextOnlyView(message: String!) {
...
}
func showWaitingView(message: String?) {
...
}
func hideWaitingView() {
...
}
func hideWaitingViewDelay(interval: Double = 0.3, completion: ((Void) -> Void)? = nil ) {
...
}
func hideWaitingViewSuccess(message: String? = nil, completion: ((Void) -> Void)? = nil) {
...
}
func hideWaitingViewError(message: String? = nil, completion: ((Void) -> Void)? = nil) {
...
}
}
extension ProgressHUDable where Self: UIView {
...
}
需要使用的类就带上该协议
class LoginViewController: UIViewController, ProgressHUDable {
...
}
终于可以摆脱前缀了,感觉世界清静了。附上我现在使用的代码,没错我最近看上的是KVNProgress
。Async
是对GCD封装的语法糖,使用起来很方便,顺便推荐一下。
//
// ProgressHUDable.swift
// MyProject
//
// Created by chenjsa on 16/3/1.
// Copyright © 2016年 MyCompany. All rights reserved.
//
import Foundation
import KVNProgress
import Async
// MARK: - ProgressHUDable
protocol ProgressHUDable {
func showTextOnlyView(message: String!)
func showWaitingView(message: String?)
func hideWaitingView()
func hideWaitingViewDelay(interval: Double, completion: ((Void) -> Void)?)
func hideWaitingViewSuccess(message: String?, completion: ((Void) -> Void)?)
func hideWaitingViewError(message: String?, completion: ((Void) -> Void)?)
}
extension ProgressHUDable where Self: UIViewController {
func showTextOnlyView(message: String!) {
KVNProgress.configuration().circleSize = 0
KVNProgress.showErrorWithStatus(message)
}
func showWaitingView(message: String?) {
var msg = message
if ValidatorUtils.isEmptyString(msg) {
msg = NSLocalizedString("wait", comment: "")
}
KVNProgress.configuration().circleSize = 80
KVNProgress.showWithStatus(msg)
}
func hideWaitingView() {
KVNProgress.dismiss()
}
func hideWaitingViewDelay(interval: Double = 0.3, completion: ((Void) -> Void)? = nil ) {
Async.main(after: interval) { _ in
KVNProgress.dismissWithCompletion(completion)
}
}
func hideWaitingViewSuccess(message: String? = nil, completion: ((Void) -> Void)? = nil) {
var msg = message
if ValidatorUtils.isEmptyString(msg) {
msg = NSLocalizedString("success", comment: "")
}
KVNProgress.configuration().circleSize = 80
KVNProgress.showSuccessWithStatus(msg, completion: completion)
}
func hideWaitingViewError(message: String? = nil, completion: ((Void) -> Void)? = nil) {
var msg = message
if ValidatorUtils.isEmptyString(msg) {
msg = NSLocalizedString("error", comment: "")
}
KVNProgress.configuration().circleSize = 80
KVNProgress.showErrorWithStatus(msg, completion: completion)
}
}
extension ProgressHUDable where Self: UIView {
func showTextOnlyView(message: String!) {
KVNProgress.configuration().circleSize = 0
KVNProgress.showErrorWithStatus(message)
}
func showWaitingView(message: String?) {
var msg = message
if ValidatorUtils.isEmptyString(msg) {
msg = NSLocalizedString("wait", comment: "")
}
KVNProgress.configuration().circleSize = 80
KVNProgress.showWithStatus(msg)
}
func hideWaitingView() {
KVNProgress.dismiss()
}
func hideWaitingViewDelay(interval: Double = 0.3, completion: ((Void) -> Void)? = nil ) {
Async.main(after: interval) { _ in
KVNProgress.dismissWithCompletion(completion)
}
}
func hideWaitingViewSuccess(message: String? = nil, completion: ((Void) -> Void)? = nil) {
var msg = message
if ValidatorUtils.isEmptyString(msg) {
msg = NSLocalizedString("success", comment: "")
}
KVNProgress.configuration().circleSize = 80
KVNProgress.showSuccessWithStatus(msg, completion: completion)
}
func hideWaitingViewError(message: String? = nil, completion: ((Void) -> Void)? = nil) {
var msg = message
if ValidatorUtils.isEmptyString(msg) {
msg = NSLocalizedString("error", comment: "")
}
KVNProgress.configuration().circleSize = 80
KVNProgress.showErrorWithStatus(msg, completion: completion)
}
}
我是咕咕鸡,一个还在不停学习的全栈工程师。
热爱生活,喜欢跑步,家庭是我不断向前进步的动力。