简洁的代码

转载自 : http://www.jianshu.com/p/2db0e6b6ecdb

最近在review整个项目的代码,因为代码量很大,参与开发的人很多,所以代码很多地方写得不够简洁。这里总结出一些代码片段,用来简化代码。

1、让TableView多余的Cell不可见。
原来的实现:
给TableView增加一个空的FooterView。但是当很多地方都需有这个需求时,类似的代码就重复出现。

UIView *view = [UIView new];
view.backgroundColor = [UIColor clearColor];
[tableView setTableFooterView:view];

改进的实现:
增加一个UITableView的category,这样在调用的地方只需一行就够了。

@implementation UITableView(Addtions)

- (void)hideEmptyCells
{
    UIView *view = [UIView new];
    view.backgroundColor = [UIColor clearColor];
    [self setTableFooterView:view];
}

使用

[self.tableView hideEmptyCells];

2、让TableView Cell之间的分隔线左间距为0
这个问题是iOS7之后才出现的,iOS7的解决方法到了iOS8又出问题,所以每次用到TableView, 都要给TableView设置一便,又要给TableViewCell设置一遍,代码很多很散。

//设置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方法:

//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的地方分别调用就好了。

//创建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、对清求结果进行处理
典型用法如下:

 MBProgressHUD* 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,请求放到一个方法里面:

+ (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);
            }
        }
    }];
}

外部调用,只要一行代码:

[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调用多次的情况),那 么对象的初始化(状态变量的复制)应该写在哪里呢?——初始化方法!不要因为基类提供了默认的初始化方法而偷懒不写,任何一个类,除非简单的不能再简单, 都要有一个初始化方法,哪怕里面什么都没写。下面这段代码的问题就是上班部分代码应该出现在初始化方法中。

- (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方法
一个类代码比较多的一个原因,就是加入了不该加入的成员方法所致。一个方法,之所以称为成员方法,是因为它操作了对象的状态,如果它没有操作对象的状态,拿它就跟该对象没有紧密的关系,就不能放在类里面实现。举个例子:

-(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的隐藏与否即可。

所以一个功能点我们只保留一个出口,其他地方只要调用方法即可。把相同的地方写在方法内部,不同的地方作为参数对外开放。

posted on 2015-08-18 16:38  奋进的闹钟  阅读(298)  评论(0编辑  收藏  举报

导航