最近在review整个项目的代码,因为代码量很大,参与开发的人很多,所以代码很多地方写得不够简洁。这里总结出一些代码片段,用来简化代码。
最近在review整个项目的代码,因为代码量很大,参与开发的人很多,所以代码很多地方写得不够简洁。这里总结出一些代码片段,用来简化代码。
1、让TableView多余的Cell不可见。
原来的实现:
给TableView增加一个空的FooterView。但是当很多地方都需有这个需求时,类似的代码就重复出现。1
2
3UIView *view = [UIView new];
view.backgroundColor = [UIColor clearColor];
[tableView setTableFooterView:view];
改进的实现:
增加一个UITableView的category,这样在调用的地方只需一行就够了。1
2
3
4
5
6
7
8@implementation UITableView(Addtions)
- (void)hideEmptyCells
{
UIView *view = [UIView new];
view.backgroundColor = [UIColor clearColor];
[self setTableFooterView:view];
}
使用1
[self.tableView hideEmptyCells];
2、让TableView Cell之间的分隔线左间距为0
这个问题是iOS7之后才出现的,iOS7的解决方法到了iOS8又出问题,所以每次用到TableView, 都要给TableView设置一便,又要给TableViewCell设置一遍,代码很多很散。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//设置tableView
if ([self.tableView respondsToSelector:@selector(setSeparatorInset:)]) {
[self.tableView setSeparatorInset:UIEdgeInsetsZero];
}
if ([self.tableView respondsToSelector:@selector(setLayoutMargins:)]) {
[self.tableView setLayoutMargins:UIEdgeInsetsZero];
}
//设置tableviewcell
if ([cell respondsToSelector:@selector(setSeparatorInset:)]) {
[cell setSeparatorInset:UIEdgeInsetsZero];
}
if ([cell respondsToSelector:@selector(setLayoutMargins:)]) {
[cell setLayoutMargins:UIEdgeInsetsZero];
}
改进的实现:
给UITableView和UITableViewCell各增加一个Category方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//UITableView
- (void)hideSeparatorLeftInset
{
if ([self respondsToSelector:@selector(setSeparatorInset:)])
{
[self setSeparatorInset:UIEdgeInsetsZero];
}
if ([self respondsToSelector:@selector(setLayoutMargins:)])
{
[self setLayoutMargins:UIEdgeInsetsZero];
}
}
//UITableViewCell
- (void)hideSeparatorLeftInset
{
if ([self respondsToSelector:@selector(setLayoutMargins:)]) {
[self setLayoutMargins:UIEdgeInsetsZero];
}
}
然后在创建UITableView和UITableViewCell的地方分别调用就好了。1
2
3
4
5
6
7
8
9
10
11
12//创建Table
_newsTable = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT - 64 -49) style:UITableViewStylePlain];
_newsTable.delegate = self;
_newsTable.dataSource = self;
[_newsTable hideSeparatorLeftInset];
//创建Cell
if (cell == nil) {
cell = [[FindBigImageTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleCell];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
[cell hideSeparatorLeftInset];
}
这样做的好处:
- 代码简洁,只要一行。因为在很多类似的页面都会出现重复的代码,所以不同的人写往往会不一致,造成bug。
- 以后api升级,只要改一处地方即可,不用满世界的找代码然后修改。
总结:当你写代码的时候需要复制粘贴的时候,肯定是没有封装。想想能不能用Util方法和Category进行抽象,把不变的剥离出来,组成新的方法,这比复制粘贴好太多。
3、网络请求的封装
目前代码中的网络请求,是基于Http的,主要分成如下几个过程:
1、设置请求参数
2、调用封装好的http方法发出请求
3、对清求结果进行处理
典型用法如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43MBProgressHUD* hudProgress;
__block int result;
hudProgress = [[MBProgressHUD alloc] initWithWindow:[UIApplication sharedApplication].keyWindow];
hudProgress.labelText = @"XXX";
APIPackageLoginGoHeader *goHead = [[APIPackageLoginGoHeader alloc]init];
APIPackageLoginBackHeader *backHead = [[APIPackageLoginBackHeader alloc]init];
goHead.loame = @"XXXX";
goHead.paord = @"XXXX";
goHead.surce = @"XXXX
goHead.sin = @"XXXX";
goHead.versNo = @"XXXX";
[goHead makeDictionary];
NSMutableDictionary *dicBack = [[NSMutableDictionary alloc]init];[hudProgress showAnimated:NO whileExecutingBlock:^{
result = [GMPostServer GetServerBack:SERVER_LOGIN path_Param:nil query_Param:goHead.dicGo body_Param:nil method:GM_NETWORK_METHOD_POST returnValue:dicBack];
[backHead getBodyDataItems:dicBack];
}completionBlock:^{
if (result == GM_POSTBACK_SUCCESS)
{
[UserInfoEntity shareEntity].pne = backHead.ph;
[UserInfoEntity shareEntity].niame = backHead.name;
[UserInfoEntity shareEntity].hasLogin = YES;
[UserInfoEntity shareEntity].userId = [NSString stringWithFormat:@"%@",backHead.userId];
[UserInfoEntity shareEntity].state = backHead.state;
if ([launchOptions objectForKey:@"UIApplicationLaunchOptionsRemoteNotificationKey"] != nil) {
self.pushInfo = [launchOptions objectForKey:@"UIApplicationLaunchOptionsRemoteNotificationKey"];
BasicHomeViewController *basicVc = (BasicHomeViewController*)self.window.rootViewController;
UINavigationController *viewController = (UINavigationController *)[[basicVc.tabbar.buttonData objectAtIndex:basicVc.tabbar.selectIndex] viewController];
[viewController dismissViewControllerAnimated:NO completion:nil];
[[PushEventManager sharedInstance] pushEvent:self.pushInfo target:viewController];
[APService setBadge:0];
[UIApplication sharedApplication].applicationIconBadgeNumber = 0;
}
[self requestUserInfo];
}
else
{
showErroMsg(backHead.errorMsg);
}
}];
上述代码是用户登录,在ViewController里面的,有一个很大的特点:长。而且集合了参数准备,发请求,请求结果处理,还和UI相互耦合。
试想一下,用户登录不会只在一个界面里有,如果多个地方存在登录的情况,这一块代码就会多次出现,而且大致结构都差不多吧?只有参数的取值和成功失败的处理逻辑不太一样,那么就把剩下的固定的逻辑:参数复制,hud,请求放到一个方法里面:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57+ (void)userLoginWithUserName:(NSString*)userName
password:(NSString*)password
success:(void(^)(APIPackageLoginBackHeader*))sucBlock
failure:(void(^)(APIPackageLoginBackHeader*))failBlock
animated:(BOOL)animated
loadingText:(NSString*)loadingText
inView:(UIView*)containerView;
{
MBProgressHUD* hudProgress;
__block NSInteger result;
hudProgress = [[MBProgressHUD alloc] initWithWindow:[UIApplication sharedApplication].keyWindow];
if (animated) {
[containerView addSubview:hudProgress];
[containerView bringSubviewToFront:hudProgress];
hudProgress.labelText = loadingText;
}
APIPackageLoginGoHeader *goHead = [[APIPackageLoginGoHeader alloc]init];
APIPackageLoginBackHeader *backHead = [[APIPackageLoginBackHeader alloc]init];
goHead.logame = userName;
goHead.pard = password;
goHead.sodde = @"iphone";
goHead.swn = @"ios";
goHead.versdddo = [LCSystemUtil appVersion];
[goHead makeDictionary];
NSMutableDictionary *dicBack = [[NSMutableDictionary alloc]init];
[hudProgress showAnimated:animated whileExecutingBlock:^{
result = [GMPostServer GetServerBack:SERVER_LOGIN path_Param:nil query_Param:goHead.dicGo body_Param:nil method:GM_NETWORK_METHOD_POST returnValue:dicBack];
[backHead getBodyDataItems:dicBack];
}completionBlock:^{
if (result == GM_POSTBACK_SUCCESS)
{
[UserInfoEntity shareEntity].phone = backHead.pe;
[UserInfoEntity shareEntity].nikename = backHead.niccdame;
[UserInfoEntity shareEntity].hasLogin = YES;
[UserInfoEntity shareEntity].userId = [NSString stringWithFormat:@"%@",backHead.uddrId];
[UserInfoEntity shareEntity].state = backHead.stcde;
if(sucBlock){
sucBlock(backHead);
}
}
else
{
if (failBlock) {
failBlock(backHead);
}
}
}];
}
外部调用,只要一行代码:1
2
3
4
5
6
7
8
9
10
11
12[HJUserProvider userLoginWithUserName:userName
password:password
success:^(APIPackageLoginBackHeader *backHeader) {
[self backButtonPressed:nil];
[HJUserProvider requestUserInfoWithSuccess:nil failure:nil];
}
failure:^(APIPackageLoginBackHeader *backHeader) {
[HJUIUtil showFailedMsg:backHeader.errorMsg];
}
animated:YES
loadingText:NSLocalizedString(@"Logging now...", nil)
inView:self.view];
这样的做法就把网络请求从ViewController分离开了,而且没有任何的耦合。同一个接口的请求逻辑只需写一遍(之前是copy、paste,因为代码不是唯一的,所以不同的人,不同的时间改了了其中一处,就会造成差异,引起未知的bug)。另外一个好处就是把各种请求逻辑都集中在了一处,便于阅读和修改。
4、数据归数据,UI归UI
从本质上说,程序就分成两部分:数据和UI。从ViewController的角度来说,就是State和View。View随着State的改变而改变,所以两者应该分开使ViewController结构更加清晰。
举个例子:viewDidLoad方法,这个方法里应该写些什么?
This method is called after the view controller has loaded its view hierarchy into memory. This method is called regardless of whether the view hierarchy was loaded from a nib file or created programmatically in the loadView method. You usually override this method to perform additional initialization on views that were loaded from nib files.
官方的建议是写视图上的额外的初始化工作,而和视图不相关的初始化工作就不该在这里写(题外话,iOS6之前,遇到内存警告,viewDidLoad是会调多次的,所以把对象的初始化写在这里是有问题的,iOS6之后反正没发现过viewDidLoad调用多次的情况),那么对象的初始化(状态变量的复制)应该写在哪里呢?——初始化方法!不要因为基类提供了默认的初始化方法而偷懒不写,任何一个类,除非简单的不能再简单,都要有一个初始化方法,哪怕里面什么都没写。下面这段代码的问题就是上班部分代码应该出现在初始化方法中。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39- (void)viewDidLoad {
[super viewDidLoad];
_newsDataAry = [[NSMutableArray alloc] init];
_currentPage = 0;
//获取缓存文章
NSArray *arr = [[ArticleSqliteData shareManager] dataGetHeadArticle];
[_newsDataAry addObject:arr];
NSArray *arrNewsInfo = [[ArticleSqliteData shareManager] dataHomeViewIsFirstGet:YES];
[_newsDataAry addObjectsFromArray:arrNewsInfo];
if ([arrNewsInfo count] != 0) {
ArticleModel *model = [arrNewsInfo lastObject];
_isExpire = [self calculationTime:model.updateTime];
}
else{
_isExpire = YES;
}
//上面的代码和对象的状态相关,不该出现在这里
[self addTableView];
[self setTitleViewWithText:@"发现"];
[self setLeftButtonWithTitle:@"秘籍" action:@selector(showCheats)];
[self setRightButtonWithImageName:@"find_xiala.png" action:@selector(showCatagory)];
self.showCategory = NO;//这一行也是状态
// Do any additional setup after loading the view from its nib.
/**
* 初始化分类页面
*/
self.categoryView = [[HJFindCategoryView alloc] initWithFrame:CGRectMake(0, 64, SCREEN_WIDTH, SCREEN_HEIGHT)];
[self.categoryView addBorderTopLine];
self.categoryView.hjFindCategoryViewDelegate = self;
self.categoryView.hidden = YES;//UI应该依赖状态,而不是写死
[[AppDelegate appDelegate].window addSubview:self.categoryView];
[self getBannerNewsTitle];
}
5、成员方法与Util方法
一个类代码比较多的一个原因,就是加入了不该加入的成员方法所致。一个方法,之所以称为成员方法,是因为它操作了对象的状态,如果它没有操作对象的状态,拿它就跟该对象没有紧密的关系,就不能放在类里面实现。举个例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20-(BOOL)calculationTime:(NSDate *)date
{
NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
NSDate * senddate=[NSDate date];
//结束时间
NSDate *endDate = date;
//当前时间
NSDate *senderDate = [dateFormatter dateFromString:[dateFormatter stringFromDate:senddate]];
//得到相差秒数
NSTimeInterval time=[senderDate timeIntervalSinceDate:endDate];
int days = ((int)time)/(3600*24);
if (days < 0) {
return YES;
}
else{
return NO;
}
}
上面这个方法和对象没什么关系,更应该抽象成为一个Util方法,可以在这个类里面调用,更可以再其他更多的地方调用。
6、功能点单一入口
在代码中,有些功能逻辑是相对固定的,但是在很多地方会反复出现。
比如登录功能,除了用户主动登录,还有在未登录状态下使用某些需要登录的功能,都会去调用登录页面。我看了一下现在的代码,一共出现了14次调用登录页面。代码重复是一个方面,还有就是定位bug麻烦。比如进入首页的时候莫名其妙弹出个登录页面,因为首页逻辑复杂,我得打很多断点才能定位到产生问题的代码。
还有对tabbar的操作,也是出现在了多出地方。其实只要搞清楚tabbar是哪个类管理的,然后让这个类封装出一个public的方法,控制tabbar的隐藏与否即可。
所以一个功能点我们只保留一个出口,其他地方只要调用方法即可。把相同的地方写在方法内部,不同的地方作为参数对外开放。