平常開發中必定會遇到這種場景,在某個類中提供了這樣一個方法:優化
@interface TTDoSomething:NSObject
+ (void)doSomethingWithName:(NSString *)name configA:(ConfigA)configA configB:(ConfigB)configB configC:(ConfigC)configC;
@end
複製代碼
這個方法經過四個參數name
, configA
, configB
, configB
提供了 doSomeThing
的功能,但是這個方法的參數實在是有點多,有的時候我只須要一個默認 config 就行了,並非每一個 config 都須要傳入一個值,這時候能夠把這個多參數的方法做爲全能方法,而後以它爲基礎實現幾個便利方法:ui
@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
複製代碼
可是這麼作的缺點也顯而易見:atom
今天忽然靈光一閃,想到能夠用鏈式表達式解決這個問題。在 iOS 開發領域由於 Masonry 這個庫的影響,鏈式表達式知名度已經很高,所以這篇文章再也不贅述其原理。直接進入主題: 首先聲明一個鏈式表達式的鏈釦類:spa
.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
// 實現全部的 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 類中增長一個方法:code
+ (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]
}
複製代碼
因而當咱們這樣調用:對象
[TTDoSomething doSomething:^(TTDoSomethingMaker *maker) {
maker.name(@"Demo").configA(ConfigAOne);
}];
複製代碼
就至關於調用了索引
[TTDoSomething doSomethingWithName:@"Demo" configA:ConfigAOne configB:0 configC:0];
複製代碼
若是此時想要將 ConfigB 配置爲 ConfigBOne, 只須要在原有的鏈式表達式後面加一個 .configB(ConfigBOne)
便可。 而若是想給萬能方法增長一個 ConfigD, 也只須要修改一下 doSomething:
和 TTDoSomethingMaker
的實現,完美地遵循了開閉原則。開發
上面 doSomething:
的實現裏我加了一個 // todo
的註釋,在這個位置能夠作一些默認實現,好比說合法性判斷和默認值:get
+ (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 能夠封裝一個宏,減小重複性的代碼:編譯器
#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
複製代碼
Masonry 利用鏈式表達式將繁瑣的 AutoLayout API 優化成了更表意、靈活、簡潔的語法。而鏈式表達式能夠隨意自由組合的特性,剛好也是 Objective-C 不支持方法默認參數的優雅解決方式,雖然和 Masonry 的使用場景不同,可是仔細想一想,這兩個場景都是對開閉原則的實現。
這篇文章在寫完後次日,同事也和我提到了參數對象
這個方案,當時和他進行了一番討論,最後結論仍是鏈式表達式更可取,原本犯懶不想補充在文章裏了,鑑於評論有同窗提到,我就再寫一點。
參數對象就是說聲明一個新的類,裏面收攏了全部的方法參數,相似這樣:
@interface TTDoSomethingObject
@property(nonatomic, copy) NSString *name;
@property(nonatomic, assign) ConfigA configA;
@property(nonatomic, assign) ConfigB configB;
@property(nonatomic, assign) ConfigC configC;
@end
複製代碼
而後新增一個方法:
- (void)doSomething:(TTDoSomethingObject *)object;
複製代碼
這樣確實在必定程度上解決了問題也遵循了開閉原則,可是鏈式表達式的方案能最大限度地利用 IDE 的提示/自動補全讓你少些不少代碼,先來看看上面的調用示例:
[TTDoSomething doSomething:^(TTDoSomethingMaker *maker) {
maker.name(@"Demo").configA(ConfigAOne);
}];
複製代碼
上面的代碼中,除了 maker 這個 block 參數須要額外地手動敲一個 m
, 其餘的代碼均可以經過 IDE 提示並補全,而若是使用參數對象
,代碼會是這樣:
[TTDoSomething doSomething:({
TTDoSomethingObject *object = TTDoSomethingObject.new;
object.configA = ConfigAOne;
object.name = @"Demo";
object;
})];
複製代碼
爲了美觀我這裏用了 ({})
的語法糖,能夠看出來要手動多寫不少代碼,在便利程度上孰優孰劣一目瞭然。 評論裏提到的方案是用 C struct 來作參數,這裏也舉個例子,我把上面的 Class 換成 C struct:
typedef struct TTDoSomethingObject {
NSString *name;
ConfigA configA;
...
}TTDoSomethingObject;
複製代碼
因而調用方式能夠變成:
[TTDoSomething doSomething:(TTDoSomethingObject){@"Demo", ConfigAOne, 0, 0}];
複製代碼
或者
//須要強轉否則會報錯
[TTDoSomething doSomething:(TTDoSomethingObject){.name = @"Demo", .configA = ...}];
複製代碼
再或者
TTDoSomethingObject object;
object.name = @"Demo";
object.configA = ConfigAOne;
[TTDoSomething doSomething:object];
複製代碼
再再或者
[TTDoSomething doSomething:^(TTDoSomethingObject *object) {
object->name = @"Demo";
object->configA = ConfigAOne;
...
}];
複製代碼
.n
時編譯器沒法索引出 .name
, 可是解決了省略參數的問題,然而仍是沒有默認參數值;有人可能會以爲了,不就手動多寫一點代碼嗎,這也算缺點?對此我想說,這篇文章的出發點就是爲了解決方法參數過長帶來的不便利,也就是說個人初衷就是不想手動多寫代碼,就這個目的來講,手動多寫一個字母都是更不具優點的方案。綜上,我認爲除了改寫編譯器 or 更換語言,鏈式表達式仍是最優解。歡迎討論