本文转自:Objective-C 编码风格指南 | www.samirchen.comhtml
保证本身的代码遵循团队统一的编码规范是一个码农的基本节操,可以进入一个有统一编码规范的团队则是一个码农的福气。ios
本文主要是对如下几个编码规范的整理:git
这里有些关于编码风格 Apple 官方文档,若是有些东西没有说起,能够在如下文档来查找更多细节:github
使用美式英语。别用拼音。objective-c
推荐:xcode
UIColor *myColor = [UIColor whiteColor];
不推荐:安全
UIColor *myColour = [UIColor whiteColor]; UIColor *woDeYanSe = [UIColor whiteColor];
使用 #pragma mark -
根据「代码功能类别」、「protocol/delegate 方法实现」等依据对代码进行分块组织。代码的组织顺序从总体上尽可能遵循咱们的认知顺序,组织规范以下:app
// 先描述这个类是什么,它的属性有什么。 // 每一个属性的 getter 方法在前,setter 方法在后。属性的 getter/setter 方法的顺序与属性声明顺序一致。 #pragma mark - Property - (id)customProperty {} - (void)setCustomProperty:(id)value {} // 再描述这个类的生命周期,从出生到消亡。 // 按照生命周期的顺序来排序相关方法。 #pragma mark - Lifecycle - (instancetype)init {} - (void)viewDidLoad {} - (void)viewWillAppear:(BOOL)animated {} - (void)viewDidAppear:(BOOL)animated {} - (void)viewWillDisappear:(BOOL)animated {} - (void)viewDidDisappear:(BOOL)animated {} - (void)didReceiveMemoryWarning {} - (void)dealloc {} // 若是这是一个 UIViewController 类,能够接着描述这个页面能够跳转到的其余页面。 #pragma mark - Navigation - (void)goToMainPage {} - (void)goToUserPage {} // 接着描述这个类的响应方法,能作哪些交互。 // 好比:按钮点击的响应方法、手势的响应方法等等。 #pragma mark - Action - (IBAction)submitData:(id)sender {} // 而后描述这个类的其余分组方法。这里的分组能够是多个,如何分组能够由你扩展。 #pragma mark - <Other Functional Grouping> - (void)someGroupedMethod {} // 接下来描述这个类实现的 Protocol/Delegate 的方法。 // 先放自定义的 Protocol/Delegate 方法,后放官方提供的 Protocal/Delegate 方法。 #pragma mark - <Protocol/Delegate Conformance> #pragma mark - UITextFieldDelegate #pragma mark - UITableViewDataSource #pragma mark - UITableViewDelegate // 而后是对继承的父类中方法重载。 // 先发自定义的父类方法重载,后方官方父类的方法重载。 #pragma mark - <Superclass Overridden> - (void)someOverriddenMethod {} #pragma mark - NSObject - (NSString *)description {}
代码如流水同样,去叙述一个类。iphone
if
/else
/switch
/while
等)老是在同一行打开,但在新的一行关闭。推荐:ide
if (user.isHappy) { // Do something } else { // Do something else }
不推荐:
if (user.isHappy) { // Do something } else { // Do something else }
@synthesize
和 @dynamic
的声明应该在实现代码中各占一行。推荐:
// blocks are easily readable [UIView animateWithDuration:1.0 animations:^{ // something } completion:^(BOOL finished) { // something }];
不推荐:
// colon-aligning makes the block indentation hard to read [UIView animateWithDuration:1.0 animations:^{ // something } completion:^(BOOL finished) { // something }];
当你写代码注释时,须要注意你的注释是解释为何要有这段代码。一段注释要确保跟代码一致更新,不然就删掉。
通常避免使用块注释,这样占用空间太大,代码应该尽可能作到自解释,代码即注释。固然,也有例外:你的注释是为了生成文档用。
你多是从 Java、Python、C++ 或是其余语言转过来的,可是来到 Objective-C 这地盘,请遵照苹果的命名规范,这样你才能使得本身的代码与周边和谐统一,尤为须要注意 memory management rules (NARC) 相关的命名规范.
长的、描述性的方法和变量命名是好的,这使得代码更容易被读懂。
推荐:
UIButton *settingsButton;
不推荐:
UIButton *setBut;
在类名和常量名上应该使用两个或三个字母的前缀(好比:CX、TB 等等)。可是在 Core Data 实体命名时应该省略前缀。
常量应该使用驼峰式命名规则,全部的单词首字母大写,并加上与类名有关的前缀。
推荐:
static NSTimeInterval const RWTTutorialViewControllerNavigationFadeAnimationDuration = 0.3;
不推荐:
static NSTimeInterval const fadetime = 1.7;
属性也是使用驼峰式命名规则,但首单词的首字母小写。对属性使用 auto-synthesis,而不是手动编写 @synthesize 语句,除非你有一个好的理由。
推荐:
@property (strong, nonatomic) NSString *descriptiveVariableName;
不推荐:
id varnm;
当使用属性时,用 self.
来访问,这就意味着全部的属性都颇有辨识度,由于他们前面有 self.
。
可是有 2 个特列:
局部变量不要包含下划线。
在方法签名中,应该在方法类型(-/+ 符号)以后有一个空格。在方法各段之间应该也有一个空格(符合 Apple 的风格)。在参数以前应该包含一个描述性的关键字来描述参数。
and
这个词的用法应该保留,它不该该用于多个参数之间。
推荐:
- (void)setExampleText:(NSString *)text image:(UIImage *)image; - (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag; - (id)viewWithTag:(NSInteger)tag; - (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
不推荐:
-(void)setT:(NSString *)text i:(UIImage *)image; - (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag; - (id)taggedView:(NSInteger)tag; - (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height; - (instancetype)initWith:(int)width and:(int)height; // Never do this.
变量尽可能以描述性的方式来命名。除了在 for()
循环中,应该尽可能避免单个字符的变量命名。
表示指针的星号应该和变量名在一块儿,好比:应该是 NSString *text
,而不是 NSString* text
或者 NSString * text
,除了一些特别的状况。
应该使用私有属性,而不要再使用实例变量了。这样能够保持代码的一致性。
除了在一些初始化方法(init
, initWithCoder:
, etc…)、销毁方法(dealloc
)和自定义的 setters/getters 方法中外,不要直接使用下划线的方式访问实例变量。详情参见这里。
推荐:
@interface RWTTutorial : NSObject @property (strong, nonatomic) NSString *tutorialName; @end
不推荐:
@interface RWTTutorial : NSObject { NSString *tutorialName; }
属性特性的顺序应该是:存储特性、访问特性、原子特性、getter/setter。其中存储特性、原子特性应该显式地列出来,有助于新手阅读代码。与在 Interface Builder 链接 UI 元素时自动生成代码一致。
推荐:
@property (weak, nonatomic) IBOutlet UIView *containerView; @property (strong, nonatomic) NSString *tutorialName; @property (assign, readonly, nonatomic, getter=isFinished) BOOL finished;
不推荐:
@property (nonatomic, weak) IBOutlet UIView *containerView; @property (nonatomic) NSString *tutorialName;
具备值拷贝类型特定的属性(如:NSString)应该优先使用 copy
而不是 strong
。这是由于即便你声明一个 NSString
类型的属性,有人也可能传入一个 NSMutableString
的实例,而后在你没有注意的状况下修改它。
推荐:
@property (copy, nonatomic) NSString *tutorialName;
不推荐:
@property (strong, nonatomic) NSString *tutorialName;
点符号语法是对方法调用语法很方便的一种封装。在返回属性时,使用点符号语法,属性的 getter/setter 方法也能确保被调用。更多信息阅读这里。
咱们应该老是使用点符号语法来访问或者修改属性,由于它使得代码更加简洁。[]
则应该用在其余场景下。
推荐:
NSInteger arrayCount = self.array.count; // `count` is a property of NSArray. view.backgroundColor = [UIColor orangeColor]; [UIApplication sharedApplication].delegate; // `sharedApplication` is not a property of UIApplication.
不推荐:
NSInteger arrayCount = [self.array count]; // `count` is a property of NSArray. [view setBackgroundColor:[UIColor orangeColor]]; UIApplication.sharedApplication.delegate; // `sharedApplication` is not a property of UIApplication.
在建立 NSString
、NSDictionary
、NSArray
和 NSNumber
对象时,应该使用字面值语法。尤为须要注意建立 NSArray
和 NSDictionary
对象时,不能传入 nil
,不然会形成 crash。
推荐:
NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"]; NSDictionary *productManagers = @{@"iPhone": @"Kate", @"iPad": @"Kamal", @"Mobile Web": @"Bill"}; NSNumber *shouldUseLiterals = @YES; NSNumber *buildingStreetNumber = @10018;
不推荐:
NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil]; NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil]; NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES]; NSNumber *buildingStreetNumber = [NSNumber numberWithInteger:10018];
比起硬编码字符串或数字的形式,咱们应该常量来定义复用型变量,由于常量更容易被修改,而不须要咱们 find + replace。使用常量时,咱们应该使用 static
而不是 #define
一个类型不明的宏。
推荐:
static NSString * const RWTAboutViewControllerCompanyName = @"RayWenderlich.com"; static CGFloat const RWTImageThumbnailHeight = 50.0;
不推荐:
#define CompanyName @"RayWenderlich.com" #define thumbnailHeight 2
当使用枚举时,咱们要用 NS_ENUM()
而不是 enum
。
例如:
typedef NS_ENUM(NSInteger, RWTLeftMenuTopItemType) { RWTLeftMenuTopItemMain, RWTLeftMenuTopItemShows, RWTLeftMenuTopItemSchedule };
你能够显示的赋值:
typedef NS_ENUM(NSInteger, RWTGlobalConstants) { RWTPinSizeMin = 1, RWTPinSizeMax = 5, RWTPinCountMin = 100, RWTPinCountMax = 500, };
不推荐:
enum GlobalConstants { kMaxPinSize = 5, kMaxPinCount = 500, };
除非编译器强制要求,通常在 Case 语句中是不须要加括号的。当一个 Case 语句包含多行代码,应该加上括号。
switch (condition) { case 1: // ... break; case 2: { // ... // Multi-line example using braces break; } case 3: // ... break; default: // ... break; }
若是一段代码被多个 Case 语句共享执行,那就要用 fall-through,即在 Case 语句中删除 break
语句,让代码可以执行到下一个 Case 中去,为了代码清晰明了,用了 fall-through 时须要注释一下。
switch (condition) { case 1: // ** fall-through! ** case 2: // code executed for values 1 and 2 break; default: // ... break; }
在 Swith 中使用枚举类型时,是不须要 default
语句的,例如:
RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain; switch (menuType) { case RWTLeftMenuTopItemMain: // ... break; case RWTLeftMenuTopItemShows: // ... break; case RWTLeftMenuTopItemSchedule: // ... break; }
私有属性应该在类的实现文件(xxx.m)中的匿名扩展(Anonymous Category)中声明。除非是要去扩展一个类,不然不要使用命名扩展(Named Category)。若是你要测试私有属性,你能够经过 <headerfile>+Private.h
的方式把私有属性暴露给测试人员。
For Example:
@interface RWTDetailViewController () @property (strong, nonatomic) GADBannerView *googleAdView; @property (strong, nonatomic) ADBannerView *iAdView; @property (strong, nonatomic) UIWebView *adXWebView; @end
Objective-C 使用 YES
和 NO
做为 BOOL 值。所以,true
和 false
只应该在 CoreFoundation、C、C++ 代码中使用。因为 nil
会被解析为 NO
,因此没有必要在条件语句中去比较它。 另外,永远不要拿一个对象和 YES
比较,由于 YES
被定义为 1 而且 BOOL
值最多 8 bit。
这时为了在不一样代码中保持一致性和简洁性。
推荐:
if (someObject) {} if (![anotherObject boolValue]) {}
不推荐:
if (someObject == nil) {} if ([anotherObject boolValue] == NO) {} if (isAwesome == YES) {} // Never do this. if (isAwesome == true) {} // Never do this.
若是一个 BOOL
类型的属性是形容词,那么它的命名能够省略掉 “is” 前缀,可是咱们仍是须要给它指定惯用的 getter 方法名,例如:
@property (assign, getter=isEditable) BOOL editable;
更多内容详见:Cocoa Naming Guidelines。
条件语句应该使用大括号包围,即便可以不用时(好比条件代码只有一行)也不要省略大括号,这样能够最大可能的避免出错(好比条件语句不当心被注释了),同时也保持了大括号的使用风格一致。
推荐:
if (!error) { return success; }
不推荐:
if (!error) return success;
或者
if (!error) return success;
只有在能提升代码清晰性和可读性的状况下,才应该使用三元操做符 ?:
。单个条件判断时能够用到它,多个条件判断时仍是用 if
来提升代码可读性吧。通常来讲,使用三元操做符最好的场景是根据条件来赋值的时候。
非布尔类型的变量与某对象比较时最好加上括号来提升代码可读性,若是被比较的变量是布尔类型那就不用括号了。
推荐:
NSInteger value = 5; result = (value != 0) ? x : y; BOOL isHorizontal = YES; result = isHorizontal ? x : y;
不推荐:
result = a > b ? x = c > d ? c : d : y;
Init 方法应该遵循 Apple 生成代码模板的命名规则。返回类型应该使用 instancetype
而不是 id
。
- (instancetype)init { self = [super init]; if (self) { // ... } return self; }
当使用类构造方法时,应该返回的类型是 instancetype
而不是 id
。这样确保编译器正确地推断结果类型。
@interface Airplane + (instancetype)airplaneWithType:(RWTAirplaneType)type; @end
查看更多关于 instancetype 的信息:NSHipster.com。
当访问 CGRect 的 x
、y
、width
、height
属性时,老是使用 CGGeometry
functions 相关的函数,而不是直接从结构体访问。
All functions described in this reference that take CGRect data structures as inputs implicitly standardize those rectangles before calculating their results. For this reason, your applications should avoid directly reading and writing the data stored in the CGRect data structure. Instead, use the functions described here to manipulate rectangles and to retrieve their characteristics.
推荐:
CGRect frame = self.view.frame; CGFloat x = CGRectGetMinX(frame); CGFloat y = CGRectGetMinY(frame); CGFloat width = CGRectGetWidth(frame); CGFloat height = CGRectGetHeight(frame); CGRect frame = CGRectMake(0.0, 0.0, width, height);
不推荐:
CGRect frame = self.view.frame; CGFloat x = frame.origin.x; CGFloat y = frame.origin.y; CGFloat width = frame.size.width; CGFloat height = frame.size.height; CGRect frame = (CGRect){ .origin = CGPointZero, .size = frame.size };
当使用条件语句编写逻辑时,左手的代码应该是 "golden" 或 "happy" 路径。也就是说,不要嵌套多个 if
语句,即便写多个 return
语句也是 OK 的。
推荐:
- (void)someMethod { if (![someOther boolValue]) { return; } //Do something important }
不推荐:
- (void)someMethod { if ([someOther boolValue]) { //Do something important } }
单例对象应该使用线程安全的方式来建立共享实例。
+ (instancetype)sharedInstance { static id sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[self alloc] init]; }); return sharedInstance; }
这样会防止 possible and sometimes prolific crashes。
换行符主要是在提升打印和网上阅读时的代码可读性时显得很重要。
例如:
self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
一行较长的代码最好能换行再加一个 Tab。
self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
物理文件应该与 Xcode 项目目录保持同步来避免文件管理杂乱。建立任何 Xcode group 应该与文件系统中的文件夹保持映射。代码分类除了以类型分类,从大的方面上也应该以功能分类。
若是能够的话,打开 Xcode 的 Treat Warnings as Errors
来下降对 warning 的容忍度。若是有时候确实要忽略某一个 warning,你可使用 Clang's pragma feature。