适当的代码规范和标准绝不是消灭代码内容的创造性、优雅性,而是限制过度个性化,以一种普遍认可的统一方式一起做事,进而提高工作效率,降低沟通成本。代码的字里行间流淌着的是软件和程序员的血液,质量的提升是尽可能少踩坑、杜绝踩重复的坑,切实提升系统稳定性,码出质量
-
[必须] 杜绝无意义的拼音,国际通用名称或者地名人名除外(例如alibaba
、taobao
、hangzhou
)。
反例:
DaZhePromotion(打折)
-
[必须] 命名要尽可能的清晰并简洁,如果两者不能兼得,则以清晰为主。
正例:
insertObject:atIndex:
反例:
insert:at:(不清晰,插入什么?at代表什么?)
-
[必须] 代码和注释中都要避免使用任何语言的种族歧视性词语。
正例:
secondary
反例:
slave
-
[必须] 类名、协议名、函数名、常量名、枚举名等一些全局命名需要添加前缀,前缀需要大于2个字符且全部大写。
Tips: 系统保留任意两个字符作为前缀的使用权,
包括但不限于NS、UI、CG、CF、CA、WK、MK、CI、NC;前缀若等于2个字符可以考虑添加_。
正例:
ZT_LoginViewController
反例:
ZTLoginViewController
-
[必须] 类名、协议名、函数名、常量名、枚举名等一些全局命名遵循首字母大写的驼峰命名方式,首个单词是HTTP
这种特殊词除外。
-
[必须] 方法名、属性名等一些非全局命名遵循首字母小写的驼峰命名方式命名,首个单词是HTTP
这种特殊词除外。
-
[必须] 成员变量需要以_开头。
正例:
NSString *_nameString;
-
[建议] 在给常量或变量命名时,尽量将表示类型的名词放在词尾,以提升辨识度。
正例:
nameLabel、nameString
反例:
name(name是字符串还是什么?)
-
[建议] 如果模块、接口、类、方法使用了模式,在命名时尽量体现出具体模式。
正例:
OrderFactory、LoginProxy
-
[建议] 局部临时变量命名可以加上标识符作为前缀。
正例:
t_label、t_string(t在这里表示temp)
-
[必须] 如果有使用到CF(Core Foundation)
等框架时,或者是在iOS10
以下系统使用通知和KVO
时,切记在dealloc
方法中释放对象以及移除通知和监听。
-
[必须] 在dealloc
方法内禁止将self
传递出去,如果self
被retain
,到下个runloop
周期再释放则会多次释放导致crash
。
反例:
- (void)dealloc {
[self unsafeMethod:self];
}
-
[必须] 禁止使用过时的方法或类,应该及时去了解和使用新方法或类。
Tips:
对于过时的方法或类,大都是因为其自身有一些缺陷或BUG才会不建议使用,使用新方法时建议了解一下为什么废弃掉旧方法/类。
-
[必须] 对剪切板的读取操作必须放在子线程中进行,因为用户可能在Mac
上复制大量数据然后通过iCloud
同步到iPhone
上。
-
[必须]if、else、for、while、case
等后面必须要有{},除非后面是简单的return类型语句,例如if (xxx) return;
。
-
[必须] 当方法可能会提前return
时,需要要注意对象的释放问题,避免内存泄漏。
反例:
CFArrayRef arrayRef = (__bridge CFArrayRef)array;
if (x == YES) return;
CFRelease(arrayRef);
以上代码如果x等于YES的话那么arrayRef对象就会内存泄漏。
-
[必须] 当使用@try
处理异常时,需要要注意对象的释放问题,避免内存泄漏。
反例:
@try {
CFArrayRef arrayRef = (__bridge CFArrayRef)array;
do some thing……
CFRelease(arrayRef);
} @catch (NSException *exception) {
}
以上代码如果do some thing……出现异常的话那么arrayRef就会出现内存泄漏。
-
[必须] 声明常量请使用const
类型声明,禁止使用#define
宏定义。
Tips:
宏定义声明常量的缺点:
1. 宏定义只是简单的替换,缺少编译检查,运行期可能会出现溢出或数据错误等问题。
2. 宏定义缺少类型,不方便编写文档用例。
3. 宏定义可能会被不小心替换。
4. 宏定义无法编写注释。
反例:
#define kTime @"10"
if (1 == 2) {
#define kTime @"20"
}
NSLog(@"time = %@", kTime);
以上代码中的if永远不会执行,但是编译器也会将kTime替换为@"20"
-
[建议] 写一些公共方法时,请尽量使用内联函数或者全局函数而不是宏定义。
Tips:
函数不通过对象调用,所以不会走OC的消息转发流程,效率远高于方法调用;而且函数会有返回值和参数类型以及参数检查,而这些都是宏定义没有的。
正例:
UIKIT_STATIC_INLINE NSString * kNSStringFromInteger(NSInteger a) {
return [NSString stringWithFormat:@"%zd", a];
}
反例:
#define kNSStringFromInteger(a) [NSString stringWithFormat:@"%zd", a]
-
[必须] UITableView
使用self-sizing
实现不等高cell
时,请在tableView
:cellForRowAtIndexPath
:代理方法中给cell
设置数据而不是tableView
:willDisplayCell
:forRowAtIndexPath
:代理方法中设置数据。
-
[必须] 只在必要的时刻使用懒加载。
Tips:
只在以下三种情况下才能使用懒加载:
1. 对象的创建需要依赖其他对象
2. 对象可能被使用,也可能不被使用
3. 对象创建比较消耗性能
-
[建议] 懒加载方法内应该只执行需要初始化的操作,不应该有其他不必要的逻辑代码。
-
[必须] 使用一目运算符时左右两边不能有空格。
正例:
i++、++i、
反例:
i ++、++ i
-
[必须] 使用二目、三目运算符时左右两边必须有且仅有一个空格。
正例:
1 + 2
反例:
1+2
-
[必须] 采用4个空格缩进,如果要使用Tab字符,请将1个Tab设置成4个空格。
-
[必须] 使用NSUserDefaults
存储数据时禁止调用synchronize
方法,因为系统会在合适的时机将数据保存到本地(即使程序闪退等极端情况)。
-
[必须] 添加到集合中的对象应该是不可变的,或者在加入之后其哈希码是不可变的。
反例:
NSMutableSet *sets = [NSMutableSet set];
NSMutableString *string1 = [NSMutableString stringWithString:@"1"];
[sets addObject:string1];
[sets addObject:@"12"];
[string1 appendString:@"2"];
当 [string1 appendString:@"2"] 执行完以后sets对象内会包含2个@"12"。
-
[必须] 必须使用CGRectGet
获取Frame
的各种值,而不是通过frame.
的方式获取。
Tips:
CGRect t_frame = CGRectMake(-10, -10, -10, -10);
当一个view的frame设置成t_frame后,其坐标会隐式的转换为CGRectMake(-20, -20, 10, 10),因为宽高不可能出现负值;这时通过t_frame.的方式获取的值都是错误的,而CGRectGet会自动帮您处理这些隐式转换。
正例:
CGRectGetWidth(frame)、CGRectGetMinX(frame)、CGRectGetMaxX(frame)
反例:
frame.size.width、frame.origin.x、frame.size.width + frame.origin.x
-
[建议] 单行字符数限制不超过150个,超出需要换行(空格可以除外),换行时遵循如下原则:
Tips:
1. 第二行相对第一行缩进4个空格,从第三行起不再继续缩进。
2. 运算符与下文一起换行。
3. 方法调用的点符号与下文一起换行。
正例:
- (void)setImageWithURL:(nullable NSURL *)imageURL
placeholder:(nullable UIImage *)placeholder
options:(YYWebImageOptions)options
progress:(nullable YYWebImageProgressBlock)progress
ransform:(nullable YYWebImageTransformBlock)transform
completion:(nullable YYWebImageCompletionBlock)completion;
-
[建议] 不可变对象尽量使用copy
修饰,如果重写使用copy
修饰的set
方法,请注意调用copy
方法。
-
[建议] 对于一些体积小并且重要的信息,不要频繁的存储到本地,可以使用NSUserDefaults
进行存储。它会在合适的时机存储到本地,这避免了频繁的写入操作,而且在某些极端情况下它也能保证数据存储到本地(例如程序闪退等情况)。
-
[建议] 在多线程环境下谨慎使用可变集合,必要时候可以采用加锁或GCD
的同步线程进行保护,或者在访问可变集合时先将其copy
为不可变对象然后再对其访问。
-
[建议] 头文件中尽量不要声明成员变量而是使用属性代替。
-
[建议] 头文件中的属性尽量声明为只读,可以在实现文件中再将属性声明为可读可写。
正例:
@interface WXYZModel : NSObject
@property (nonatomic, readonly) NSString *name;
@end
@interface WXYZModel ()
@property (nonatomic, strong) NSString *name;
@end
-
[建议] 不要使用一个类去维护多个类的内容,例如一个常量类维护所有的常量类,要按常量功能进行归类,分开维护。
Tips: 大而全的类,杂乱无章,使用查找功能才能定位到具体位置,不利于理解也不利于维护。
正例:
缓存相关常量类放在CacheCosts下,系统配置相关常量类放在SystemConfigConsts下。
-
[建议] 如果大括号内为空,则简洁的写成{}就行。
-
[建议] 没有必要增加多余空格来使上下代码的等号对齐。
正例:
int a1 = 1;
long a2 = 3;
NSString *a3 = @"";
反例:
int a1 = 1;
long a2 = 3;
NSString *a3 = @"";
-
[建议] 少用if else
,可以使用if return
替换,if
嵌套最好不超过5层。
正例:
if (x == 1) {
……
return;
}
if (x == 2) {
……
return;
}
反例:
if (x == 1) {
……
} else if (x == 2) {
……
}
-
[建议] 尽量避免采用取反逻辑运算符,因为取反逻辑不利于快速理解。
正例:
if (array == nil) {
……
}
反例:
if (!array) {
……
}
-
[建议] 如果用到了很多协议,必要时可以把协议封装到一个单独的头文件中,这样做不仅可以减小编译时间,还能避免循环引用。
-
[建议] 使用Switch枚举时尽量将所有枚举类型都列举出来而不使用default
,这样下次增加枚举类型时如果Switch
没有处理会有警告信息。
-
[建议] 尽量使用字面量语法创建对象,少用与之等价的方法。
Tips:
OC中的NSArray、NSString、NSDictionay、NSNumber都有与之对应的字面量语法: @[]、@""、@{}、@();使用它们有以下优点:
1. 简单易读,提高代码的可读性和可维护性。
2. 使用字面量创建数组、字典时如果元素里在nil则会抛出异常,而使用arrayWithObjects:这些等价方法创建则会丢失nil后的数据,抛出异常能让你知道这里有问题及时修改防止问题在线上发生。
缺点:
1. 使用字面量创建的对象默认是不可变的,如果要创建可变对象需要进行mutableCopy操作。
2. 不支持子类,如果你创建了一个NSString的子类,@""并不会返回你想要的子类对象。
-
[建议] 头文件中尽量少引用其他头文件,尽量使用@class
向前声明,每次引入其他头文件时问问自己是否必须要这样做。
-
[建议] UI
控件建议使用weak
修饰而不是strong
修饰。
这只是一篇关于iOS的代码规范,所以某些需要和服务端需要统一的规范(例如错误码)并没有提到,还有些关于如何编写安全代码方面的规范也只是略微提到,因为关于如何写出更安全的代码应该不属于代码规范层面;欢迎大家提出更好的建议或改进,我也会不断更新完善;最后祝大家码出开心,码出质量。