iOS Coding Style Guide
命名规范
我们尽可能遵守 Apple 的命名约定, 其推荐使用长的,描述性强的方法和变量名,使其阅读起来更加清晰易懂。不能随意使用缩写,导致其他人员阅读代码困难。Coding Guidelines for Cocoa
前缀
项目名称、类名、文件名都应该保持一致的前缀名,NAP iOS项目使用YN作为前缀。
类名
大驼峰式命名:每个单词的首字母都采用大写字母。
@interface YNHomeViewController : YNBaseViewController @end
property变量
小驼峰式命名:
- 第一个单词以小写字母开始,后面的单词的首字母全部大写。
- 属性的关键字推荐按照 原子性,内存管理,读写的顺序排列。
- Block、NSString属性应该使用copy关键字。
- 代理使用weak关键字防止循环引用。
@property (nonatomic, strong, readonly) UILabel *subjectLabel; @property (nonatomic, copy) NSString *statusName; @property (nonatomic, weak) id <YNLogisticsProductsCellDelegate>delegate; @property (nonatomic, copy) void(^actionBlock)(__nullable id);
枚举
- Enum中枚举内容的命名需要以该Enum类型名称开头。
- NS_ENUM定义通用枚举,NS_OPTIONS定义位移枚举。
typedef NS_OPTIONS(NSUInteger, YNLogLevel){ YNLogLevelError = 1 << 0, YNLogLevelWarning = 1 << 1, YNLogLevelInfo = 1 << 2, YNLogLevelDebug = 1 << 3, YNLogLevelVerbose = 1 << 4 }; typedef NS_ENUM(NSUInteger, YNContentAlignment) { YNContentAlignmentLeft = 0, YNContentAlignmentCenter = 1, YNContentAlignmentRight = 2 };
宏和常量
- 对于宏定义的常量
define 预处理定义的常量全部大写,单词间用 _ 分隔,宏定义中如果包含表达式或变量,表达式或变量必须用小括号括起来。
- 对于类型常量
对于局限于某编译单元(实现文件)的常量,以字符k开头,例如kAnimationDuration,且需要以static const修饰。
对于定义于类头文件的常量,外部可见,则以定义该常量所在类的类名开头,例如EOCViewClassAnimationDuration, 仿照苹果风格,在头文件中进行extern声明,在实现文件中定义其值。
//宏定义的常量 #define ANIMATION_DURATION 0.3 #define MY_MIN(A, B) ((A)>(B)?(B):(A)) //局部类型常量 static const NSTimeInterval kAnimationDuration = 0.3; //外部可见类型常量 //YNMagazinePresenter.h extern NSString * const YNMagazineChannelTypeFemaleNewest; //YNMagazinePresenter.m NSString * const YNMagazineChannelTypeFemaleNewest = @"1";
方法
- 方法名用小驼峰式命名。
- 方法名不要使用new作为前缀。
- 不要使用and来连接属性参数,如果方法描述两种独立的行为,使用and来串接它们。
- 方法实现时,如果参数过长,则令每个参数占用一行,以冒号对齐。
- 一般方法不使用前缀命名,私有方法可以使用统一的前缀来分组和辨识。
- 方法名要与对应的参数名保持高度一致。
//不要使用 and 来连接属性参数 - (int)runModalForDirectory:(NSString *)path file:(NSString *)name types:(NSArray *)fileTypes; //推荐 - (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes; //反对 //表示对象行为的方法、执行性的方法 - (void)insertModel:(id)model atIndex:(NSUInteger)atIndex; - (void)selectTabViewItem:(NSTableViewItem *)tableViewItem //返回性的方法 - (instancetype)arrayWithArray:(NSArray *)array; //参数过长的情况 - (void)longMethodWith:(NSString *)theFoo rect:(CGRect)theRect interval:(CGFloat)theInterval; //不要加get - (NSSize)cellSize; //推荐 - (NSSize)getCellSize; //反对
方法参数
- 不要在参数名中使用 pointer 或 ptr,让参数的类型来说明它是指针
- 避免使用 one, two,...,作为参数名,更不要用1,2,...
- 避免为节省几个字符而缩写
采用以下参数标签与参数组合的惯例
...action:(SEL)aSelector ...alignment:(int)mode ...atIndex:(int)index ...content:(NSRect)aRect ...doubleValue:(double)aDouble ...floatValue:(float)aFloat ...font:(NSFont *)fontObj ...frame:(NSRect)frameRect ...intValue:(int)anInt ...keyEquivalent:(NSString *0charCode ...length:(int)numBytes ...point:(NSPoint)aPoint ...stringValue:(NSString *)aString ...tag:(int)anInt ...target:(id)anObject ...title:(NSString *)aString
代码注释规范
优秀的代码大部分是可以自描述的,我们完全可以用代码本身来表达它到底在干什么,而不需要注释的辅助。
但并不是说一定不能写注释,有以下三种情况比较适合写注释:
- 公共接口(注释要告诉阅读代码的人,当前类能实现什么功能)。
- 涉及到比较深层专业知识的代码(注释要体现出实现原理和思想)。
- 容易产生歧义的代码(但是严格来说,容易让人产生歧义的代码是不允许存在的)。
除了上述这三种情况,如果别人只能依靠注释才能读懂你的代码的时候,就要反思代码出现了什么问题。
属性注释
写在属性上方,三斜线开头斜线后面跟一个空格后面跟注释内容;可使用Xcode自带模板快捷添加(默认快捷键:⌥+⌘+/)。
/// 文章ID,用于下面的更多精彩内容去重 @property (nonatomic, copy) NSArray <NSString *> *articleIds;
方法声明注释
公开接口,重要的方法,分类,以及协议,都应该伴随文档(注释):
三斜线开头,斜线后跟一个空格;第一行为方法描述,第二行开始为方法参数以@param标记(默认快捷键:⌥+⌘+/)。
/// 快速生成渐变图层 /// @param frame 图层位置大小 /// @param beginColor 起始颜色 /// @param endColor 结束颜色 + (CAGradientLayer *)gradientLayerWithFrame:(CGRect )frame beginColor:(UIColor *)beginColor endColor:(UIColor *) endColor;
TODO
使用// TODO: 说明 标记一些未完成的或完成的需要优化的地方。
// TODO: restore scrollView state // This is temporary solution. Have to implement the save and restore scrollView state UIScrollView *superscrollView = strongLastScrollView;
FIXME
使用// FIXME: 说明 标记一些有bug的或以临时方案解决的地方。(使用// FIXME: 标记在检索器会出现提醒图)
// FIXME: 等待接口字段 // FIXME: someday check the return codes on these binds.
代码格式规范
指针*位置
跟在类型后面用空格隔开紧跟着变量名。
NSURL *url; - (id)initWithChildren:(NSArray *)children;
方法声明和实现
+、-后面跟一个空格,方法名和第一个参数之间不留空格;方法体的第一个大括号跟在方法名末尾用空格隔开。
// 方法声明 - (id)initWithChildren:(NSArray *)children; + (instancetype)actionWithTitle:(NSString *)title style:(UIPreviewActionStyle)style handler:(void (^)(UIPreviewAction *action, UIViewController *previewViewController))handler; // 方法实现 - (id)initWithChildren:(NSArray *)children { self = [super initWithChildren:children]; return self; } + (instancetype)actionWithTitle:(NSString *)title style:(UIPreviewActionStyle)style handler:(void (^)(UIPreviewAction *action, UIViewController *previewViewController))handler { UIPreviewAction *action = [[UIPreviewAction alloc] init]; action.title = title; ... return action; }
对implementation方法进行分组
#pragma mark - Life cycle - (void)viewDidLoad { [super viewDidLoad]; } #pragma mark - Private methods #pragma mark - Delegates #pragma mark - Event response #pragma mark - Setter #pragma mark - Getter
编码规范
if语句
- 尽量列出所有分支(穷举所有的情况),而且每个分支都须给出明确的结果。
- 不要使用过多的分支,要善于使用return来提前返回错误的情况,把最正确的情况放到最后返回。
- 条件过多,过长的时候应该换行;条件表达式如果很长,则需要将他们提取出来赋给一个BOOL值,或者抽取出一个方法。
NSString *hintStr; if (count < 3) { hintStr = @"Good"; } else { hintStr = @""; }
if (!user.userName) return NO; if (!user.password) return NO; if (!user.email) return NO; return YES;
if (condition1 && condition2 && condition3) { // Do something } BOOL finalCondition = condition1 && condition2 && condition3 && condition4 if (finalCondition) { // Do something } if ([self canDelete]){ // Do something } - (BOOL)canDelete { BOOL finalCondition1 = condition1 && condition2 BOOL finalCondition2 = condition3 && condition4 return condition1 && condition2; }
for循环
避免在for循环内修改循环变量,防止for循环失去控制。如果必须修改,可以创建一个临时变量进行遍历或者使用NSArray的enumerateObjectsUsingBlock:方法进行遍历。
NSArray *arr = [NSArray arrayWithArray:mArr]; for (NSMutableDictionary *dic in arr) { if ([dic[@"a"] isEqualToString:@"3"]) { [mArr removeObject:dic]; } } [mArr enumerateObjectsUsingBlock:^( id obj, NSUInteger idx, BOOL *stop) { if ([[obj objectForKey: @"a"] isEqualToString:@"3"]) { *stop = YES ; [mArr removeObject:obj]; } }];
方法
- 单一性原则,每个函数的职责都应该划分的很明确(就像类一样)。
- 对于有返回值的方法,每一个分支都必须有返回值。
- 对输入参数的正确性和有效性进行检查。
- 如果在不同的方法内部有相同的功能,应该把相同的功能抽取出来单独作为另一个方法。
- 将方法内部比较复杂的逻辑提取出来作为单独的方法。
// 推荐写法 - (void)viewDidLoad { [super vidwDidLoad]; [self setupUI]; [self prepareData]; } - (void)setupUI { } - (void)prepareData { } // 不推荐 - (void)viewDidLoad { [super vidwDidLoad]; [self build]; } - (void)build { // setupUI ... // prepareData ... }
// 推荐写法 - (int)function { if(condition1){ return count1 }else if(condition2){ return count2 }else{ return defaultCount } } // 不推荐 - (int)function { if(condition1){ return count1 }else if(condition2){ return count2 } }
- (void)functionWithParam:(id)param { if([param isUnavailable]){ return; } //Do some right thing }
属性和变量
- 数据成员保持最小公开原则
- 在不需要改变一个属性时,添加readonly
- 最好使用auto-synthesis。
- 当使用属性,实例变量时应该使用self.来访问,这意味着所有的属性将很容易区分,因为它们都使用 self. 开头
nil / Nil / NULL / NSNull
Objective-C中默认所有的指针指向nil,nil最显著的行为是可以有消息发送给它,结果返回0
if (name != nil && [name isEqualToString:@"Steve"]) { ... } // …可以被简化为: if ([name isEqualToString:@"steve"]) { ... }
Symbol Value Meaning NULL (void *)0 literal null value for C pointers nil (id)0 literal null value for Objective-C objects Nil (Class)0 literal null value for Objective-C classes NSNull [NSNull null] singleton object used to represent null Booleans
Objective-C用BOOL来编码真值。它是signed char的typedef,并且用宏YES和NO来相应的表示真和假;在Objective-C中,当遇到处理真值的参数,属性和实例变量时,使用类型BOOL。当分配字面值时,使用宏YES和NO;如果BOOL属性的名称表示为一个形容词,该属性可以省略“is”字头,但需要为get方法按照常规指定名称。
@property (assign, getter=isEditable) BOOL editable;
判断真假时不要与字面值比较
// 推荐 if (someObject) { } if (![anotherObject boolValue]) { } // 不推荐 if (someObject == nil) {} if ([anotherObject boolValue] == NO) {} if (isAwesome == YES) {} // Never do this. if (isAwesome == true) {} // Never do this.
Name Typedef Header True Value False Value BOOL signed char objc.h YES NO bool _Bool (int) stdbool.h true false boolean unsigned char MacTypes.h TRUE FALSE NSNumber __NSCFBoolean Foundation.h @(YES) @(NO) CFBooleanRef struct CoreFoundation.h kCFBooleanTrue kCFBooleanFalse
团队约定
字典类型参数使用YNParameters进行包装,可以避免空数据带来的crash。
YNParameters *parameter = [YNParameters new]; [parameter addParameter:@"order_id" value:order_id]; [parameter addParameter:@"current_page" value:current_page]; [parameter addParameter:@"order_type" value:orderType]; [YNSensorsAnalytic trackEvent:YNSensorsAnalytic_OrderPageOrderDelete properties:parameter.parameterDict];
数组取值使用YYKit的objectOrNilAtIndex:方法,避免数组越界。
__kindof UIViewController *viewController = [self.viewControllers objectOrNilAtIndex:index];
字符串文案使用yn_safeString:方法进行转换,避免显示异常。
NSString *title = [NSString yn_safeString:model.title];
使用枚举定义有限类型而非简单数字判断。
typedef NS_ENUM(NSUInteger, YNContentAlignment) { YNContentAlignmentLeft = 0, YNContentAlignmentCenter = 1, YNContentAlignmentRight = 2, }; if (aligment == YNContentAlignmentCenter) { ... } else { ... } switch (aligment) { case YNContentAlignmentCenter:{ ... break; } case YNContentAlignmentRight:{ ... break; } default: { ... break; } } // 不推荐 if (aligment == 2) { ... } else { ... } switch (aligment) { case 1:{ ... break; } case 2:{ ... break; } default: { ... break; } }
所有测试代码使用DEBUG宏包起来,不将测试代码上传到远程仓库。
#if DEBUG - (void)mock { self.title = @"PDP"; self.brand = @"NET-A-PORTER"; } #endif
- 所有接口地址统一在YNNetInterface进行维护。
评论