如何優(yōu)雅地解決 Objective-C 不支持方法默認(rèn)參數(shù)的問題

背景

日常開發(fā)中一定會遇到這種場景,在某個類中提供了這樣一個方法:

@interface TTDoSomething:NSObject

  • (void)doSomethingWithName:(NSString *)name configA:(ConfigA)configA configB:(ConfigB)configB configC:(ConfigC)configC;
    @end

這個方法通過四個參數(shù)name, configA, configB, configB提供了 doSomeThing的功能,可是這個方法的參數(shù)實在是有點多,有的時候我只需要一個默認(rèn) config 就好了,并不是每個 config 都需要傳入一個值,這時候可以把這個多參數(shù)的方法作為全能方法,然后以它為基礎(chǔ)實現(xiàn)幾個便利方法:

@interface TTDoSomething:NSObject

  • (void)doSomethingWithName:(NSString *)name configA:(ConfigA)configA;
  • (void)doSomethingWithName:(NSString *)name configB:(ConfigB)configB;
  • (void)doSomethingWithName:(NSString *)name configC:(ConfigC)configC;
  • (void)doSomethingWithName:(NSString *)name configA:(ConfigA)configA configB:(ConfigB)configB configC:(ConfigC)configC;
    @end

@implementation TTDoSomething

  • (void)doSomethingWithName:(NSString *)name configA:(ConfigA)configA {
    [self doSomethingWithName:name configA:configA configB:nil configC:nil];
    }

  • (void)doSomethingWithName:(NSString *)name configB:(ConfigB)configB {
    [self doSomethingWithName:name configA:nil configB:configB configC:nil];
    }

...

@end

但是這么做的缺點也顯而易見:

組合不靈活,如果需要增加 2 個種 config 組合的便利方法,需要增加新方法;
擴展不便,如果全能方法增加了新的配置 configD 需要在現(xiàn)有便利方法的基礎(chǔ)上增加更多的便利方法;

鏈?zhǔn)奖磉_(dá)式

今天突然靈光一閃,想到可以用鏈?zhǔn)奖磉_(dá)式解決這個問題。在 iOS 開發(fā)領(lǐng)域因為 Masonry 這個庫的影響,鏈?zhǔn)奖磉_(dá)式知名度已經(jīng)很高,因此這篇文章不再贅述其原理。直接進入主題:首先聲明一個鏈?zhǔn)奖磉_(dá)式的鏈扣類:

.h
@interface TTDoSomethingMaker:NSObject //這里命名也參考 Masonry, 定義為 Maker
// 聲明需要的 block 屬性,包括 Name 和 Config
@property (nonatomic, copy, readonly) TTDoSomethingMaker *(^name)(NSString *name);
@property (nonatomic, copy, readonly) TTDoSomethingMaker *(^configA)(ConfigA configA);
@property (nonatomic, copy, readonly) TTDoSomethingMaker *(^configB)(ConfigB configB);
@property (nonatomic, copy, readonly) TTDoSomethingMaker *(^configC)(ConfigC configC);

@end

.m
@interface TTDoSomethingMaker ()
// 在.m 中聲明保存 name 和 config 的屬性,這里不需要暴露給外部,所以不需要聲明在 .h
@property(nonatomic, copy) NSString *nameValue;
@property(nonatomic, assign) ConfigA configAValue;
@property(nonatomic, assign) ConfigB configBValue;
@property(nonatomic, assign) ConfigC configCValue;

@end

@implementation TTDoSomethingMaker

// 實現(xiàn)所有的 block getter

  • (NSString *)name {
    return ^TTDoSomethingMaker *(NSString *name) {
    self.nameValue = name;
    return self;
    };
    }

  • (ConfigA)configA {
    return ^TTDoSomethingMaker *(ConfigA configA) {
    self.configAValue = configA;
    return self;
    };
    }

  • (ConfigB)configB {
    return ^TTDoSomethingMaker *(ConfigB configB) {
    self.configBValue = configB;
    return self;
    };
    }

  • (ConfigC)configC {
    return ^TTDoSomethingMaker *(ConfigC configC) {
    self.configCValue = configC;
    return self;
    };
    }

@end

然后在提供功能的 TTDoSomething 類中增加一個方法:

  • (void)doSomething:(void(^)(TTDoSomethingMaker *maker))block {
    TTDoSomethingMaker *maker = TTDoSomethingMaker.new;
    block ?: block(maker);
    // todo
    [self doSomethingWithName:maker.nameValue configA:maker.configAValue configB:configBValue configC:configCValue]
    }

于是當(dāng)我們這樣調(diào)用:

[TTDoSomething doSomething:^(TTDoSomethingMaker *maker) {
maker.name(@"Demo").configA(ConfigAOne);
}];

就相當(dāng)于調(diào)用了

[TTDoSomething doSomethingWithName:@"Demo" configA:ConfigAOne configB:0 configC:0];

如果此時想要將 ConfigB 配置為 ConfigBOne, 只需要在原有的鏈?zhǔn)奖磉_(dá)式后面加一個 .configB(ConfigBOne) 即可。而如果想給萬能方法增加一個 ConfigD, 也只需要修改一下 doSomething: 和 TTDoSomethingMaker 的實現(xiàn),完美地遵循了開閉原則。

小優(yōu)化一發(fā)

上面 doSomething: 的實現(xiàn)里我加了一個 // todo 的注釋,在這個位置可以做一些默認(rèn)實現(xiàn),比如說合法性判斷和默認(rèn)值:

  • (void)doSomething:(void(^)(TTDoSomethingMaker *maker))block {
    TTDoSomethingMaker *maker = TTDoSomethingMaker.new;
    !block ?: block(maker);
    NSAssert(maker.nameValue, @"Parameter 'name' should not be nil.");
    // 將 ConfigB 配置為 ConfigBTwo
    if (maker.configBValue == 0) {
    maker.configBValue = ConfigBTwo;
    }
    [self doSomethingWithName:maker.nameValue configA:maker.configAValue configB:configBValue configC:configCValue]
    }

另外,上面的 block getter 可以封裝一個宏,減少重復(fù)性的代碼:

define TTDoSomethingMakerProperty(TYPE, NAME) - (TTDoSomethingMaker *(^)(TYPE))NAME {\

return ^TTDoSomethingMaker *(TYPE NAME) {
self.NAME##Value = NAME;
return self;
};
}\

@implementation TTDoSomethingMaker

TTDoSomethingMakerProperty(NSString *, name)
TTDoSomethingMakerProperty(ConfigA, configA)
TTDoSomethingMakerProperty(ConfigB, configB)
TTDoSomethingMakerProperty(ConfigC, configC)

@end

總結(jié)

Masonry 利用鏈?zhǔn)奖磉_(dá)式將繁瑣的 AutoLayout API 優(yōu)化成了更表意、靈活、簡潔的語法。而鏈?zhǔn)奖磉_(dá)式可以隨意自由組合的特性,恰好也是 Objective-C 不支持方法默認(rèn)參數(shù)的優(yōu)雅解決方式,雖然和 Masonry 的使用場景不一樣,但是仔細(xì)想想,這兩個場景都是對開閉原則的實現(xiàn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容