iOS程式碼重構的一些方法

NO IMAGE

最近專案已到尾聲了。對於這個只是換湯不換藥的新專案來說,開始接手的時候是有些抵觸情緒的。經過熟悉了幾個月,出於對職業的責任心,開始一直在公司的iOS討論組裡面分享知識,鼓勵大家一起去改變這一切。希望能把共同認為是好的方式帶進來。下面是最近進行程式碼重構的一些心得。

刪掉死程式碼

諸如以下這些:

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
// Custom initialization
}
return self;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}

使用Category

我們平時寫UITableView是這樣的:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = @"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
//do something here
return cell;
}

重構後:

.h檔案
@interface UITableView (dequeue)
- (UITableViewCell *)dequeueCell;
@end

.m檔案

@implementation UITableView (dequeue)
- (UITableViewCell *)dequeueCell
{
static NSString *cellIdentifier = @"CellIdentifier";
UITableViewCell *cell = [self dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
return cell;
}
@end
//使用
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueCell];
//do something here
return cell;
}

使用NSDictionary對映字串等

重構前:
    switch (expression) {
case constant1:
return @"result1";
case constant2:
return @"result2";
...
case constantN:
return @"resultN";
default:
return @"default";
}

重構後:

    static NSDictionary *resultMap;
if (!resultMap) {
resultMap = @{@(constant1) : @"result1",
@(constant2) : @"result2",
...
@(constantN) : @"resultN"};
}
return resultMap[expression] ?: @"default";

是不是邏輯上變得更清晰了呢?

Objective-C 動態方法

`NSSelectorFromString` 與 `[NSObject performSelector:]`

一般我們是這樣寫

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0) {
[self func0];
} else if (indexPath.row == 1) {
[self func1];
} else if (indexPath.row == N) {
[self funcN];
} else {
[self doSomething];
}
}

重構後

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
SEL funcSEL = NSSelectorFromString([NSString stringWithFormat:@"func%d", indexPath.row]);
if ([self respondsToSelector:funcSEL]) {
[self performSelector:funcSEL];
} else {
[self dosomething];
}
}

加上 前一點 用NSDictionary 來對映字串,是不是又多了一分想象力呢?

PS:基於以上兩點,如果UITableView展示一些客戶端固有的屬性,可以將資料與點選的響應函式組織成一個字典,放在dataSource裡面。

delegate 與 block

介面使用delegate好還是block好呢?個人選擇如下:

對於那些block的生命週期裡不會出現迴圈引用的優先選擇block。否則選擇delegate。

比如,`[NSArray enumerateObjectsUsingBlock:]`和UITableView的delegate兩者。`[NSArray enumerateObjectsUsingBlock:]`中,即使有迴圈引用存在,也將在block在使用完畢後釋放。而UITableView中,如果用block,出現迴圈引用需要自己打破。

基於以上選擇規則,UIAlertView跟UISheetView中使用block也是可以的。重構之。

其它一些規則:

杜絕魔數。

使用NS_ENUM來列舉有規則的數。

儘量把變數的宣告、定義放在最接近使用的地方。

使用最新的OC語法。

儘量精簡方法。