作者 子豪 貝聊iOS工程師
前言
貝聊的移動(dòng)客戶(hù)端分別有家長(zhǎng)端和老師端诲锹,一家公司里同時(shí)維護(hù)多個(gè)業(yè)務(wù)上有關(guān)聯(lián)性的app這種情況其實(shí)很常見(jiàn)牙捉,例如一些提供 O2O 服務(wù)的公司,經(jīng)常會(huì)分用戶(hù)端和商家端恼五。這些客戶(hù)端雖然各自負(fù)責(zé)著一個(gè)業(yè)務(wù)環(huán)里面的不同部分蓝角,看似不相關(guān)阱穗,但其實(shí)內(nèi)在的設(shè)計(jì)、代碼都有很多共同之處使鹅。
我們編寫(xiě)代碼時(shí)一條最重要的軍規(guī)是 DRY (Don't Repeat Yourself)揪阶,意思就是同樣或者相似的代碼只寫(xiě)一次,通過(guò)代碼復(fù)用的技巧做成公用的組件患朱。項(xiàng)目工期緊張時(shí)鲁僚,其他的一些編碼守則都可以稍微變通一下,但唯獨(dú) DRY 是絕對(duì)要遵守的。這樣做最大的好處是當(dāng)發(fā)生需求變更冰沙、重構(gòu)或者修復(fù) bug 時(shí)侨艾,只要改動(dòng)一處的代碼就可以了。如果采用到處 copy 代碼的方式拓挥,則需要在每一處引用到的地方做修改蒋畜,很容易就會(huì)出現(xiàn)遺漏。并且時(shí)間一長(zhǎng)撞叽,這些復(fù)制的代碼很容易會(huì)漸行漸遠(yuǎn),衍生出許多不同的分支插龄,維護(hù)難度呈指數(shù)級(jí)上升愿棋。稍有經(jīng)驗(yàn)的程序員應(yīng)該都知道到處拷代碼就是挖坑的開(kāi)始,本文以一個(gè)較簡(jiǎn)單的 UI 組件為例均牢,介紹貝聊 iOS 組在設(shè)計(jì)可復(fù)用組件時(shí)的一點(diǎn)小技巧糠雨。
遇到的問(wèn)題
貝聊的家長(zhǎng)版和老師版針對(duì)的受眾不同,設(shè)計(jì)語(yǔ)言徘跪、配色等方面也有點(diǎn)不同甘邀,以最簡(jiǎn)單的自定義提示框?yàn)槔?/p>
家長(zhǎng)版:
老師版:
主要的不同點(diǎn):
- 按鈕的圓角半徑 (cornerRadius)
- 按鈕的大小、位置 (frame)
- 文字的字號(hào) (fontSize)
- 文字內(nèi)容到提示框邊界的距離 (contentInsets)
- 其實(shí)之前連按鈕的顏色都不一樣垮庐,不過(guò)最近UI改版了
初看起來(lái)不同點(diǎn)很多松邪,但仔細(xì)看其實(shí)只是一些設(shè)計(jì)上的元素有不同。事實(shí)上家長(zhǎng)版和老師版的提示框其實(shí)底層用的都是同一套代碼哨查,這個(gè)彈框組件BLAlertController
是我們 iOS 組一個(gè)新入行的小伙寫(xiě)的逗抑,很好地遵守了 DRY 原則,靈活性和代碼質(zhì)量都非常高寒亥。本文就用這個(gè)組件為例來(lái)說(shuō)說(shuō)邮府,怎樣在多個(gè) app 之間優(yōu)雅地復(fù)用代碼。
創(chuàng)建一個(gè)配置類(lèi)
先來(lái)看看初始化方法溉奕,alertController
的命名是仿照系統(tǒng)的UIAlertController
褂傀,但是因?yàn)?UI 是高度可定制的,所以多加入了很多參數(shù)加勤。
+ (instancetype)alertControllerWithTitle:(NSString *)title
message:(NSString *)message
buttonTextColor:(UIColor *)textColor
buttonBackgroundColor:(UIColor *)buttonBackgroundColor
cornerRadius:(CGFloat)cornerRadius
.... // 篇幅原因仙辟,點(diǎn)擊回調(diào)和其他配置項(xiàng)都省略,全部列出來(lái)的話超過(guò)二十項(xiàng)
這里遇到的第一個(gè)問(wèn)題就是參數(shù)列表過(guò)長(zhǎng)胸竞,Objective-C 沒(méi)有默認(rèn)參數(shù)也沒(méi)有方法重載欺嗤,如果每次初始化都要填寫(xiě)這一大堆參數(shù),這樣的組件也未免太難用了卫枝。
其實(shí) iOS SDK 的代碼里面就有很多優(yōu)秀的設(shè)計(jì)模式的應(yīng)用范例煎饼,遇到問(wèn)題的時(shí)候參考一下,會(huì)有很多收獲校赤。這里遇到的問(wèn)題主要是代碼架構(gòu)的問(wèn)題吆玖,發(fā)散一下筒溃,發(fā)現(xiàn) Foundation 框架的 NSURLSession
也是有很多可配置的屬性的。蘋(píng)果的工程師把這些可選參數(shù)專(zhuān)門(mén)構(gòu)造成了一個(gè)NSURLSessionConfiguration
來(lái)管理這些可配置屬性沾乘。創(chuàng)建一個(gè)NSURLSession
時(shí)怜奖,需要傳入一個(gè)NSURLSessionConfiguration
來(lái)指定一些參數(shù),而NSURLSessionConfiguration
的大部分屬性都是有默認(rèn)值的翅阵,例如timeoutIntervalForRequest
歪玲。通過(guò)NSURLSessionConfiguration.defaultSessionConfiguration
方法可以創(chuàng)建一個(gè)默認(rèn)的 configuration
,此時(shí)timeoutIntervalForRequest
的默認(rèn)值是60掷匠,這個(gè)值能適用于大部分情況滥崩。如果有特殊的需求也可以自行調(diào)整。
我們?cè)?9%的情況下其實(shí)都只是想用默認(rèn)樣式的彈框讹语,這時(shí)創(chuàng)建一個(gè)可定制的钙皮,帶默認(rèn)值的配置類(lèi)就是很好的解決方法。
依葫蘆畫(huà)瓢顽决,我們也創(chuàng)建一個(gè)BLAlertConfiguration
短条,定義大致如下:
@interface BLAlertConfiguration : NSObject <NSCopying> // 配置類(lèi)實(shí)現(xiàn)了深拷貝
@property (nonatomic) UIColor *buttonTextColor;
@property (nonatomic) UIColor *buttonBackgroundColor;
@property (nonatomic) CGFloat cornerRadius;
// 默認(rèn)的配置項(xiàng)
@property (class, nonatomic) BLAlertConfiguration *defaultConfiguration;
... //其他可配置項(xiàng)由于篇幅原因不一一列舉了
@end
@interface BLAlertController : UIViewController
- (instancetype)initWithTitle:(NSString *)title
message:(NSString *)message
configuration:(BLAlertConfiguration *)configuration;
- (instancetype)initWithTitle:(NSString *)title
message:(NSString *)message;
@end
BLAlertController
有兩個(gè)初始化方法,initWithTitle:message:
是個(gè) convenience initializer才菠,內(nèi)部調(diào)用了 initWithTitle:message:configuration:
并把BLAlertConfiguration.defaultConfiguration
傳進(jìn)去了茸时。所以一般的使用就很簡(jiǎn)單了,直接調(diào)用initWithTitle:message:
就好赋访。
在不同的項(xiàng)目中設(shè)置不同的默認(rèn)值
上面解決了參數(shù)列表過(guò)長(zhǎng)的問(wèn)題屹蚊,但是還是沒(méi)有說(shuō)明在兩個(gè)項(xiàng)目中怎么設(shè)置不同的默認(rèn) UI 風(fēng)格。答案其實(shí)呼之欲出进每,聰明的讀者應(yīng)該已經(jīng)想到了汹粤。
BLAlertConfiguration.defaultConfiguration
這個(gè)屬性是 Objective-C 新加的 class property 語(yǔ)法,用來(lái)打通 Swift 的類(lèi)屬性田晚。我們可以通過(guò)靜態(tài)變量和 getter setter嘱兼,把 defaultConfiguration
變成一個(gè)可讀可寫(xiě)的類(lèi)屬性。
@implementation BLAlertConfiguration
static BLAlertConfiguration *defaultConfiguration;
+ (void)setDefaultAlertConfiguration:(BLAlertConfiguration *)configuration {
if (defaultConfiguration) { //只允許設(shè)置一次贤徒,有值的時(shí)候返回
return;
}
defaultConfiguration = [configuration copy]; // 通過(guò)拷貝對(duì)象芹壕,避免配置項(xiàng)后面被修改
}
+ (instancetype)defaultConfiguration {
NSAssert(defaultConfiguration, @"未設(shè)置 defaultConfiguration,應(yīng)先調(diào)用 +[BLAlertConfiguration setDefaultAlertConfiguration:] 來(lái)進(jìn)行初始化");
return defaultConfiguration;
}
@end
這樣只要在程序啟動(dòng)的時(shí)候接奈,例如在 AppDelegate 的application:didFinishLaunchingWithOptions:
回調(diào)中設(shè)置一下 BLAlertConfiguration.defaultConfiguration
就可以了踢涌,在不同的項(xiàng)目中設(shè)置不同的默認(rèn)值,就能達(dá)成不同的設(shè)計(jì)風(fēng)格序宦。
BLAlertConfiguration *configuration = [BLAlertConfiguration new];
configuration.buttonTextColor = [UIColor blackColor];
configuration.buttonBackgroundColor = [UIColor yellowColor];
configuration.cornerRadius = 4.0;
BLAlertConfiguration.defaultAlertConfiguration = configuration;
結(jié)語(yǔ)
本文作為系統(tǒng)的首篇和引子睁壁,內(nèi)容相對(duì)簡(jiǎn)單,但是很好地體現(xiàn)了 DRY 的精神。如果你很少接觸這類(lèi)問(wèn)題潘明,這會(huì)是一個(gè)很好的開(kāi)始行剂。學(xué)會(huì)發(fā)現(xiàn)代碼中的壞味道,思考改進(jìn)的方法钳降,保持項(xiàng)目整潔是提升架構(gòu)設(shè)計(jì)能力的必經(jīng)之路厚宰。這種代碼復(fù)用的手法目前已經(jīng)貫穿了我們整個(gè)公共代碼庫(kù),并且有很多變體遂填,后面會(huì)陸續(xù)地介紹貝聊項(xiàng)目中其他關(guān)于代碼復(fù)用方面的心得铲觉,敬請(qǐng)期待。