iOS代码开发详细规范

一、命名

1.属性对象和局部变量(采用小驼峰命名法)

UI视图对象

变量名称 + (Label / Button / Cell / TableView / WebView / ScrollView / CollectionView / ImageView / TabBar / TextField ...)
如:UILabel *titleLabel;
特殊类型:UIBarButtonItem -> ButtonItem.

非UI视图对象

VC: name + (VC / TableVC / CollectionVC / PageVC)
如: UIPageViewController *firstPageVC;
特殊类型:

  • UINavigationController -> NavCtroller
  • UITapGestureRecognizer -> TapGesture
  • UILongPressGestureRecognizer -> LongPressGesture

数据类型: name + 类型(Integer / Int / Long / Float / Double / Number / String / Array / MutableArray / IndexPath / Date / Error...)
如:NSArray *dataArray;
特殊类型:

  • NSDictionnary -> Dict
  • NSMutableDictionnary等可变类型变量,使用不可变类型的命名方式如:name + Dict
  • NSTimeInterval -> Double
  • CGRect -> Rect, CGSize -> Size

注意:1.局部变量可不遵守变量名字后面加数据类型这一规则。 2.Bool类型以is、has、can等作为前缀,或以ing、ed等作后缀。

其他:

  • name + Model
  • name + ViewModel
  • name + Request(网络请求)
  • name + Block

注意:尽量为每个变量取有意义的名字,即“name”,但若确实没有name的情况下,直接采用如下形式,如:UITableView *tableView;

2.实例变量

实例变量采用小驼峰命名法的基础上,以下划线“_”作为前缀,如:
UIButton *_loginButton;

3.常量 和 宏常量

普通常量:以小写字母“k”开头的驼峰命名法,如:static NSString *const kMovieCellHeight;
通知常量:一般形式为[ 触发通知的类名] + [Did 或 Will] + [ 动作 ] + Notification ;
宏常量:全部大写,中间用下划线“_”作间隔,如:#define TARGET_OS_IOS
注意:

  • 如果宏常量和APP业务相关则添加相应业务类名作前缀,如果适用于整个APP则直接使用APP前缀,如:#define MOHomeCellHeaderHeight (18.0)
  • 尽量不用宏来定义常量,而是采用枚举和const定义的常量

4.枚举类型名称 和 枚举变量名称

定义枚举类型:采用OC风格定义枚举类型,举个栗子:

typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {  
    UIViewAnimationTransitionNone,         //默认从0开始  
    UIViewAnimationTransitionFlipFromLeft,  
    UIViewAnimationTransitionFlipFromRight,  
    UIViewAnimationTransitionCurlUp,  
    UIViewAnimationTransitionCurlDown,  
};

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {  
    UIViewAutoresizingNone                 = 0,  
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,  
    UIViewAutoresizingFlexibleWidth        = 1 << 1,  
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,  
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,  
    UIViewAutoresizingFlexibleHeight       = 1 << 4,  
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5  
};

其中:    
1.枚举类型名称采用首字母大写的驼峰命名法,并且添加相关类作为前缀    
(本例中相关的类为UIView)
2.枚举值命名要添加枚举类型名作前缀    
(本例中枚举值前缀分别为UIViewAnimationTransition和UIViewAutoresizing)

枚举变量名称和类型名称基本保持一致的情况下,尽量增强其可阅读性,采用小驼峰命名法,如:

UIViewAnimationTransition animationTransitionType; 

5.方法名

采用小驼峰命名法,做到见其名知其含义,本身具有充分的解释性,拒绝无意义命名。
例外情况:

  • 可以用一些通用的大写字母缩写打头的方法,比如 PDF,TIFF
  • 可以用带下划线的前缀来命名私有方法或者类别中的方法

6.类名 和 协议名

类名:“统一的类名前缀” + “功能模块简称”(可省略) + “描述该类的词或词组” + 控件类型名尾缀(如Cell,ViewController,View等,可省略)
协议名:类名 + “Delegate”
都采用大驼峰命名法。

7.分类的名称 和 分类中方法名

分类的名称和类名的命名方式基本一致,只是前缀由开发者指定。
但分类中的方法名,需使用分类的前缀小写形式 + “_”的形式,如:

@interface NSDate (MOLocalDate)

/// 获取根据时间间隔按照系统时间计算的日期,seconds:单位(秒)
+ (NSDate *)mo_dateWithTimeIntervalSince1970:(NSTimeInterval)seconds;    


8.Assets.xcassets文件命名

1.功能模块使用的资源,使用功能模块代码资源同样的文件夹结构(一级或多级)和名称。
2.公共或通用资源文件夹取名为“Common”或“Normal”
3.图片资源命名方式采用全部小写和分割线分隔的形式,前缀为功能模块名称或缩写,如:
common_navigation_bar_back

9.功能模块内文件夹命名

一般情况下,各功能模块下的文件夹命名包括但不限于以下命名:

  • View 或 CustomView
  • Model
  • ViewController
  • Cell
  • ViewModel (MVVM模式下使用)
  • Helper
  • ……

10.Xib或Storyboard中命名

  • Xib文件名和对应类名保持一致
  • Xib或者Storyboard中的控件,命名采用全部大写且用一个空格分隔的形式。添加控件时立即添加有意义的命名,方便查看不同控件的约束关系以及后期维护。

总结:命名尽量不要用单词简写,注重保持语义明确;名称保持一致性,避免同样语义出现多种单词如“number”和“count”;命名风格统一,遵守统一的的命名规则。

二、风格

1.关于空格与空行

  • 数组或字典字面量用逗号隔开的形式书写时,逗号后留有一个空格
  • ifswitch语句中,这两个关键字和紧跟的括号之间留有一个空格
  • 二元符号如"="、"=="、">"、"<"、"||"、"&&"等两侧留有一个空格
  • 三元符号中"?"和":"两侧留有一个空格
  • 方法名“+”和“-”号和方法名中间留有一个空格;方法实现中“{”和方法名之间留有一个空格。
  • 定义属性时,逗号后留有一个空格,如:
@property (nonatomic, copy, readonly) NSString *titleString;    
小括号左右两边各用一个空格与外部隔开;
星号“*”左边留有一个空格。
  • 声明类时
@interface MOCalendarService : NSObject
冒号左右各用一个空格隔开
  • 声明分类或延展(类目)时
/// 分类
@interface NSDate (MOLocalDate)
/// 延展(类目)
@interface ClassName () 

- 小括号左右两边各用一个空格与外部隔开
- 协议列表中,逗号后留有一个空格
  • 方法与方法之间空一行,方法内的代码块与代码块之间都空一行
  • #prgma mark - Demo分隔语句上方留有一空行,下方不留空行或留有一空行。

2.注释

  • 在需要注释的方法或属性上方使用Command + Alt + /”快捷键,或者“/// + 一个空格 + 说明”的形式进行注释,方便别处使用“Alt + 鼠标点按”直接查询该方法或属性的定义。
    (注意:在属性右侧使用“/// + 一个空格 + 说明”的形式进行注释,在别处无法用“Alt + 鼠标点按”快捷键获取注释详情)
  • 针对方法内某一行代码语句进行注释,可以在此行代码上方或右侧进行“// + 一个空格 + 说明”的形式进行注释,注意将该代码段和其它代码段用注释行或空白行进行分隔。
  • 针对方法内某几行代码段进行注释,也注意将该代码段和其它代码段用注释行或空白行进行分隔。

总结:

  • 注释方法和属性尽量方便代码使用时用快捷键获取注释内容
  • 注释内容尽量让不懂代码的人能看懂在做的事情
  • 注释代码行或代码段要明确区分注释区域
  • 典型的需要注释的地方包括:头文件中类名、属性和方法注释,ifswitch语句注释,魔术数字或字符串注释
  • 注释不要和Mark混淆使用。

3.Mark分区

建议开发时使用Xcode右下角的代码快捷(Code Snippet Library)方式保存和提取常用文件Mark分区结构。
针对VC:

#pragma mark - View Lifecycle
- (void)viewDidLoad {
    [super viewDidLoad];
    [self buildingUI];
    [self makeViewConstraints];
    [self bindViewModel];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
}

#pragma mark - buildingUI
- (void)buildingUI {
}

#pragma mark - Make Constraints
- (void)makeViewConstraints {
}

#pragma mark - bindViewModel
- (void)bindViewModel {
}

#pragma mark - Delegate Methods

#pragma mark - Publich Methods

#pragma mark - Private Methods

#pragma mark - Notification Event

#pragma mark - Property Set

#pragma mark - Property Get

针对ViewModel:

#pragma mark - Life Cycle
- (instancetype)init {
    self = [super init];
    if (self) {
        [self binding];
    }
    
    return self;
}

#pragma mark - Bind
- (void)binding {
    
}

#pragma mark - Publich Methods

#pragma mark - Private Methods

#pragma mark - Property Set

#pragma mark - Property Get


针对TableViewController:

#import "<#ControllerName#>.h"

// static NSString *const <#cellNameId#> = @"<#cellId#>";

@interface <#ControllerName#> () 

// @property (nonatomic, strong) <#ViewModolClass#> *viewModel;
@property (nonatomic, strong) UITableView *tableView;

@end

@implementation <#ControllerName#>

#pragma mark - View Lifecycle
- (void)viewDidLoad {
    [super viewDidLoad];
    [self buildingUI];
    [self makeViewConstraints];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
}

#pragma mark - buildingUI
- (void)buildingUI {
    self.title = @"<#TitleName#>";
}

#pragma mark - Make Constraints
- (void)makeViewConstraints {
    [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
    }];
}

#pragma mark - UITableViewDataSource && UITableViewDelegate
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return <#countInteger#>;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 44.f;
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    return 0.1f;
}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
    return 0.1f;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:<#cellNameId#>];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:<#cellNameId#>];
    }
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:NO];
    // do something
    
}

#pragma mark - Publich Methods

#pragma mark - Private Methods

#pragma mark - Notification Event

#pragma mark - Property Set

#pragma mark - Property Get
// - (<#ViewModolClass#> *)viewModel {
//  if (!_viewModel) {
//      _viewModel = [[<#ViewModolClass#> alloc] init];
//  }
//  return _viewModel;
//}

- (UITableView *)tableView {
    if (!_tableView) {
        _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
        _tableView.delegate = self;
        _tableView.dataSource = self;
        // _tableView.tableFooterView = [[UIView alloc] init];
        // _tableView.showsVerticalScrollIndicator = NO;
        // _tableView.contentInset = UIEdgeInsetsMake(0, 0, 50, 0);
        [self.view addSubview:_tableView];
    }
    return _tableView;
}

@end


针对Cell:

#pragma mark - Init Method
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        [self makeViewConstraints];
    }
    return self;
}

#pragma mark - 约束布局
- (void) makeViewConstraints {
}

#pragma mark - Public Methods

#pragma mark - Private Methods

#pragma mark - Property Set

#pragma mark - Property Get

针对自定义View:

#pragma mark - Init Method
- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self makeViewConstraints];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self makeViewConstraints];
    }
    return self;
}

#pragma mark - 约束布局
- (void) makeViewConstraints {
}

#pragma mark - Publich Methods

#pragma mark - Private Methods

#pragma mark - Property Set

#pragma mark - Property Get

4.对齐

  • 所有代码段使用四个空格进行缩进
  • 名字太长的方法定义或调用,可采用冒号对齐的方式多行展示
  • 数组和字典字面量跨行书写时“@”符号对齐:
/// 我是数组
NSArray *demoArray = @[@"Object-C",
                       @"Swift",
                       @"Python",
                       @(YES),
                       @(1)
                      ];
/// 我是字典
NSDictionary *demoDict = @{@"Object-C":@"Object-C",
                           @"Swift":@"Swift",
                           @"Python":@"Python",
                           @"BoolValue":@(YES),
                           @"Number":@(1)
                          };
其实,上面两个例子中,中括号和大括号的始末位置也进行了对齐。
  • if语句中,左大括号“{”不跨行,并和右括号“)”之间留有一个空格; 右大括号“}”和对应的“if”保持左端对齐; else语句和左侧“}”以及右侧“{”在同一行,举个例子:
/// 我是 if 语句
if (18 == self.age) {
    if (self.isGirl) {
        NSLog(@"Marry me!");
    } else {
        /// do something
    }
}

建议使用if语句时,直接选择苹果提供的快捷代码段。

  • 所有枚举值与最左侧保持四个空格的缩进
  • 非线程安全的属性以"@property (nonatomic, ...)"打头的形式书写,代码更工整
  • block中嵌套block时,注意每个"}"和对应的代码起点对齐

三、开发习惯

1.Switch语句每个case后添加“{}”的习惯

- (void)sampleForSwitch {
    SampleEnum testEnum = SampleEnumTwo;
    switch(testEnum) {
        caseSampleEnumUndefined: {
            // do something
            break;
        }
        caseSampleEnumOne: {
            // do something
            break;
        }
        caseSampleEnumTwo: {
            // do something
            break;
        }
        default: {
            NSLog(@"WARNING: there is an enum type not handled properly!");
            break;
        }
    }
}

2.先建立实体文件夹,后在工程中引入

3.条件语句

简单条件判断推荐使用三目运算符“? :”,如:

result = object ? : [self createObject];

注意这里“?”后写成空格,会直接返回object的情况,不建议用下面这种形式:
result = object ? object : [self createObject];    


4.Bool赋值

简单的条件判断后赋布尔值的逻辑,可以省略if语句,如:

BOOL isAdult = age > 18;

而不是:
BOOL isAdult;
if (age > 18) {
    isAdult = YES;
}
else {
    isAdult = NO;
}

5.拒绝魔术数字和字符串

举个例子:

/// 反例1 “Nissan”字符串突然横空出现
if (carName == "Nissan")
/// 反例2 “18”是个什么意思~
if (age > 18) { ... }

推荐下面的方式:
/// 用枚举类型代替字符串类型(由于是数字类型比较,编译速度比字符串类型更为高效)
if (car == Car.Nissan)
/// 提前为魔术数字定义常量,同时方便添加注释(即明确数字含义后再继续写代码)
const int adultAge = 18; 
if (age > adultAge) { ... }

6.readonly属性

不需要修改属性的地方,心怀添加readonly的念想。

7.copy属性

对于有可变类型子类的数据类型,使用copy属性防止数据被更改,如NSString、NSArray、NSDictionary。

8.使用字面量方式初始化数据,增强可读性

9.复杂判断简单化

if (job.JobState == JobState.New
    || job.JobState == JobState.Submitted
    || job.JobState == JobState.Expired
    || (job.JobTitle && job.JobTitle.length) {
    ....
}

可以被优化为:
/// 首先将上面整体提炼为一个方法
if ([self canDeleteJob:job]) { ... }        
/// 方法内对条件判断进行拆分
- (BOOL)canDeleteJob:(Job *)job {
    BOOL invalidJobState = job.JobState == JobState.New
                          || job.JobState == JobState.Submitted
                          || job.JobState == JobState.Expired;
    BOOL invalidJob = job.JobTitle && job.JobTitle.length;

    return invalidJobState || invalidJob;
}

10.嵌套判断平行化

BOOL isValid = NO;
if (user.UserName) {
    if (user.Password) {
        if (user.Email) {
            isValid = YES;
        }
    }
}
return isValid;

可以被优化为:

if (!user.UserName) return NO;
if (!user.Password) return NO;
if (!user.Email) return NO;

return YES;

11.回调方法加调用者的习惯

如经典的:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

添加调用者tableView作为参数,方便信息传递和区分调用者。

12.NSTimer、观察者、通知、网络请求等注意在Dealloc或viewWillDisappear:方法中移除。同一个文件中,移除的顺序应和创建的顺序保持一致,方便后期维护(注意:工程中使用RAC或已经封装好的方法不需要作移除操作)。

13.访问或操作NSArray、NSDictionary等对象时,注意判断对象和对象内要访问的元素是否为nil。

14.虽然很简单,但尽量不用new方法而是统一采用Cocoa规范中的[[ClassName alloc] init]方法

15.Delegate使用weak属性修饰

@property (nonatomic, weak) delegate;

16.提交代码前保证无warning和error

17.随时注释别人或未来的自己有可能看不懂的代码

18.逻辑捋清楚前不要写代码,因为一定会重写。

四、设计思想

1.精简

  • 方法不超过约一百行,否则就要考虑拆分
  • 清除:无用类、方法、资源、注释、多余空行、Log语句、警告

2.分工明确

  • .h文件:核心属性和方法声明地带 (.h文件由于不参与实现过程,用@class引用类,将#import "Demo.h"形式的引入统一写在.m文件中)
  • .m文件:私有方法、私有变量声明以及所有方法的实现基地
  • 分类:不常用、或与主业务无关的方法聚居地(不要滥用分类)
  • View层:UI搭建(MVC设计模式下可以进行简单的数据展示)
  • Model层:展示最直白的数据结构
  • ViewController层:
    • 1.容器:容纳子VC,构建View布局和展示
    • 2.控制Viewmodel
    • 3.响应UI事件(或信号)和代理方法
    • 4.不同生命周期逻辑处理
  • ViewModel层:用来且只用来处理和数据相关的一切
    • 1.数据初始化
    • 2.请求网络数据
    • 3.数据业务处理:数据持久化、筛选、排序、验证、增删改查
    • 4.将处理后的数据在View上展示
    • 5.头文件中返回最终有效readonly数据
  • 网络层:分担ViewModel层网络请求职责

3.团队一致性

上述所有,共同自律,提高整体效率。

你可能感兴趣的:(iOS代码开发详细规范)