iOS 代码规范
2021-01-30 19:39:50 # Objective-C

命名规范

基本原则

命名应该尽可能的清晰和简洁,避免使用单词的简写,避免有歧义的命名

  • 命名遵循驼峰命名法
  • 必须由数字、字母、下滑线组成,不能数字开头。
  • 见名知意(非常重要,严禁出现无意义的命名,如aa,cc,data1,data2..
  • 变量名不能重名
  • 变量名不能和系统关键字重名(如int id 等)

代码示范:

1
2
3
4
5
6
7
8
9
10
11
//推荐
insertObject:atIndex:
setBackgroundColor:

//不推荐
// 不清晰,insert的对象类型和at的位置属性没有说明
insert:at:
// 不清晰,不要使用简写
setBkgdColor:
// 有歧义,是返回sendPort还是send一个Port?
sendPort

但在 Objective-C 编码过程中有部分单词简写非常常用,以至于成为了一种规范,这些简写可以在代码中直接使用,下面部分列举:

1
2
3
4
5
6
7
8
9
10
alloc   == Allocate         max    == Maximum
alt == Alternate min == Minimum
app == Application msg == Message
calc == Calculate nib == Interface Builder archive
dealloc == Deallocate pboard == Pasteboard
func == Function rect == Rectangle
horiz == Horizontal Rep == Representation (used in class name such as NSBitmapImageRep).
info == Information temp == Temporary
init == Initialize vert == Vertical
int == Integer

一致性

整个工程的命名风格要保持一致性,最好和苹果SDK的代码保持统一。不同类中完成相似功能的方法应该叫一样的名字,比如我们总是用count来返回集合的个数,不能在A类中使用count而在B类中使用getNumber

工程名

工程名的命名必须有强烈的导向性,让人看到工程名的第一眼就明白该工程对应哪个项目。如KKLOnLine
工程名字不能出现中文,不允许有空格。
工程名字采用大驼峰式命名法。

类名

类名的命名遵循大驼峰式命名法,应该包含一个名词来表示它代表的对象类型,类名中可以添加工程的前缀**,防止多个子工程出现类名重复的情况。(如,学习工程中的课程列表STCourseListTableViewController)

方法名

方法名的命名规范遵循小驼峰式命名法**,如果是私有的方法,在方法面前可以加p_,每个方法名之前需要有详细的注释,标明方法的作用。

  • 可以用一些通用的大写字母缩写打头方法,比如PDFTIFF等。
  • 可以用带下划线的前缀来命名私有方法或者类别中的方法。
  • 若方法表示让对象执行一个动作,使用动词打头来命名(不要使用 dodoes 这种多余的关键字)。
  • 若方法是为了获取对象的一个属性值,直接用属性名称来命名这个方法(不要添加 get 或者其他的动词前缀)。

变量名

变量名的命名规范遵循小驼峰式命名法(参数名的命名规则和变量名一直)。

常量名

常量名的命名模式 k+大驼峰式命名法形式。(如,kCourseCount**)

头文件

源码的头文件名应该清晰地暗示它的功能和包含的内容。(如:NSBundleAdditions.h

委托

委托的第一个参数是触发它的对象,第一个关键词是触发对象的类名,除非委托方法只有一个名为sender的参数。根据委托方法触发的时机和目的,使用 shouldwilldid 等关键词

通知

[触发通知的类名] + [Did | Will] + [动作] + Notification

函数

函数的的命名遵循大驼峰式命名法,一般带有缩写前缀,表示方法所在的框架。

  • 如果函数通过指针参数来返回值,需要在函数名中使用 Get
1
const char *NSGetSizeAndAlignment(const char *typePtr, unsigned int *sizep, unsigned int *alignp)
  • 函数的返回类型是BOOL时的命名
1
BOOL NSDecimalIsNotANumber(const NSDecimal *decimal)
  • 如果函数返回其参数的某个属性,省略动词:
1
2
unsigned int NSEventMaskFromType(NSEventType type)
float NSHeight(NSRect aRect)

注释规范

一份规范的代码必定不能缺少详尽的注释。代码块的注释应该更注重说明为什么这么做,而不是做了什么

注释的作用:

  1. 方便工作交接和引导新同事熟悉代码
  2. 方便自己之后回忆代码逻辑
  3. 方便生成文档

属性的注释

对于属性我们使用 三杠 /// 进行单行注释

1
2
///姓名
@property (nonatomic,copy)NSString *name;

特殊行和代码块的注释

对于特殊行和代码块的注释使用 双杠 //

1
2
3
4
5
6
7
8
//设置音量
player.volum = 0.5;
if(liveStatus == kLiveStatusEnded){//直播结束的操作

}
[[[ResourceTask task] post] subscribeNext:^(ResourceTask *task) {//请求之后的处理

}];

方法的注释

对于方法我们使用多行注释

1
2
3
4
5
6
7
/**
@abstract 初始化方法
@param name 姓名
@param sex 性别
@result 返回一个初始化实例
*/
- (instancetype)initWithName:(NSString *)name sex:(NSString *)sex;

类的注释

对于类的注释我们使用多行注释 类的声明部分要对当前类的功能作用进行详细的注释,标明类的作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//
// TopView.h
// KKLOnLine
//
// Created by zahir on 2017/7/31.
// Copyright © 2017年 mistong. All rights reserved.
//
#import <UIKit/UIKit.h>
/**
@class TopView
@abstract TopView 的简单介绍
@discussion TopView 的功能介绍 和 一些特殊的使用注意事项
*/
@interface TopView : UIView

@end

特定注释

  • ///TODO: 有功能代码待编写,需要注明,以便自己和代码审查者全局搜索。

  • ///FIXME:(禅道bug编号) 用于追溯bug,如果有禅道记录,修复完成后带入禅道的bug号,没有的话,也需要写明bug描述

  • ///!!!: 代码需要注意

  • ///???: 代码有疑问
  • ///MARK: 标记,与#pragma mark 效果相同

文件的注释

每一个文件都必须写文件注释,文件注释通常包含

  • 文件所在模块
  • 作者信息
  • 历史版本信息
  • 版权信息
  • 文件包含的内容,作用

注:文件注释的格式通常不作要求,能清晰易读就可以了,但在整个工程中风格要统一。

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
/*******************************************************************************
Copyright (C), 2011-2013, Andrew Min Chang

File name: AMCCommonLib.h
Author: Andrew Chang (Zhang Min)
E-mail: LaplaceZhang@126.com

Description:
This file provide some covenient tool in calling library tools. One can easily include
library headers he wants by declaring the corresponding macros.
I hope this file is not only a header, but also a useful Linux library note.

History:
2012-??-??: On about come date around middle of Year 2012, file created as "commonLib.h"
2012-08-
Copyright information: 20: Add shared memory library; add message queue.
2012-08-21: Add socket library (local)
2012-10-10: Change file name as "AMCCommonLib.h"
2012-12-04: Add UDP support in AMC socket library
2013-01-22: Add CFG_LIB_TIMER.
2013-01-22: Remove CFG_LIB_DATA_TYPE because there is already AMCDataTypes.h

This file was intended to be under GPL protocol. However, I may use this library
in my work as I am an employee. And my company may require me to keep it secret.
Therefore, this file is neither open source nor under GPL control.

********************************************************************************/

代码规范

空格

  1. 不要在工程里使用 Tab,使用空格来进行缩进。在 Xcode > Preferences > Text Editing 将 Tab 和自动缩进都设置为4个空格。

  2. + - * / = 的前后都应该保留一个空格。

  3. 逗号,的前面不应该留有空格,,后面应该保留一个空格
1
2
3
4
5
6
7
//推荐
NSArray *data = @[@"1", @"2", @"3"];
NSInteger x = 4 * (5 + 3);

// 不推荐
NSArray* data=@[ @"1", @"2", @"3" ];
NSInteger x=4*(5+3);

每一行的最大长度

Xcode > Preferences > Text Editing > Page guide at column: 中将最大行长设置为80,过长的一行代码将会导致可读性问题。

括号的换行

方法及其他大括号(if/else/switch/while等),总是在同一行语句打开但在新行中关闭。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//推荐
- (void)foobar {
if (user.age > 18) {
// Do something
} else {
// Do something
}
}

// 不推荐
- (void)foobar
{
if (user.age > 18)
{
// Do something
}
else
{
// Do something
}
}

函数的书写和调用

  1. -(void) 之间应该有一个空格,第一个大括号 { 的位置在函数所在行的末尾,同样应该有一个空格。

  2. 函数有特别多的参数或者名称很长,应该将其按照:来对齐分行显示。

1
2
3
4
5
6
7
8
9
10
11
- (id)initWithModel:(IPCModle)model
ConnectType:(IPCConnectType)connectType
Resolution:(IPCResolution)resolution
AuthName:(NSString *)authName
Password:(NSString *)password
MAC:(NSString *)mac
AzIp:(NSString *)az_ip
AzDns:(NSString *)az_dns
Token:(NSString *)token
Email:(NSString *)email
Delegate:(id<IPCConnectHandlerDelegate>)delegate;

方法内部避免空白行

在方法内部尽量避免空白行,如果为了区分功能,请用单行注释进行分割(注释可以增加斜杠数量////表明功能隔离)。

闭包建议避免以冒号对齐的方式调用

  1. 较短的 block 可以写在一行内。
  2. block 内的代码采用4个空格的缩进
  3. ^( 之间,^{ 之间都没有空格,参数列表的右括号 ){ 之间有一个空格
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 较短的block写在一行内
[operation setCompletionBlock:^{ [self onOperationDone]; }];

// 推荐
[UIView animateWithDuration:1.0 animations:^{
// something
} completion:^(BOOL finished) {
// something
}];

//不推荐
[UIView animateWithDuration:1.0
animations:^{
// something
}
completion:^(BOOL finished) {
// something
}];

数据结构的语法糖

应该使用可读性更好的语法糖来构造 NSArrayNSDictionary 等数据结构,避免使用冗长的 alloc,init 方法。

一行构造,应该在括号两端保留一个空格,使得被构造的元素于与构造语法区分开

1
2
3
4
5
6
7
// 正确,在语法糖的"[]"或者"{}"两端留有空格
NSArray *array = @[ [foo description], @"Another String", [bar description] ];
NSDictionary *dic = @{ NSForegroundColorAttributeName : [NSColor redColor] };

// 不正确,不留有空格降低了可读性
NSArray* array = @[[foo description], [bar description]];
NSDictionary* dic = @{NSForegroundColorAttributeName: [NSColor redColor]};

多行构造,构造元素应保留两个空格来进行缩进,右括号 ] 或者 }写在新的一行,并且与调用语法糖那行代码的第一个非空字符对齐

1
2
3
4
5
6
7
8
9
10
11
NSArray *array = @[
@"This",
@"is",
@"an",
@"array"
];

NSDictionary *dictionary = @{
NSFontAttributeName : [NSFont fontWithName:@"Helvetica-Bold" size:12],
NSForegroundColorAttributeName : fontColor
};

构造字典时,字典的Key和Value与中间的冒号:都要留有一个空格,多行书写时,也可以将Value对齐

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
// 推荐,冒号':'前后留有一个空格
NSDictionary *option1 = @{
NSFontAttributeName : [NSFont fontWithName:@"Helvetica-Bold" size:12],
NSForegroundColorAttributeName : fontColor
};

// 推荐,按照Value来对齐
NSDictionary *option2 = @{
NSFontAttributeName : [NSFont fontWithName:@"Arial" size:12],
NSForegroundColorAttributeName : fontColor
};

// 不推荐,冒号前应该有一个空格
NSDictionary *wrong = @{
AKey: @"b",
BLongerKey: @"c",
};

// 不推荐,每一个元素要么单独成为一行,要么全部写在一行内
NSDictionary *alsoWrong= @{ AKey : @"a",
BLongerKey : @"b" };

// 不推荐,在冒号前只能有一个空格,冒号后才可以考虑按照Value对齐
NSDictionary *stillWrong = @{
AKey : @"b",
BLongerKey : @"c",
};

TODO:待讨论

  • 在方法之间应该有且只有一行。这样有利于在视觉上更清晰。

  • 函数有特别多的参数或者名称很长,应该将其按照:来对齐分行显示。

  • 应该避免以冒号对齐的方式来调用方法。因为有时方法签名可能有3个以上的冒号和冒号对齐会使代码更加易读。请不要这样做,尽管冒号对齐的方法包含代码块,因为Xcode的对齐方式令它难以辨认。(?????)

  • 闭包建议避免以冒号对齐的方式调用。

编码规范

编码规范简单来说就是为了保证写出来的代码具备三个原则:可复用, 易维护, 可扩展. 这其实也是面向对象的基本原则.

方法

  • 在方法签名中,应该在方法类型(-/+ 符号)之后有一个空格

  • 一个方法,应该少于50行

  • 方法分布区

  • life-Cycle、Delegate、Event-Response、Private-Method、Setters-Getters 顺序进行分区域书写 不是delegate方法的,不是event response方法的,不是life cycle方法的,就是private method了。

  • 对于整个单独的功能模块是可以考虑添加category。

  • 同一区域中的方法按照经常:容易忘记的、常使用的程度顺序进行排列

  • 当一个方法参数过多,其实这样就预示着是否我们可以聚合一个model类,一方面代码整洁;另一方面参数过多逻辑不简容易导致错误

1
2
3
4
//推荐
- (void)registerUser(User*)user;
//不推荐
-(void)registerUserName:(NSString*)userName password:(NSString*)password email:(NSString*)email;

属性

  • 所有的属性特性应该显示的列出来,有助于新手理解代码。

  • *应该跟着属性名字,而不是跟在类型后面。

  • 集合作为属性时,标注集合内元素类型

1
2
3
4
//推荐
@property (nonatomic,strong) NSArray<Teacher *> *teacherList;
//不推荐
@property (nonatomic,strong) NSArray *teacherList;

变量

  • 变量尽量以描述性的方式来命名。单个字符的变量命名应该尽量避免,除了在for()循环。

  • 变量进行赋值、比较时避免产生硬编码

1
2
3
4
5
6
7
8
9
10
11
12
13
//推荐
#define kPeopleNumber 2
if(peopleNumber == kPeopleNumber){
}
//或者
int studentNumber = kPeopleNumber;
int teacherNumber = kPeopleNumber;
//不推荐
if(peopleNumber == 2){
}
//或者
int studentNumber = 2;
int teacherNumber = 2;
  • 星号表示变量是指针。例如, NSString *text 既不是 NSString* text 也不是 NSString * text
  • 尽可能用私有属性代替实例变量的使用。如果有实例变量,要用下划线开头命名NSString *_text
1
2
3
4
5
6
7
8
9
10
11
//推荐
@interface GrowthStationVC ()
@property (nonatomic, strong) NSString *fmTitle;
@property (nonatomic, strong) UIView *containerView;
@end
//不推荐
@interface GrowthStationVC () {
NSString *_fmTitle;
UIView *containerView;
}
@end

容器

枚举类型

当使用enum时,推荐使用NS_ENUM方式,因为它有更强的类型检查和代码补全。现在SDK有一个宏NS_ENUM()来帮助和鼓励你使用固定的基本类型。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
//推荐
typedef NS_ENUM(NSInteger, RWTLeftMenuTopItemType) {
RWTLeftMenuTopItemMain = 1,
RWTLeftMenuTopItemShows,
RWTLeftMenuTopItemSchedule
};

//不推荐
enum GlobalConstants {
kMaxPinSize = 5,
kMaxPinCount = 500,
};

布尔值

Objective-C使用YESNO。因为truefalse应该只在C或C++代码使用。不要拿某样东西直接与YES比较。

1
2
3
4
5
6
7
8
9
//推荐
if (someObject) {}
if (![anotherObject boolValue]) {}

//不推荐
if (someObject == nil) {}
if ([anotherObject boolValue] == NO) {}
if (isAwesome == YES) {} // Never do this.
if (isAwesome == true) {} // Never do this.

条件语句

  • 条件语句主体为了防止出错应该使用大括号包围,即使条件语句主体能够不用大括号编写(如,只用一行代码)。这些错误包括添加第二行代码和期望它成为if语句;还有,

    even more dangerous defect

    可能发生在if语句里面一行代码被注释了,然后下一行代码不知不觉地成为if语句的一部分。除此之外,这种风格与其他条件语句的风格保持一致,所以更加容易阅读。

1
2
3
4
5
6
7
8
9
//推荐
if (!error) {
return success;
}
//不推荐
if (!error)
return success;
//或:
if (!error) return success;
  • 当使用条件语句编码时,左手边的代码应该是”golden” 或 “happy”路径。也就是不要嵌套if语句,多个返回语句也是OK。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //推荐
    - (void)someMethod {
    if (![someOther boolValue]) {
    return;
    }
    //Do something important
    }

    //不推荐
    - (void)someMethod {
    if ([someOther boolValue]) {
    //Do something important
    }
    }

三元操作符

当需要提高代码的清晰性和简洁性时,三元操作符?:才会使用。单个条件求值常常需要它。多个条件求值时,如果使用if语句或重构成实例变量时,代码会更加易读。一般来说,最好使用三元操作符是在根据条件来赋值的情况下。

Non-boolean的变量与某东西比较,加上括号()会提高可读性。如果被比较的变量是boolean类型,那么就不需要括号。

1
2
3
4
5
6
7
8
9
//推荐
NSInteger value = 5;
result = (value != 0) ? x : y;

BOOL isHorizontal = YES;
result = isHorizontal ? x : y;

//不推荐
result = a > b ? x = c > d ? c : d : y;

nil检测

在 Objective-C 中向 nil 对象发送命令是不会抛出异常或者导致崩溃的,只是完全的“什么都不干”,所以,只在程序中使用 nil 来做逻辑上的检查。

不要使用诸如 nil == Object 或者 Object == nil 的形式来判断。

1
2
3
4
5
6
7
8
9
// 正确,直接判断
if (!objc) {
...
}

// 错误,不要使用nil == Object的形式
if (nil == objc) {
...
}

使用NSNumber的语法糖

使用带有@符号的语法糖来生成 NSNumber 对象能使代码更简洁

1
2
NSNumber *phoneNumber = @42;
NSNumber *pi_2 = @(M_PI / 2);

NSString 在赋值时被复制

以复制(copy)的方式防止在不知情的情况下 String 的值被其它对象修改。

1
2
3
- (void)setFood:(NSString *)food {
_food = [food copy];
}

BOOL的使用

  1. 不要将 BOOL 类型变量直接和 YES
  2. BOOL类型可以和 _Bool,bool 相互转化,但是不能Boolean 转化。
  3. 不要将其它类型的值作为 BOOL 来返回,这种情况下,BOOL 变量只会取值的最后一个字节来赋值,这样很可能会取到0(NO)。但是,一些逻辑操作符比如 &&,||,!的返回是可以直接赋给 BOOL。
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
// 错误,无法确定|great|的值是否是YES(1),不要将BOOL值直接与YES比较
BOOL great = [foo isGreat];
if (great == YES)
// ...be great!

//正确
BOOL great = [foo isGreat];
if (great)
// ...be great!

// 错误,不要将其它类型转化为BOOL返回
- (BOOL)isBold {
return [self fontTraits] & NSFontBoldTrait;
}
- (BOOL)isValid {
return [self stringValue];
}

// 正确
- (BOOL)isBold {
return ([self fontTraits] & NSFontBoldTrait) ? YES : NO;
}

// 正确,逻辑操作符可以直接转化为BOOL
- (BOOL)isValid {
return [self stringValue] != nil;
}
- (BOOL)isEnabled {
return [self isValid] && [self isBold];
}

不要使用 new 方法

尽管很多时候能用new代替alloc init方法,但这可能会导致调试内存时出现不可预料的问题。Cocoa的规范就是使用alloc init方法,使用new会让一些读者困惑。

避免循环引用

为了保持代码统一性,避免开发中疏忽导致强引用。统一强调在代码块中使用 @weakify(self) ,@strongify(self)来做处理。

1
2
3
4
5
6
@weakify(self);
void(^messageBlock)(NSString *) = ^(NSString *message) {
@strongify(self);
self.message = message;
NSLog(@"%@",message);
};