[貝聊科技]iOS 程式碼架構(一)如何建立一個易複用的元件

貝聊科技發表於2019-03-01

前言

貝聊的移動客戶端分別有家長端和老師端,一家公司裡同時維護多個業務上有關聯性的app這種情況其實很常見,例如一些提供 O2O 服務的公司,經常會分使用者端和商家端。這些客戶端雖然各自負責著一個業務環裡面的不同部分,看似不相關,但其實內在的設計、程式碼都有很多共同之處。

我們編寫程式碼時一條最重要的軍規是 DRY (Don't Repeat Yourself),意思就是同樣或者相似的程式碼只寫一次,通過程式碼複用的技巧做成公用的元件。專案工期緊張時,其他的一些編碼守則都可以稍微變通一下,但唯獨 DRY 是絕對要遵守的。這樣做最大的好處是當發生需求變更、重構或者修復 bug 時,只要改動一處的程式碼就可以了。如果採用到處 copy 程式碼的方式,則需要在每一處引用到的地方做修改,很容易就會出現遺漏。並且時間一長,這些複製的程式碼很容易會漸行漸遠,衍生出許多不同的分支,維護難度呈指數級上升。稍有經驗的程式設計師應該都知道到處拷程式碼就是挖坑的開始,本文以一個較簡單的 UI 元件為例,介紹貝聊 iOS 組在設計可複用元件時的一點小技巧。

遇到的問題

貝聊的家長版和老師版針對的受眾不同,設計語言、配色等方面也有點不同,以最簡單的自定義提示框為例:

家長版:

老師版:

主要的不同點:

  1. 按鈕的圓角半徑 (cornerRadius)
  2. 按鈕的大小、位置 (frame)
  3. 文字的字號 (fontSize)
  4. 文字內容到提示框邊界的距離 (contentInsets)
  5. 其實之前連按鈕的顏色都不一樣,不過最近UI改版了

初看起來不同點很多,但仔細看其實只是一些設計上的元素有不同。事實上家長版和老師版的提示框其實底層用的都是同一套程式碼,這個彈框元件BLAlertController是我們 iOS 組一個新入行的小夥寫的,很好地遵守了 DRY 原則,靈活性和程式碼質量都非常高。本文就用這個元件為例來說說,怎樣在多個 app 之間優雅地複用程式碼。

建立一個配置類

先來看看初始化方法,alertController的命名是仿照系統的UIAlertController,但是因為 UI 是高度可定製的,所以多加入了很多引數。

+ (instancetype)alertControllerWithTitle:(NSString *)title 
                                         message:(NSString *)message
                               buttonTextColor:(UIColor *)textColor
                        buttonBackgroundColor:(UIColor *)buttonBackgroundColor
                                  cornerRadius:(CGFloat)cornerRadius
                   ....  // 篇幅原因,點選回撥和其他配置項都省略,全部列出來的話超過二十項複製程式碼

這裡遇到的第一個問題就是引數列表過長,Objective-C 沒有預設引數也沒有方法過載,如果每次初始化都要填寫這一大堆引數,這樣的元件也未免太難用了。

其實 iOS SDK 的程式碼裡面就有很多優秀的設計模式的應用範例,遇到問題的時候參考一下,會有很多收穫。這裡遇到的問題主要是程式碼架構的問題,發散一下,發現 Foundation 框架的 NSURLSession也是有很多可配置的屬性的。蘋果的工程師把這些可選引數專門構造成了一個NSURLSessionConfiguration來管理這些可配置屬性。建立一個NSURLSession時,需要傳入一個NSURLSessionConfiguration來指定一些引數,而NSURLSessionConfiguration的大部分屬性都是有預設值的,例如timeoutIntervalForRequest。通過NSURLSessionConfiguration.defaultSessionConfiguration方法可以建立一個預設的 configuration,此時timeoutIntervalForRequest的預設值是60,這個值能適用於大部分情況。如果有特殊的需求也可以自行調整。

我們在99%的情況下其實都只是想用預設樣式的彈框,這時建立一個可定製的,帶預設值的配置類就是很好的解決方法。

依葫蘆畫瓢,我們也建立一個BLAlertConfiguration,定義大致如下:

@interface BLAlertConfiguration : NSObject <NSCopying> // 配置類實現了深拷貝

@property (nonatomic) UIColor *buttonTextColor;
@property (nonatomic) UIColor *buttonBackgroundColor;
@property (nonatomic) CGFloat cornerRadius;

// 預設的配置項
@property (class, nonatomic) BLAlertConfiguration *defaultConfiguration;

... //其他可配置項由於篇幅原因不一一列舉了

@end

@interface BLAlertController : UIViewController

- (instancetype)initWithTitle:(NSString *)title
                      message:(NSString *)message
                configuration:(BLAlertConfiguration *)configuration;

 - (instancetype)initWithTitle:(NSString *)title
                       message:(NSString *)message;

@end複製程式碼

BLAlertController 有兩個初始化方法,initWithTitle:message: 是個 convenience initializer,內部呼叫了 initWithTitle:message:configuration:並把BLAlertConfiguration.defaultConfiguration傳進去了。所以一般的使用就很簡單了,直接呼叫initWithTitle:message:就好。

在不同的專案中設定不同的預設值

上面解決了引數列表過長的問題,但是還是沒有說明在兩個專案中怎麼設定不同的預設 UI 風格。答案其實呼之欲出,聰明的讀者應該已經想到了。

BLAlertConfiguration.defaultConfiguration 這個屬性是 Objective-C 新加的 class property 語法,用來打通 Swift 的類屬性。我們可以通過靜態變數和 getter setter,把 defaultConfiguration 變成一個可讀可寫的類屬性。

@implementation BLAlertConfiguration

static BLAlertConfiguration *defaultConfiguration;

+ (void)setDefaultAlertConfiguration:(BLAlertConfiguration *)configuration {
    if (defaultConfiguration) { //只允許設定一次,有值的時候返回
        return;
    }
    defaultConfiguration = [configuration copy]; // 通過拷貝物件,避免配置項後面被修改
}

+ (instancetype)defaultConfiguration {
    NSAssert(defaultConfiguration, @"未設定 defaultConfiguration,應先呼叫 +[BLAlertConfiguration setDefaultAlertConfiguration:] 來進行初始化");
    return defaultConfiguration;
}

@end複製程式碼

這樣只要在程式啟動的時候,例如在 AppDelegate 的application:didFinishLaunchingWithOptions:回撥中設定一下 BLAlertConfiguration.defaultConfiguration 就可以了,在不同的專案中設定不同的預設值,就能達成不同的設計風格。

BLAlertConfiguration *configuration = [BLAlertConfiguration new];
configuration.buttonTextColor = [UIColor blackColor];
configuration.buttonBackgroundColor = [UIColor yellowColor];
configuration.cornerRadius = 4.0;

BLAlertConfiguration.defaultAlertConfiguration = configuration;複製程式碼

結語

本文作為系統的首篇和引子,內容相對簡單,但是很好地體現了 DRY 的精神。如果你很少接觸這類問題,這會是一個很好的開始。學會發現程式碼中的壞味道,思考改進的方法,保持專案整潔是提升架構設計能力的必經之路。這種程式碼複用的手法目前已經貫穿了我們整個公共程式碼庫,並且有很多變體,後面會陸續地介紹貝聊專案中其他關於程式碼複用方面的心得,敬請期待。

相關文章