1.命名规范
你的代码可能会被任何人阅读,而阅读的人来自不同的地方接受不同的教育不同的文化。
苹果的API设计命名都是非常好的参照典范,如果不知道如何设计命名,请多阅读文档。
1.通用命名规范
- 清晰性:好的命名应该是能自我描述的。
//正例:
removeObject:、[string stringByReplacingOccurrencesOfString:@"1" withString:@"2"]
//反例:
remove:(不清楚,要删除什么?)、string.replace("1", "2")
- 一致性:命名应该和上下文乃至全局保持一致性,相同类型或者具有相同作用的变量的命名方式应该相同或类似。
// 正例:
NSDictionary、NSArray、NSSet这几个集合类都是用count来表示数量而不是一个用count其它的用amount或其他单词,这体现了命名的一致性。
@property (readonly) NSUInteger count;
- 禁止自我指涉:命名不要自我指涉。通知、掩码常量等除外(通常指那些可以进行按位运算的枚举值)。
// 正例:
NSString
// 反例:
NSStringObject
- 杜绝过度缩写,严禁自创缩写(例如把button缩写为btn);国际通用缩写名称除外(例如ATM、GPS)。
// 正例:
destinationSelection、setBackgroundColor
// 反例:
destSel、setBgColor
- 杜绝无意义的拼音,国际通用名称或者地名人名除外(例如alibaba、taobao、hangzhou)。
// 反例 中英结合:
DaZhePromotion(打折促销)
- 命名要尽可能的清晰并简洁,如果两者不能兼得,则以清晰为主。
// 正例:
insertObject:atIndex:
// 反例:
insert:at:(不清晰,插入什么?at代表什么?)
代码和注释中都要避免使用任何语言的种族歧视性词语。
类名、协议名、函数名、常量名、枚举名等一些全局命名需要添加前缀,前缀需要大于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)
2.类命名规范
类名命名风格由"前缀+类的名称+类的类型"3个部分组成,前缀必须大于2个字符且全部大写(如果等于2个字符可以添加_);
类的名称遵循首字母大写驼峰式命名,类的名称要能表达出该类的功能;
类的类型必须使用全称,严禁使用缩写(例如vc代替viewController,cell代替TableViewCell),命名方式和名称命名一样首字母大写。
// 正例:
WXYZ_LoginViewControler
WXYZ_表示前缀,
Login表示该类跟登录相关,
ViewController表示该类是一个视图控制器而不是View。
3.方法命名规范
所有方法名称禁止以new开始。
所有方法名称禁止使用开始。(系统会使用开头命名一些系统私有方法)
内部私有方法需要增加前缀,前缀需要保持唯一性(例如p_)。给私有方法加前缀有2个好处:
- 增加辨识度,提高代码可读性。
- 避免自己的方法无意间覆盖了系统/框架同名的私有方法。
如果方法返回接收者的某个属性值,那么请直接使用属性名作为方法名。
// 正例:
- (CGSize)cellSize;
// 反例:
- (CGSize)getCellSize;
- 如果方法间接返回一个或多个值,那么请用"getXXX"命名,这种命名只适用于返回多个数据项的情况。
正例:
- (void)getCachedResponseForDataTask:(NSURLSessionDataTask *)dataTask
completionHandler:(void (^) (NSCachedURLResponse * __nullable cachedResponse))completionHandler;
- 方法的每个参数前都必须添加关键字。
// 正例:
- (void)sendAction:(SEL)aSelector toObject:(id)anObject forAllCells:(BOOL)flag;
// 反例:
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;
- 参数之前的单词尽量能描述参数的意义。
// 正例:
- (id)viewWithTag:(NSInteger)aTag;
// 反例:
- (id)taggedView:(int)aTag;
- 请不要使用
and连接接收者属性,尽管and读起来还算顺口,但随着你创建的方法参数的增加,这将会带来一系列的问题。
正例:
- (int)runModalForDirectory:(NSString *)path file:(NSString *) name types:(NSArray *)fileTypes;
反例:
- (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;
- 如果方法描述了两个独立的动作,则可以使用"and"连接起来。
正例:
- (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag;
4.Protocol命名规范
- Protocol中的方法命名以触发消息的对象名开头,省略类名前缀并首字母小写,如果它没有关联任何类则可以忽略这个规则。
正例:
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
- 除非Protocol方法只有一个参数,否则冒号需紧跟在类名后面。
正例:
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;
5.Category命名规范
- 分类命名也要和类命名一样添加前缀。
正例:
UIView (YYAdd)
反例:
UIView (Add)
分类中声明的方法名都要加上前缀。
Category中尽量不要声明属性,能挪尽量挪到主类中声明。
如果一个类比较复杂,那么建议使用分类组织代码(可以参考系统的UIView)。
6.Notification命名规范
- Notification的命名风格由"类名前缀" + “通知事件名称” + “Notification"3个部分组成。
正例:
UIApplicationDidBecomeActiveNotification
UIApplication表示该通知属于谁,
DidBecomeActive表示该通知的作用,
Notification表示它是一个通知。
- 如果一个类声明了delegate属性,通常情况下,这个类的delegate对象应该可以通过实现的delegate方法收到大部分通知消息。例如applicationDidBecomeActive:代理方法和NSApplicationDidBecomeActiveNotification通知(这其实也符合命名规范的基本原则"一致性")。
7.常量命名规范
- 如果常量局限于某"编译单元"之内,通常在前面加小写字母k作为前缀,若常量在全局可见,通常以类名作为前缀,然后采用首字母大写的驼峰式命令风格。
正例:
// 局部可见
const CGFloat kAnimationDuration = 2.0;
// 全局可见
const CGFloat UIActivityIndicatorViewAnimationDuration = 2.0;
8.Exception命名规范
- 命令规范和Notification一样,把后缀改为"Exception"即可。
9.文件(图片)命名规范
- 文件名全部小写。
- 采用_连接单词。
- 命名的风格:"模块_属性描述",可根据项目适当增加描述。
正例:
public_back@2x.png
2.编码规范
1.通用编码规范
如果有使用到CF(Core Foundation)等框架时,或者是在iOS10以下系统使用通知和KVO时,切记在dealloc方法中释放对象以及移除通知和监听。
在dealloc方法内禁止将self传递出去,如果self被retain,到下个runloop周期再释放则会多次释放导致crash。
禁止使用过时的方法或类,应该及时去了解和使用新方法或类。使用新方法时建议了解一下为什么废弃掉旧方法/类。
对剪切板的读取操作必须放在子线程中进行,因为用户可能在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宏定义。
宏定义声明常量的缺点:
- 宏定义只是简单的替换,缺少编译检查,运行期可能会出现溢出或数据错误等问题。
- 宏定义缺少类型,不方便编写文档用例。
- 宏定义可能会被不小心替换。
- 宏定义无法编写注释。
反例:
#define kTime @"10"
if (1 == 2) {
#define kTime @"20"
}
NSLog(@"time = %@", kTime);
以上代码中的if永远不会执行,但是编译器也会将kTime替换为@"20"
- 写一些公共方法时,请尽量使用内联函数或者全局函数而不是宏定义。函数不通过对象调用,所以不会走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:代理方法中设置数据。只在必要的时刻使用懒加载。
- 对象的创建需要依赖其他对象
- 对象可能被使用,也可能不被使用
- 对象创建比较消耗性能
懒加载方法内应该只执行需要初始化的操作,不应该有其他不必要的逻辑代码。
使用一目运算符时左右两边不能有空格。
正例:
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.的方式获取。
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个,超出需要换行(空格可以除外),换行时遵循如下原则:
- 第二行相对第一行缩进4个空格,从第三行起不再继续缩进。
- 运算符与下文一起换行。
- 方法调用的点符号与下文一起换行。
正例:
- (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为不可变对象然后再对其访问。
头文件中尽量不要声明成员变量而是使用属性代替。
头文件中的属性尽量声明为只读,可以在实现文件中再将属性声明为可读可写。
不要使用一个类去维护多个类的内容,例如一个常量类维护所有的常量类,要按常量功能进行归类,分开维护。
正例:
缓存相关常量类放在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没有处理会有警告信息。
尽量使用字面量语法创建对象,少用与之等价的方法。
- 优点
- 简单易读,提高代码的可读性和可维护性。
- 使用字面量创建数组、字典时如果元素里在nil则会抛出异常,而使用arrayWithObjects:这些等价方法创建则会丢失nil后的数据,抛出异常能让你知道这里有问题及时修改防止问题在线上发生。
缺点
- 使用字面量创建的对象默认是不可变的,如果要创建可变对象需要进行mutableCopy操作。
- 不支持子类,如果你创建了一个NSString的子类,@“"并不会返回你想要的子类对象。
头文件中尽量少引用其他头文件,尽量使用@class向前声明,每次引入其他头文件时问问自己是否必须要这样做。
UI控件建议使用weak修饰而不是strong修饰。
2. 类编码规范
如果超类的某个初始化方法不适用于子类,那么子类一定要覆写超类的这个方法并解决该问题或抛出异常。
尽量不要使用load类方法,如果必须要使用不能在方法内实现复杂逻辑或堵塞线程。
尽量减少继承,类的继承尽量不要超过3层,必要时刻可以考虑用分类、协议来代替继承。
把一些稳定的、公共的变量或者方法抽取到父类中。子类尽量只维持父类所不具备的特性和功能。
3. 方法编码规范
禁止在init等初始化方法内部、getter、setter、dealloc或其他特殊地方使用.语法访问属性。
方法参数在定义和传入时,逗号后面必须添加一个空格。
// 正例:
method(a1, a2, a3);
单个方法的行数建议不超过80行,注释、左右大括号、空行、回车等除外。
在实现文件内部也尽量使用.语法访问属性而不是使用_直接访问成员变量来保证风格统一。
4. Block编码规范
- 调用Block必须判空处理。
对于简单的Block可以使用三目运算进行判空处理,
例如 !self.block ?: self.block();
- 在Block内部使用外部变量时要注意循环引用的问题。
- 不一定在Block内使用self才会循环引用,如下情况也会造成循环引用:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { WXYZ_TitleTableViewCell *cell = ……… cell.refreshTableViewBlock = ^{ [tableView reloadData]; }; return cell; }Block内部是否要使用weak需要看Block本身和weak的这个对象是否存在直接或间接的相互引用,若无相互引用则不需要使用weak。
如果Block内部使用了strong修饰了外部的weak变量,那么当使用strong指向成员变量时需要进行判空,否则会崩溃
__weak typeof(self) weakSelf = self; cell.refreshTableViewBlock = ^{ __strong typeof(weakSelf) strongSelf = weakSelf; if (strongSelf != nil) { strongSelf->_name = @"name"; } };
5. 通知编码规范
在发送通知时,请使用userInfo进行传值,而不是object。
通知中心是以同步的方式发送请求的,所以不要在通知方法做一些复杂的计算,特别是当它处于主线程的时候,如果想发送异步通知可以使用NSNotificationQueue。
在工程里能不用通知尽量不用通知,通知虽然灵活强大,但是如果滥用会导致工程质量下降,出现问题时也比较难排查。
6. 注释编码规范
注释是起辅助作用的,清晰的命名可以不需要注释,注释应该帮助别人更快的理解该方法的使用和注意事项
当修改了方法实现时需要同步修改注释内容。
注释不要写的太冗长,要简单易读容易理解。
注释的双斜线和内容之间有且仅有一个空格。
对于代码注释需谨慎,代码被注释一般有2种可能,
- 1) 后续会恢复此段代码逻辑;需添加相应注释
- 2) 永久不用;建议直接删除。
别给糟糕的代码加注释,重构它。
3.工程结构规范
局部使用的常量、静态变量声明在@interface之前。
@property同一类型的声明放在一块,不同类型的声明用2行空格隔开。
不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开以提升可读性。
方法归类
#pragma mark - LifeCycle(生命周期相关的代码放在最上面)
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
#pragma mark - Public(公开方法)
// code...
// 上空一行
// 下空两行
#pragma mark - Private(私有方法)
//
#pragma mark - Override(需要覆盖父类的方法)
//
#pragma mark - Notification(通知方法)
//
#pragma mark - Delegate(Delegate需要实现的方法)
//
#pragma mark - getter/setter






