如何让你的代码写的更优雅?(持续更新)

96
Link913
0.8 2017.07.06 17:22* 字数 2028

前言

以前写代码也仅仅是注意C语言一些基本的规范还有驼峰命名法什么的,后来看了一些书籍,也写过不少代码,review过不少别人的代码,所以把自己的一些感悟记一下吧.在本文的最后我会把我们老大写的集团的iOS端编码规范放一下,第一部分的那个新特性的规范的demo就是把我之前的代码改了下,哈哈.

这篇文章会持续更新,这里我会把我文章最后除了附录中的集团的代码规范外自己的东西会写出来.再看这篇文章前推荐看两本书Objective-C高级编程 iOS与OS X多线程和内存管理,Effective Objective-C 2.0:编写高质量iOS与OS X代码的52个有效方法,第二本书读完写了篇读书笔记,这是链接

自己的一些编码技巧和代码规范(2017.7.6版)

tableview中cell的复用id

  • 1在需要使用的控制器内这么写,使用static修饰,但这样你如果换了个控制器还得再写一遍很僵硬,不是很推荐.

    static NSString *baseInfomationSubCellID = @"CBaseInfoSubCell";
    
  • 2在cell头文件上声明一个常量,采用extern来修饰,这有个优点,就是当你的cell需要在多个控制器上使用时,会很方便.

      //头文件,你要是使用苹果的extern当然也可以
      extern NSString *const CBASE_INFOMATION_CELLID;
      //实现文件
      NSString *const CBASE_INFOMATION_CELLID = @"CBaseInfomationCell";
    
  • 3在UITableViewCell的基类或者分类中写一个类方法,能够获取cell的类名
    + (NSString *)getCellIDStr
    {
    return NSStringFromClass([self class]);
    }

控件可以这样命名,这样联想起来更加的方便

比如说一个姓名label你可以起名为lb_name,这样当你想调用他的时候是不是更方便呢?公司现在的要求是写成nameLabel的.

条件语句或者参数过多时请换行对齐

        [[KLPrestoClient prestoClient] postBaseInfoMationWithEducation:_postParaFlags.education
                                                         monthlyIncome:_postParaFlags.monthlyIncome
                                                        professionType:_postParaFlags.jobInfo
                                                              marriage:_postParaFlags.marriage
                                                              cityName:_cityNameStr
                                                           cityAddress:_addressStr
                                                              latitude:_latitudeStr
                                                             longitude:_longitudeStr
                                                       isProvidentFund:providentFundModel.selectedIndex
                                                          eMailAddress:_EMailAddrStr
                                                      censusIsLiveCity:censusModel.selectedIndex
                                                                 isHud:YES
                                                               success:^(BaseModel *model) {
                                                                   
            if ([model.status isEqualToString:@"000"]) {
                
                //上传数据成功
                [self.navigationController popViewControllerAnimated:YES];
                
            } else {
                
            }
            
        } failture:^(id error) {
        }];

尽量多的利用参数和返回值,尽量不要使用全局变量

    /**
     对城市按照首字母分组
     */
    - (NSArray *)p_makeCityGroup:(NSArray *)cityArray{
        NSMutableArray *letterDictArray = [NSMutableArray array];
        //创建26个数组用来存储
        for (int i = 0; i < 26; i ++) {
            NSMutableArray *letterCityArray = [NSMutableArray array];
            [letterDictArray addObject:letterCityArray];
        }
        
        for (CCityModel *model in cityArray) {
            
            //将城市首字母转化为asic码
            int asciiCode = [model.cityCode characterAtIndex:3];
            int index = asciiCode - 97;
            [letterDictArray[index] addObject:model];
        }
        return [letterDictArray copy];
    }

在一些需求巧妙地利用完全初始化方法,不要将不需要外部改变的属性readwrite

    /**
     普通model
     */
    - (instancetype)initNormalModelWithTitle:(NSString *)title answer:(NSString *)answer answerArray:(NSArray *)answerArray {

        return [self initBaseInfomationWithTitle:title
                                          answer:answer
                                   selectedIndex:NO_CHOOSE_ANSWER_INDEX
                                           isMap:NO
                                     answerArray:answerArray
                              isTwoChooseBtnCell:NO
                              isEMailAddressCell:NO];
    }

    /**
     带地图的model
     */
    - (instancetype)initMapModelWithTitle:(NSString *)title answer:(NSString *)answer {
        
        return [self initBaseInfomationWithTitle:title
                                          answer:answer
                                   selectedIndex:NO_CHOOSE_ANSWER_INDEX
                                           isMap:YES
                                     answerArray:nil
                              isTwoChooseBtnCell:NO
                              isEMailAddressCell:NO];
    }

    /**
     二选一model
     */
    - (instancetype)initTwoChooseOneModelWithTitle:(NSString *)title answerArray:(NSArray *)answerArray {
        //index传-1表示二选一未选择
        return [self initBaseInfomationWithTitle:title
                                          answer:@""
                                   selectedIndex:NO_CHOOSE_ANSWER_INDEX
                                           isMap:NO
                                     answerArray:answerArray
                              isTwoChooseBtnCell:YES
                              isEMailAddressCell:NO];
    }

    /**
     带邮箱的model
     */
    - (instancetype)initEMailModelWithTitle:(NSString *)title defaultAnswer:(NSString *)defaultAnswer{
        //index传-1表示未输入
        return [self initBaseInfomationWithTitle:title
                                          answer:defaultAnswer
                                   selectedIndex:NO_CHOOSE_ANSWER_INDEX
                                           isMap:NO
                                     answerArray:nil
                              isTwoChooseBtnCell:NO
                              isEMailAddressCell:YES];
    }

    /**
     完全初始化
     */
    - (instancetype)initBaseInfomationWithTitle:(NSString *)title answer:(NSString *)answer selectedIndex:(NSInteger)selectedIndex isMap:(BOOL)isMap answerArray:(NSArray *)answerArray isTwoChooseBtnCell:(BOOL)isTwoChooseBtnCell isEMailAddressCell:(BOOL)isEMailAddressCell{
        if (self = [super init]) {
            _title = title;
            _answer = answer;
            _selectedIndex = selectedIndex;
            _isMap = isMap;
            _answerArray = answerArray;
            _isTwoChooseBtnCell = isTwoChooseBtnCell;
            _isEMailAddressCell = isEMailAddressCell;
        }
        return self;
    }

    @end

附录(集团iOS端代码规范)

Objective-C 编程风格指南

Controller注释、命名示例

  • 1、请在类文件顶部注明作者姓名
  • 2、控件命名请使用 控件作用拼接数据类型 (例如:UIButton => settingButton UIButton => submitButton UILabel => companyLabel )
  • 3、一个方法中代码尽量不要超过20行,如超过请考虑按职责将代码拆分到子方法中,保障主方法流程清晰
  • 4、类、类成员(变量或方法)顶部应添加注释说明清楚类的职责、变量的作用或者方法的提供的功能
  • 5、方法内注释请使用 双斜杠加空格再加说明文案
//
//  JLaunchViewController.m
//  J1000
//
//  Created by 作者姓名 on 17/2/8.
//  Copyright © 2017年 user. All rights reserved.
//

#import "JLaunchViewController.h"

@interface JLaunchViewController ()<UIScrollViewDelegate>

/*
 * 引导页位置指示器
 */
@property (weak, nonatomic) IBOutlet UIPageControl *dotPageControl;

/*
 * 引导图容器
 */
@property (weak, nonatomic) IBOutlet UIScrollView *intrScrollView;

/*
 * 进入按钮
 */
@property (weak, nonatomic) IBOutlet UIButton *nextButton;

@end

@implementation JLaunchViewController{
    /*
     * 引导图数组
     */
    NSMutableArray * _imageArray;
}

- (void)viewDidLoad {
    [super viewDidLoad];

    // 初始化界面
    [self initView];
    
    // 数据绑定()
    [self dataBind];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - UI
/*
 * 初始化界面
 */
- (void)initView{
    
    // 设置scrollView属性(隐藏水平滚动条,边框不反弹、整页翻动、关联代理类)
    self.intrScrollView.showsHorizontalScrollIndicator = NO;
    self.intrScrollView.bounces = NO;
    self.intrScrollView.pagingEnabled = YES;
    self.intrScrollView.delegate = self;
    
    // 设置pageControl属性(设置指示圆点默认色值与选中色值、背景颜色、整页翻动后再切换当前位置)
    self.dotPageControl.currentPageIndicatorTintColor = [UIColor blackColor];
    self.dotPageControl.pageIndicatorTintColor = [UIColor whiteColor];
    self.dotPageControl.backgroundColor = [UIColor clearColor];
    self.dotPageControl.defersCurrentPageDisplay = YES;
    
    // 默认隐藏底部按钮
    self.nextButton.hidden = YES;
}

/*
 * 数据绑定
 */
- (void)dataBind{

    // 测试
    _imageArray =  [[NSMutableArray alloc]initWithObjects:@1, @2, @2, @2, @2, nil];
    
    // 设置ScrollView内容的宽度与高度
    self.intrScrollView.contentSize = CGSizeMake(ScreenWidth * _imageArray.count, ScreenHeight);
    
    // 循环添加图片
    for (int i = 0; i< _imageArray.count ; i++) {
        UIImageView * imageView = [[UIImageView alloc] initWithFrame:CGRectMake(ScreenWidth * i, 0, ScreenWidth, ScreenHeight)];
        imageView.contentMode = UIViewContentModeScaleAspectFit;
        imageView.backgroundColor = i == 0 ? [UIColor greenColor] : [UIColor orangeColor];
        [self.intrScrollView addSubview:imageView];
    }
    
    // 设置pageControl圆点总数与当前位置
    self.dotPageControl.numberOfPages = _imageArray.count;
    self.dotPageControl.currentPage = 0;
    
}
#pragma mark - target
/*
 * 进入按钮单击事件处理函数
 */
- (IBAction)didClickNextBtn:(id)sender {
    
    if (self.loginSwitchVCBlock) {
        self.loginSwitchVCBlock();
    }
}

#pragma mark - delegate
/*
 * 滚动视图滚动事件处理函数
 */
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    // 当拖动偏移量超过半屏时切换圆点位置
    self.dotPageControl.currentPage = (int) (scrollView.contentOffset.x + ScreenWidth/2) /ScreenWidth;
    
    // 判断是否隐藏进入按钮
    self.nextButton.hidden = !(self.dotPageControl.currentPage == _imageArray.count - 1);
}

@end

Xcode项目

  • 为避免文件混乱,实际的物理文件应和Xcode项目保持一直。在Xcode中所创建的任何group都应有文件系统中相对应的文件夹。不应仅根据文件类型来进行分组,还需要考虑到其作用。

简写

  • 较长的单词可取单词的头几个字母形成缩写
  • 仅在变量命名,图片命名中使用
名称 缩写
infomation info
message msg
descriptation desc

类的命名

  • 大驼峰式命名:类的命名的时候,每个单词的首字母都采用大写字母。(项目简写+名称+后缀)。
要求 例如
ViewController 使用Controller做后缀 例子: DDFHomeController
View 使用View做后缀 例子: DDFAlertView
UITableCell 使用Cell做后缀 例子: DDFNewsCell
Protocol 使用Delegate或者DataSource作为后缀 例子: UITableViewDelegate
Model 使用model作为后缀 例子: DDFDataModel
单例 manager 例子: DDFUserInfoManager
类的拓展 类名+项目简写+extra 例子: NSString+DDFExtra

宏命名

要求 例如
宏定义方法 小驼峰命名 例子: getImageUrl(url) [NSURL URLWithString:[NSString stringWithFormat:@"%@%@",DDFBaseUrl,url]]
宏定义常量 遵循大驼峰命名(开头以项目简写) 例子:#define DDFWidth self.frame.size.width, KScreenWidth [UIScreen mainScreen].bouds.size.width

变量的命名

  • 用修饰+类型的方式 ,类型简写参考简写。
要求 例如
用修饰+类型的方式 (小驼峰式命名) UIButton *settingButton

property变量

要求 例如
小驼峰式命名 @property (strong, nonatomic) NSSNumber *userId;
NSString 使用copy @property (copy, nonatomic) NSString *userName;
所有属性特性应该显式地列出来 @property (weak, nonatomic) IBOutlet UIView *containerView;

图片命名

  • 可以进行简写 nav,btn,bg,imagev
要求 例如
功能名称_____控件类型 ___自定义参数 share_____btn _weixin

枚举类型

  • 项目简写开头,跟随其后的命名应采用驼峰命名法则,命名应准确表述枚举表示的意义,枚举中各个值都应以定义的枚举类型开头,其后跟随各个枚举值对应的状态、选项或者状态码。
typedef NS_ENUM(NSInteger, DDFRequestState) {
    DDFRequestStateInactive,
    DDFRequestStateLoading
}; 

方法命名

  • 使用小驼峰法,规范的方法名应该看起来像一个完整的句子,读过便知函数作用。

  • 在(-/+ )符号之后应加上一个空格

- (void)setExampleText:(NSString *)text image:(UIImage *)image;
+ (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;

代理命名

  • 名称以标示发送消息的对象的类名开头,省略类名的前缀并⼩小写第⼀个字⺟
- (BOOL)tableView:(NSTableView*)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender 
           openFile:(NSString *)filename;
  • 冒号紧跟在类名之后(随后的那个参数表⽰委派的对象)。该规则不适用于只有一个 sender 参数的⽅法
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;

代码组织

  • 在函数分组和protocol/delegate实现中使用#pragma mark -来分类方法,要遵循以下一般结构:
  • 结构按照功能模块划分
  1. 懒加载
  2. 控制器生命周期。
  3. 私有方法
  4. 通知方法
  5. 代理方法。
  6. 公共方法。
  7. 触发事件。

#pragma mark - lazy

- (NSArray *)array {
    if(!_array) {
    
    }
    return _array;
}



#pragma mark - Lifecycle

- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}

#pragma mark - Private

- (void)privateMethod {}

#pragma mark - NSCopying
- (void)didReceiveNotification {}

#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate

#pragma mark - Custom Accessors
- (void)setCustomProperty:(id)value {}
- (id)customProperty {}

#pragma mark - Public
- (void)publicMethod {}
#pragma mark - IBActions

- (IBAction)submitData:(id)sender {}

点表示法

  • 应“仅”用于获取和改变属性。括号表示法用于所有其它实例。
应该:
NSInteger arrayCount = self.array.count;
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;

不应该:
NSInteger arrayCount = [self.array count];
[view setBackgroundColor:[UIColor orangeColor]];
[[UIApplication sharedApplication] delegate];

空格

  • 类方法声明在方法类型与返回类型之间要有空格。
- (void)methodName:(NSString *)string;

  • 条件判断的括号内侧不应有空格。
if (a < b) {
    // something
}

  • 关系运算符(如 >=、!=)和逻辑运算符(如 &&、||)两边要有空格。
(someValue > 100)? YES : NO

Literals字面量

  • 在创建NSString,NSDictionary,NSArrayNSNumber等对象的immutable实例时,应使用字面量。需要注意的是,不应将nil传递给NSArrayNSDictionary字面量,否则会引起程序崩溃。
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018; 

Case语句

  • 大括号在case语句中并不是必须的,除非编译器强制要求。当一个case语句包含多行代码时,大括号应该加上。
switch (condition) {
  case 1:
    // ...
    break;
  case 2: {
    // ...
    // Multi-line example using braces
    break;
  }
  case 3:
    // ...
    break;
  default: 
    // ...
    break;
}

  • 有很多次,当相同代码被多个cases使用时,一个fall-through应该被使用。一个fall-through就是在case最后移除break语句,这样就能够允许执行流程跳转到下一个case值。为了代码更加清晰,一个fall-through需要注释一下。
switch (condition) {
  case 1:
    // ** fall-through! **
  case 2:
    // code executed for values 1 and 2
    break;
  default: 
    // ...
    break;
}

布尔变量

  • 因为nil将被解析为NO,因此没有必要在条件语句中进行比较。永远不要将任何东西和YES进行直接比较,因为YES被定义为1,而一个BOOL变量可以有8个字节。
if (someObject) {}
if (![anotherObject boolValue]) {}
  • 如果BOOL属性的名字是一个形容词,属性就能忽略"is"前缀,但要指定get访问器的惯用名称。例如:
@property (assign, getter=isEditable) BOOL editable;

条件语句

  • 为避免错误,条件语句体必须使用大括号,即便语句体中的语句可以不必使用大括号(比如只有一行语句)。常见的错误包括在不使用大括号的情况下添加第二行语句,以为它属于if语句的一部分。此外,更可怕的事情是,如果条件语句中的代码行被注释,则本不属于条件语句的下一行代码将变成条件语句的一部分。此外,这种编码风格和所有其它条件语句均保持一致。
应该:
if (!error) {
  return success;
}
不应该:
if (!error)
  return success;
  
 if (!error) return success;

三元操作符

  • 仅当使用该运算子可以让代码显得更清晰易懂时方可使用三元运算子。更多情况下应使用条件语句。使用类似if的条件语句对多种条件进行判断通常要更容易理解,或使用实例变量。
应该:
result = a > b ? x : y; 
不应该:
result = a > b ? x = c > d ? c : d : y; 

注释

  • 当需要注释时,注释应该用来解释这段特殊代码为什么要这样做。任何被使用的注释都必须保持最新或被删除。

  • 一般都避免使用块注释,因为代码尽可能做到自解释,只有当断断续续或几行代码时才需要注释。例外:这不应用在生成文档的注释

iOS开发