欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

简洁的代码

程序员文章站 2024-03-24 11:59:22
...

转载自 : 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的隐藏与否即可。

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