iOS编码规范,OC代码规范

作者:新葡京简介

一直以来都是在谈如何开发, 如开发的小技巧小经验

命名

Preferred :

UIColor *myColor = [UIColor whiteColor];

Not Preferred :

UIColor *myColour = [UIColor whiteColor];

语言

使用US英语

注释规范

今天为什么突然说起编程规范来了呢?

代码组织

使用#pragma mark - 将生命周期、分类方法和代理方法分块标注管理

#pragma mark - Lifecycle

- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}

#pragma mark - Custom Accessors

- (void)setCustomProperty:(id)value {}
- (id)customProperty {}

#pragma mark - IBActions

- (IBAction)submitData:(id)sender {}

#pragma mark - Public

- (void)publicMethod {}

#pragma mark - Private

- (void)privateMethod {}

#pragma mark - Protocol conformance
#pragma mark - UITextFieldDelegate
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate

#pragma mark - NSCopying

- (id)copyWithZone:(NSZone *)zone {}

#pragma mark - NSObject

- (NSString *)description {}

#pragma mark -  lazy

代码组织

在函数分组和protocol/delegate实现中使用#pragma mark -来分类方法,要遵循以下一般结构:

#pragma mark - Lifecycle
- (instancetype)init {...}

- (void)dealloc {...}

- (void)viewDidLoad {...}

- (void)viewWillAppear:(BOOL)animated {...}

- (void)didReceiveMemoryWarning {...}

#pragma mark - Custom Accessors
- (void)setCustomProperty:(id)value {...}

- (id)customProperty {...}

#pragma mark - IBActions
- (IBAction)submitData:(id)sender {...}

#pragma mark - Public
- (void)publicMethod {...}

#pragma mark - Private
- (void)privateMethod {...}

#pragma mark - Protocol conformance

#pragma mark - UITextFieldDelegate

#pragma mark - UITableViewDataSource

#pragma mark - UITableViewDelegate

#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {...}

#pragma mark - NSObject
- (NSString *)description {...}

文件头注释

文件头注释采用如下格式,该注释由xcode自动生成。如果你对其他人的原始代码作出重大的修改,请把你自己的名字添加到作者里面,并且填写相应的备注。

//
//  TestComment.m
//  TestVcard
//
//  Created by zhangyang on 2017/6/20.
//  Copyright © 2017年 myCompany. All rights reserved.
//

修改项目名称和公司名称的方法请参考下图。修改用户名请在系统偏好设置->用户账户中进行修改。

新葡京8455 1

image.png

因为在我看来, 编程规范是反映一个开发者或是一个开发团队素质和成熟度的最重要标志!

空格

  • 使用2个空格不要使用Tab键
  • 方法和其他后的大括弧在第一行开始在新的一行结束。
    Preferred:
if (user.isHappy) {
  //Do something
} else {
  //Do something else
}

Not Preferred:

if (user.isHappy)
{
    //Do something
}
else {
    //Do something else
}
  • 在方法中的参数中间添加一个空格方便阅读。
  • 优先使用自动生成,但是如果需要的话可以在实现文件中添加@ synthesize 和 @dynamic。
  • 调用方法时尽量避免方法中冒号对齐。当方法中包含3个以上的参数,使用冒号对齐会增加方法的可读性。但是,当方法中包含block时Xcode 的自动对齐会让代码看起来不容易分辨。
    Preferred:
// blocks are easily readable
[UIView animateWithDuration:1.0 animations:^{
  // something
} completion:^(BOOL finished) {
  // something
}];

Not Preferred:

// colon-aligning makes the block indentation hard to read
[UIView animateWithDuration:1.0
                 animations:^{
                     // something
                 }
                 completion:^(BOOL finished) {
                     // something
                 }];

空格

  • 缩进使用4个空格,确保在Xcode偏好设置来设置
  • 方法大括号和其他大括号(if/else/switch/while等)总是在同一行语句打开但在新行中关闭。
  • 注释//后要有空格

应该:

if (user.isHappy) {
    // Do something  
} else {
    // Do something else  
}

不应该:

if (user.isHappy)
{
    //Do something  
}
else {
    //Do something else  
}
  • 在方法之间应该有且只有一行,这样有利于在视觉上更清晰和更易于组织。在方法内的空白应该分离功能,但通常都抽离出来成为一个新方法。
  • 优先使用auto-synthesis。但如果有必要,@synthesize@dynamic应该在实现中每个都声明新的一行。
  • 应该避免以冒号对齐的方式来调用方法。因为有时方法签名可能有3个以上的冒号和冒号对齐会使代码更加易读。请不要这样做,尽管冒号对齐的方法包含代码块,因为Xcode的对齐方式令它难以辨认。

应该:

// 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  
                 }];  

接口注释

每个接口、类别以及协议应辅以注释,以描述它的目的及与整个项目的关系。使用如下格式:

/**
 添加描述

 @author zhangyang
 @date 2017/06/20
 */

可以将该段注释放在代码段中,如下图,我设置了快捷键为cmc。注意添加完成后需要修改描述和日期。

新葡京8455 2

注意这里的措辞很严厉哈, 我用了"最重要"

注释

所有的注释必须保持最新或者删除,避免块注释。

注释

  • 当需要注释时,注释应该用来解释这段特殊代码为什么要这样做。任何被使用的注释都必须保持最新或被删除。
  • 一般都避免使用块注释,因为代码尽可能做到自解释,只有当断断续续或几行代码时才需要注释。
  • 注释//后要有空格

应该:

// something 

不应该:

//something

变量和常量注释

下面几种情况下的常量和变量,都要添加注释说明,在上方添加注释。//和内容后面有一个空格。

  • 接口中定义的所有常量
  • 公有类的公有常量
  • 枚举类定义的所有枚举常量
  • 实体类的所有属性变量
// 用户名
@property (nonatomic, strong) NSString *userId;

//用户密码
@property (nonatomic, strong) NSString *userPwd;

当我们看到各种文不表意的函数命名、剪不断理还乱的文件和代码结构、以及不符合Apple官方基本规范的风格时

命名

尽量保持苹果的命名方式,长的,描述性和变量名都很好。
Preferred:

UIButton *settingsButton;

Not Preferred:

UIButton *setBut;

对于类名和常量名一般使用三字母前缀,在coreData 实体名称可以省略。常量一般遵循驼峰命名方式,使用相关类名最为前缀。
Preferred:

static NSTimeInterval const RWTTutorialViewControllerNavigationFadeAnimationDuration = 0.3;

Not Preferred:

static NSTimeInterval const fadetime = 1.7;

属性命名应该遵循首字母小写的驼峰原则。尽量使用自动生成而非手动。
Preferred:

@property (strong, nonatomic) NSString *descriptiveVariableName;

Not Preferred:

id varnm;

命名

Apple命名规则尽可能坚持,特别是与这些相关的memory management rules (NARC)。
长的,描述性的方法和变量命名是好的。

应该:

UIButton *settingsButton;  

不应该:

UIButton *setBut;  

三个字符前缀应该经常用在类和常量命名,但在Core Data的实体名中应被忽略。前缀'AN'应该被使用。

常量应该使用驼峰式命名规则,所有的单词首字母大写和加上与类名有关的前缀。

应该:

static NSTimeInterval const ANTutorialViewControllerNavigationFadeAnimationDuration = 0.3;  

不应该:

static NSTimeInterval const fadetime = 1.7;  

属性也是使用驼峰式,但首单词的首字母小写。对属性使用auto-synthesis,而不是手动编写@synthesize语句,除非你有一个好的理由。

应该:

@property (strong, nonatomic) NSString *descriptiveVariableName;  

不应该:

id varnm; 

方法注释

xcode 8已经集成了VVDocument,直接使用该注释格式。所有非系统方法都需要添加注释,包括在头文件和实现文件中。在相应的方法上方使用快捷键alt command /,即可生成该方法的注释:

/**
 注册用户

 @param userId 用户id
 @param userPwd 用户密码
 @return 0 成功  其他 失败
 */
- (NSInteger)registerUser:(NSString *)userId userPwd:(NSString *)userPwd;

注意,如果该方法所属的文件是他人创建的,需要添加上用户和日期。如果该方法不是与文件在同一时间创建的,也需要添加时间:

/**
 注册用户

 @author zhangyang
 @date 2017/06/20
 @param userId 用户id
 @param userPwd 用户密码
 @return 0 成功  其他 失败
 */
- (NSInteger)registerUser:(NSString *)userId userPwd:(NSString *)userPwd;

我想说: 程序员的加班 -- 你懂得(开发者制造的问题往往比他们解决的问题要多)

下划线

在初始化的时候,实例变量使用下划线。局部变量不要使用下划线。

下划线

  • 当使用属性时,实例变量应该使用self.来访问和改变。这就意味着所有属性将会视觉效果不同,因为它们前面都有self.
  • 但有一个特例:在初始化方法里,实例变量(例如,_variableName)应该直接被使用来避免getters/setters潜在的副作用。
  • 局部变量不应该包含下划线。

代码组织分类

在函数分组和protocol/delegate实现中使用#pragma mark -来分类方法,要遵循以下一般结构:

#pragma mark - Lifecycle  
- (instancetype)init {}  
- (void)dealloc {}  
- (void)viewDidLoad {}  
- (void)viewWillAppear:(BOOL)animated {}  
- (void)didReceiveMemoryWarning {}  
#pragma mark - Custom Accessors  
- (void)setCustomProperty:(id)value {}  
- (id)customProperty {}  
#pragma mark - IBActions  
- (IBAction)submitData:(id)sender {}  
#pragma mark - Public  
- (void)publicMethod {}  
#pragma mark - Private  
- (void)privateMethod {}  
#pragma mark - Protocol conformance  
#pragma mark - UITextFieldDelegate  
#pragma mark - UITableViewDataSource  
#pragma mark - UITableViewDelegate  
#pragma mark - NSCopying  
- (id)copyWithZone:(NSZone *)zone {}  
#pragma mark - NSObject  
- (NSString *)description {} 

如果读者觉得有比编程规范更重要的事情, 可以来辩, 欢迎拍砖~

方法

在( /-)符号后添加一个空格,关键词应该对参数有简单的描述。
关键字and的使用不建议使用在多参数的方法名中。

Preferred:

- (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;

Not Preferred:

-(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.

方法

  • 在方法签名中,应该在方法类型(-/ 符号)之后有一个空格。在方法各个段之间应该也有一个空格(符合Apple的风格)。在参数之前应该包含一个具有描述性的关键字来描述参数。
  • and这个词的用法应该保留。它不应该用于多个参数来说明,就像initWithWidth:height以下这个例子:

应该:

- (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. 

书写规范

本文的代码规范基于The official raywenderlich.com Objective-C style guide, 但不局限于此, 结合实际开发遇到的问题, 做了部分修订和补充

变量

应该使用可描述的变量名称。单字符变量名除了在for循环中使用其他情况不宜使用。
*号应该跟在变量前,NSString text not NSString text;
尽量使用私有属性代替实例变量。
Preferred:

@interface RWTTutorial : NSObject
@property (strong, nonatomic) NSString *tutorialName;
@end

Not Preferred:

@interface RWTTutorial : NSObject {
  NSString *tutorialName;
}

变量

  • 变量尽量以描述性的方式来命名。单个字符的变量命名应该尽量避免,除了在for()循环。
  • 星号表示变量是指针。例如,NSString *text既不是NSString* text也不是NSString * text,除了一些特殊情况下常量。
  • 私有变量应该尽可能代替实例变量的使用。尽管使用实例变量是一种有效的方式,但更偏向于使用属性来保持代码一致性。
  • 通过使用back属性(_variable,变量名前面有下划线)直接访问实例变量应该尽量避免,除了在初始化方法(init,initWithCoder:, 等…),dealloc方法和自定义的settersgetters。想了解关于如何在初始化方法和dealloc直接使用Accessor方法的更多信息,查看这里。

应该:

@interface ANTutorial : NSObject

@property (strong, nonatomic) NSString *tutorialName;

@end  

不应该:

@interface ANTutorial : NSObject {  
  NSString *tutorialName;  
}  

缩进

缩进统一为4个空格

Apple官方的代码规范, 供补充参考:

属性

Preferred:

@property (weak, nonatomic) IBOutlet UIView *containerView;
@property (strong, nonatomic) NSString *tutorialName;

Not Preferred:

@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic) NSString *tutorialName;

字符串属性推荐使用copy不建议使用strong。
Preferred:

@property (copy, nonatomic) NSString *tutorialName;

Not Preferred:

@property (strong, nonatomic) NSString *tutorialName;

属性特性

  • 所有属性特性应该显式地列出来,有助于新手阅读代码。属性特性的顺序应该是storageatomicity,与在Interface Builder连接UI元素时自动生成代码一致。

应该:

@property (weak, nonatomic) IBOutlet UIView *containerView;

@property (strong, nonatomic) NSString *tutorialName;  

不应该:

@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;  

花括号书写

  • 左大括号前不换行
  • 左大括号后换行
  • 右大括号前换行
  • 如果右大括号是一个语句、函数体或类的终止,则右大括号后换行; 否则不换行

例如,如果右大括号后面是else或逗号,则不换行。

  • The Objective-C Programming Language
  • Cocoa Fundamentals Guide
  • Coding Guidelines for Cocoa
  • iOS App Programming Guide

点语法

Preferred:

NSInteger arrayCount = [self.array count];
view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;

Not Preferred:

NSInteger arrayCount = self.array.count;
[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;

点符号语法

  • 点语法是一种很方便封装访问方法调用的方式。当你使用点语法时,通过使用gettersetter方法,属性仍然被访问或修改。想了解更多,阅读这里。
  • 点语法应该总是被用来访问和修改属性,因为它使代码更加简洁。[]符号更偏向于用在其他例子。

应该:

NSInteger arrayCount = [self.array count];  
view.backgroundColor = [UIColor orangeColor];  
[UIApplication sharedApplication].delegate;  

不应该:

NSInteger arrayCount = self.array.count;  
[view setBackgroundColor:[UIColor orangeColor]];  
UIApplication.sharedApplication.delegate;  

空格的使用

if、else、for、switch、while等逻辑关键字与后面的语句留一个空格隔开。

if (isSuccess) { 
    // TODO while booleanVariable is true
} else { 
    // TODO else
}

运算符两边各用一个空格隔开。

int result = a   b; //Good, = 和   两边各用一个空格隔开
int result=a b; //Bad,=和 两边没用空格隔开
for (int i = 0; i < 10; i  );   //good
for (int i=0;i<10;i  );   //bad

属性定义括号与前后各有一个空格,指针的*号紧靠变量名。如下所示:

@property (strong, nonatomic) UIWindow *window;

字面量

NSString, NSDictionary, NSArray, and NSNumber中尽量使用不可变的值。不要在NSArray 和NSDictionary中添加nil值会引起崩溃。
Preferred :

NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone": @"Kate", @"iPad": @"Kamal", @"Mobile Web": @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingStreetNumber = @10018;

Not Preferred:

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];

字面值

NSStringNSDictionaryNSArrayNSNumber的字面值应该在创建这些类的不可变实例时被使用。

  • 请特别注意nil值不能传入NSArrayNSDictionary字面值,因为这样会导致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];

行宽

尽量让你的代码保持在 80 列之内(或者100)。在xcode中的设置方式如下:

新葡京8455 3

image.png

  • 语言
  • 代码组织
  • 空格
  • 注释
  • 命名
    • 图标资源命名
    • 下划线
  • 方法
  • 变量
  • 属性
  • 点语法
  • Object Literals
  • 新葡京8455,常量
  • 枚举
  • Case表达式
  • 私有成员
  • 布尔
  • 条件语句
    • 三元运算符
  • 初始化方法
  • 类构造方法
  • CGRect函数
  • Golden Path
  • 错误处理
  • 单例
  • 换行
  • 头文件
  • 版权声明
  • Xcode Project

常量

常量应该是静态的常量,不应该是#define 除非作为宏使用。
Perferred:

static NSString * const RWTAboutViewControllerCompanyName = @"RayWenderlich.com";
static CGFloat const RWTImageThumbnailHeight = 50.0;

Not Perferred:

#define CompanyName @"RayWenderlich.com"#define thumbnailHeight 2

常量

常量是容易重复被使用和无需通过查找和代替就能快速修改值。常量应该使用static来声明而不是使用#define,除非显式地使用宏。

应该:

static NSString * const ANAboutViewControllerCompanyName = @"xinshui.com";  
static CGFloat const ANImageThumbnailHeight = 50.0;  

不应该:

#define CompanyName @"xinshui.com" 
#define thumbnailHeight 2  

方法声明和定义

Tip
-   和返回类型之间须使用一个空格,参数列表中只有参数之间可以有空格。

方法应该像这样:

- (void)doSomethingWithString:(NSString *)theString { 
    ...
}

星号前有空格。当写新的代码时,要与先前代码保持一致。

如果一行有非常多的参数,将每个参数单独拆成一行。如果使用多行,将每个参数前的冒号对齐。

- (void)doSomethingWith:(GTMFoo *)theFoo 
                   rect:(NSRect)theRect
               interval:(float)theInterval { 
    ...
}

当第一个关键字比其它的短时,保证下一行至少有 4 个空格的缩进。这样可以使关键字垂直对齐,而不是使用冒号对齐:

- (void)short:(GTMFoo *)theFoo 
    longKeyword:(NSRect)theRect 
    evenLongerKeyword:(float)theInterval { 
    ...
}

使用美式英语

枚举类型

For Example:

typedef NS_ENUM(NSInteger, RWTLeftMenuTopItemType) {
  RWTLeftMenuTopItemMain,
  RWTLeftMenuTopItemShows,
  RWTLeftMenuTopItemSchedule
};

你可以明确值的分配

typedef NS_ENUM(NSInteger, RWTGlobalConstants) {
  RWTPinSizeMin = 1,
  RWTPinSizeMax = 5,
  RWTPinCountMin = 100,
  RWTPinCountMax = 500,
};

Not Preferred:

enum GlobalConstants {
  kMaxPinSize = 5,
  kMaxPinCount = 500,
};

枚举类型

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

例如:

typedef NS_ENUM(NSInteger, ANLeftMenuTopItemType) {  
  ANLeftMenuTopItemMain,  
  ANLeftMenuTopItemShows,  
  ANLeftMenuTopItemSchedule  
}; 

你也可以显式地赋值(展示旧的k-style常量定义):

typedef NS_ENUM(NSInteger, ANGlobalConstants) {  
  ANPinSizeMin = 1,  
  ANPinSizeMax = 5,  
  ANPinCountMin = 100,  
  ANPinCountMax = 500,  
}; 

旧的k-style常量定义应该避免除非编写Core Foundation C的代码。

不应该:

enum GlobalConstants {  
  kMaxPinSize = 5,  
  kMaxPinCount = 500,  
}; 

方法调用

Tip
方法调用应尽量保持与方法声明的格式一致。当格式的风格有多种选择时,新的代码要与已有代码保持一致。

调用时所有参数应该在同一行:

[myObject doFooWith:arg1 name:arg2 error:arg3];

或者每行一个参数,以冒号对齐:

[myObject doFooWith:arg1 
               name:arg2 
              error:arg3];

不要使用下面的缩进风格:

[myObject doFooWith:arg1 name:arg2 // some lines with >1 arg 
              error:arg3];

[myObject doFooWith:arg1 
               name:arg2 error:arg3];

[myObject doFooWith:arg1 
          name:arg2 // aligning keywords instead of colons 
          error:arg3];

方法定义与方法声明一样,当关键字的长度不足以以冒号对齐时,下一行都要以四个空格进行缩进。

[myObj short:arg1 
    longKeyword:arg2 
    evenLongerKeyword:arg3];

Preferred:

case 状态

当在switch中使用枚举类型,default是不需要的。

RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain;

switch (menuType) {
  case RWTLeftMenuTopItemMain:
    // ...
    break;
  case RWTLeftMenuTopItemShows:
    // ...
    break;
  case RWTLeftMenuTopItemSchedule:
    // ...
    break;
}

Case语句

大括号在case语句中并不是必须的,除非编译器强制要求。当一个case语句包含多行代码时,大括号应该加上。

switch (condition) {  
  case 1:  
    // ...  
    break;  
  case 2: {  
    // ...  
    // Multi-line example using braces  
    break;  
  }  
  case 3:  
    // ...  
    break;  
  default:   
    // ...  
    break;  
}  

有很多次,当相同代码被多个cases使用时,一个fall-through应该被使用。一个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;  
}  

当在switch使用枚举类型时,default是不需要的。

例如:

ANLeftMenuTopItemType menuType = ANLeftMenuTopItemMain;  
switch (menuType) {  
  case ANLeftMenuTopItemMain:  
    // ...  
    break;  
  case ANLeftMenuTopItemShows:  
    // ...  
    break;  
  case ANLeftMenuTopItemSchedule:  
    // ...  
    break;  
}  

@public和 @private

Tip
@public和 @private访问修饰符应该以一个空格缩进。

与 C 中的 public, private以及 protected非常相似。

@interface MyClass : NSObject { 
 @public
  ... 
 @private 
  ...
}
@end
UIColor *myColor = [UIColor whiteColor];

私有属性

私有属性应该在类的实现文件中的类扩展(匿名分类)中声明,命名分类(比如RWTPrivate或private)应该从不使用除非是扩展其他类。匿名分类应该通过使用<headerfile> Private.h文件的命名规则暴露给测试。

@interface RWTDetailViewController ()
@property (strong, nonatomic) GADBannerView *googleAdView;
@property (strong, nonatomic) ADBannerView *iAdView;
@property (strong, nonatomic) UIWebView *adXWebView;
@end

私有属性

私有属性应该在类的实现文件中的类扩展(匿名分类)中声明,命名分类(比如ANPrivateprivate)应该从不使用除非是扩展其他类。匿名分类应该通过使用<headerfile> Private.h文件的命名规则暴露给测试。

例如:

@interface ANDetailViewController ()  
@property (strong, nonatomic) GADBannerView *googleAdView;  
@property (strong, nonatomic) ADBannerView *iAdView;  
@property (strong, nonatomic) UIWebView *adXWebView;  
@end  

异常

Tip
每个 @标签应该有独立的一行,在 @与 {}之间需要有一个空格,@catch
与被捕捉到的异常对象的声明之间也要有一个空格。

如果决定使用 Objective-C 的异常,那么就按下面的格式。不过最好先看看 避免抛出异常 了解下为什么不要使用异常。

@try { 
    foo();
}
@catch (NSException *ex) { 
    bar(ex);
}
@finally {
    baz();
}

Not Preferred:

布尔类型

Preferred:

if (someObject) {}
if (![anotherObject boolValue]) {}

Not Preferred:

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

如果BOOL属性的名字是一个形容词,属性就能忽略"is"前缀,但要指定get访问器的惯用名称。

@property (assign, getter=isEditable) BOOL editable;

布尔值

Objective-C使用YESNO。因为truefalse应该只在CoreFoundation,C或C 代码使用。既然nil解析成NO,所以没有必要在条件语句比较。不要拿某样东西直接与YES比较,因为YES被定义为1和一个BOOL能被设置为8位。

  • 这是为了在不同文件保持一致性和在视觉上更加简洁而考虑。

应该:

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前缀,但要指定get访问器的惯用名称。

例如:

@property (assign, getter=isEditable) BOOL editable;  

文字和例子从这里引用Cocoa Naming Guidelines。

协议名

Tip
类型标识符和尖括号内的协议名之间,不能有任何空格。

这条规则适用于类声明、实例变量以及方法声明。例如:

@interface MyProtocoledClass : NSObject<NSWindowDelegate> { 
    @private id<MyFancyDelegate> delegate_;
}
- (void)setDelegate:(id<MyFancyDelegate>)aDelegate;
@end
UIColor *myColour = [UIColor whiteColor];

条件语句

  • 后面要加大括号
    Preferred:
if (!error) {
  return success;
}

Not Preferred:

if (!error)
  return success;

or

if (!error)  return success;

条件语句

条件语句主体为了防止出错应该使用大括号包围,即使条件语句主体能够不用大括号编写(如,只用一行代码)。这些错误包括添加第二行代码和期望它成为if语句;还有,even more dangerous defect可能发生在if语句里面一行代码被注释了,然后下一行代码不知不觉地成为if语句的一部分。除此之外,这种风格与其他条件语句的风格保持一致,所以更加容易阅读。

应该:

if (!error) {  
  return success;  
}  

不应该:

if (!error)  
  return success;  

if (!error) return success;  

块(闭包)

Tip
块(block)适合用在 target/selector 模式下创建回调方法时,
因为它使代码更易读。块中的代码应该缩进 4 个空格。

取决于块的长度,下列都是合理的风格准则:

  • 如果一行可以写完块,则没必要换行。
  • 如果不得不换行,关括号应与块声明的第一个字符对齐。
  • 块内的代码须按 4 空格缩进。
  • 如果块太长,比如超过 20 行,建议把它定义成一个局部变量,然后再使用该变量。
  • 如果块不带参数,^{ 之间无须空格。如果带有参数,^( 之间无须空格,但 ) { 之间须有一个空格。
// The entire block fits on one line.
[operation setCompletionBlock:^{ [self onOperationDone]; }];

// The block can be put on a new line, indented four spaces, with the
// closing brace aligned with the first character of the line on which
// block was declared.
[operation setCompletionBlock:^{ 
    [self.delegate newDataAvailable];
}];

// Using a block with a C API follows the same alignment and spacing
// rules as with Objective-C.
dispatch_async(fileIOQueue_, ^{ 
    NSString* path = [self sessionFilePath]; 
    if (path) { 
        // ... 
}});

// An example where the parameter wraps and the block declaration fits
// on the same line. Note the spacing of |^(SessionWindow *window) {|// compared to |^{| above.
[[SessionService sharedService] 
    loadWindowWithCompletionBlock:^(SessionWindow *window) { 
        if (window) { 
            [self windowDidLoad:window]; 
        } else { 
            [self errorLoadingWindow]; 
        } }];

// An example where the parameter wraps and the block declaration does
// not fit on the same line as the name.
[[SessionService sharedService] 
    loadWindowWithCompletionBlock:
        ^(SessionWindow *window) { 
            if (window) { 
                [self windowDidLoad:window]; 
            } else { 
                [self errorLoadingWindow]; 
            } 
        }];

// Large blocks can be declared out-of-line.
void (^largeBlock)(void) = ^{ 
    // ...
};
[operationQueue_ addOperationWithBlock:largeBlock];

使用#pragma mark -对function/protocol/delegate进行分组

三目运算符

Preferred:

NSInteger value = 5;
result = (value != 0) ? x : y;
BOOL isHorizontal = YES;
result = isHorizontal ? x : y;

Not Preferred:

result = a > b ? x = c > d ? c : d : y;

三元操作符

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

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

应该:

NSInteger value = 5;  
result = (value != 0) ? x : y;  
BOOL isHorizontal = YES;  
result = isHorizontal ? x : y;  

不应该:

result = a > b ? x = c > d ? c : d : y;  

命名规范

#pragma mark - Lifecycle- (instancetype)init {}- dealloc {}- viewDidLoad {}- viewWillAppear:animated {}- didReceiveMemoryWarning {}#pragma mark - Custom Accessors- setCustomProperty:value {}- customProperty {}#pragma mark - IBActions- submitData:sender {}#pragma mark - Public- publicMethod {}#pragma mark - Private- privateMethod {}#pragma mark - Protocol#pragma mark - UITextFieldDelegate#pragma mark - UITableViewDataSource#pragma mark - UITableViewDelegate#pragma mark - NSCopying- copyWithZone:zone {}#pragma mark - NSObject- (NSString *)description {}

初始化方法

  • 返回类型是instancetype不是返回id。确保编译器正确判断推断结果。
- (instancetype)init {
  self = [super init];
  if (self) {
    // ...
  }
  return self;
}

Init方法

Init方法应该遵循Apple生成代码模板的命名规则,返回类型应该使用instancetype而不是id

- (instancetype)init {  
  self = [super init];  
  if (self) {  
    // ...  
  }  
  return self;  
}  

基本原则

条件表达式的括号 (如: if/else/switch/while), 左括号和表达式在同一行, 右括号另起一行

类构造方法

要求同上

@interface Airplane
  (instancetype)airplaneWithType:(RWTAirplaneType)type;
@end

类构造方法

当类构造方法被使用时,它应该返回类型是instancetype而不是id。这样确保编译器正确地推断结果类型。

@interface Airplane  
  (instancetype)airplaneWithType:(ANAirplaneType)type;  
@end  

关于更多instancetype信息,请查看NSHipster.com。

清晰

既清晰又简洁的命名最好,但以清晰为主,不要用单词简写(非常常用除外,苹果列出的可以接受的简写:链接),尽量使用全称。命名应符合OC标准,让人一看就知道是什么意思,不要让人有疑问,使用英文而不是拼音。

Preferred:

CGRect 方法

  • 访问x,y,width或者height时使用CGGeometry来访问。

Preferred:

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);

Not Preferred:

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 };

CGRect函数

当访问CGRect里的x,y,width,或height时,应该使用CGGeometry函数而不是直接通过结构体来访问。引用Apple的CGGeometry:

在这个参考文档中所有的函数,接受CGRect结构体作为输入,在计算它们结果时隐式地标准化这些rectangles。因此,你的应用程序应该避免直接访问和修改保存在CGRect数据结构中的数据。相反,使用这些函数来操纵rectangles和获取它们的特性。

应该:

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 };  

一致性

做某件事的代码通常都叫这个名字,比如tag、setStringValue,那你也这么叫。

if (user.isHappy) { // Do something} else { // Do something else}

Golden Path

Preferred:

- (void)someMethod {
  if (![someOther boolValue]) {
    return;
  }

  //Do something important
}

Not Preferred:

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

黄金路径

当使用条件语句编码时,左手边的代码应该是"golden" 或 "happy"路径。也就是不要嵌套if语句,多个返回语句也是OK。

应该:

- (void)someMethod {  
  if (![someOther boolValue]) {  
    return;  
  }  
  //  Do something important  
}  

不应该:

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

驼峰原则

大驼峰(UserNameLabel):每个单词首字母大写
小驼峰(userNameLabel):除第一个单词外,其它单词首字母大写

Not Preferred:

错误处理

判断返回值而不是错误变量。
Preferred:

NSError *error;
if (![self trySomethingWithError:&error]) {
  // Handle Error
}

Not Preferred:

NSError *error;
[self trySomethingWithError:&error];
if (error) {
  // Handle Error
}

错误处理

当方法通过引用来返回一个错误参数,判断返回值而不是错误变量。

应该:

NSError *error;  
if (![self trySomethingWithError:&error]) {  
  // Handle Error  
}  

不应该:

NSError *error;  
[self trySomethingWithError:&error];  
if (error) {  
  // Handle Error  
}  

在成功的情况下,有些Apple的APIs记录垃圾值(garbage values)到错误参数(如果non-NULL),那么判断错误值会导致false``负值crash

文件命名

Tip
文件名须反映出其实现了什么类 – 包括大小写。

文件的扩展名应该如下:

后缀 说明
.h C/C /Objective-C 的头文件
.m Ojbective-C 实现文件
.mm Ojbective-C 的实现文件
.mm Ojbective-C 的实现文件
.cc 纯 C 的实现文件
.c 纯 C 的实现文件
  • 所有自定义的文件名称以项目工程开头命名,eg:“XP”、“ZJG”、“SZ”
  • 针对不同视图控制器,在末尾添加后缀,eg:
    • UIViewController 后缀添加“ViewController”
    • UIView 后缀添加“View”
if (user.isHappy){ // Do something}else { // Do something else}

单例模式

  • 使用线程安全方式创建实例。
  (instancetype)sharedInstance {
  static id sharedInstance = nil;

  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedInstance = [[self alloc] init];
  });

  return sharedInstance;
}

单例模式

单例对象应该使用线程安全模式来创建共享实例。

  (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

类命名

类名(以及类别、协议名)应首字母大写,并以驼峰格式分割单词

  • 所有类名称以项目工程开头命名,eg:“XP”、“ZJG”、“SZ”
  • 针对不同视图控制器,在末尾添加后缀,eg:
    • UIViewController 后缀添加“ViewController”
    • UIView 后缀添加“View”
    • UIButton 后缀添加“Button”、“Btn”
    • UILabel 后缀添加“Label"

避免使用冒号对齐方式, 除非代码过长需要换行, 但是不要让block也同样保持冒号对齐

换行符

一行代码太长的话可以使用换行符,换成两行。

self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];

down

self.productsRequest = [[SKProductsRequest alloc]     
    initWithProductIdentifiers:productIdentifiers];
  • 少用 -- 运算符
  • 规范只是建议不要拘泥
  • 对于文章的大部分内容还是认同的,但是对于方法名( /-)符号后添加空格,就个人而言感觉意义并不大。

参考文章

换行符

换行符是一个很重要的主题,因为它的风格指南主要为了打印和网上的可读性。

例如:

self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];  

一行很长的代码应该分成两行代码,下一行用两个空格隔开。

self.productsRequest = [[SKProductsRequest alloc]   
  initWithProductIdentifiers:productIdentifiers];  

类别名

Tip
类别名应该有两三个字母的前缀以表示类别是项目的一部分
或者该类别是通用的。类别名应该包含它所扩展的类的名字。

比如我们要基于 NSString 创建一个用于解析的类别,我们将把类别放在一个名为 GTMNSString Parsing.h 的文件中。类别本身命名为 GTMStringParsingAdditions (是的,我们知道类别名和文件名不一样,但是这个文件中可能存在多个不同的与解析有关类别)。类别中的方法应该以 gtm_myCategoryMethodOnAString: 为前缀以避免命名冲突,因为 Objective-C 只有一个名字空间。

类名与包含类别名的括号之间,应该以一个空格分隔。

Preferred:

Xcode工程

物理文件应该与Xcode工程文件保持同步来避免文件扩张。任何Xcode分组的创建应该在文件系统的文件体现。代码不仅是根据类型来分组,而且还可以根据功能来分组,这样代码更加清晰。

变量命名

Tip
变量名应该以小写字母开头,并使用驼峰格式。
// blocks are easily readable[UIView animateWithDuration:1.0f animations:^{ // something} completion:^(BOOL finished) { // something}];

普通变量名

对于静态的属性(int 或指针),尽量为变量起一个描述性的名字。不要担心浪费列宽,因为让新的代码阅读者立即理解你的代码更重要。例如:

  • 错误的命名:
int w;
int nerr;
int nCompConns;
tix = [[NSMutableArray alloc] init];
obj = [someObject object];
p = [network port];
  • 正确的命名:
int numErrors;
int numCompletedConnections;
tickets = [[NSMutableArray alloc] init];
userInfo = [someObject object];
port = [network port];

Not Preferred:

类成员变量

  • 如果只是单纯的private变量,最好声明在implementation里.
  • 如果是类的public属性,就用property写在.h文件里
  • 如果自己内部需要setter和getter来实现一些东西,就在.m文件的类目里用property来声明

类成员变量(写在implementation里面),以下划线开头,使用单词全名按顺序拼接方式,属性不需要,系统会自动生成下划线开头的成员变量。

// colon-aligning makes the block indentation hard to read[UIView animateWithDuration:1.0 animations:^{ // something } completion:^(BOOL finished) { // something }];

类成员是界面元素的命名

命名方式为变量含义 视图后缀,采用小驼峰方式书写。

@property (nonatomic, strong) UIViewController *loginViewController;
@property (nonatomic, strong) UIView *loginHeaderView;
@property (nonatomic, strong) UILabel *loginLabel;
@property (nonatomic, strong) UIButton *loginBtn;
  • 签代理

常量

常量名(如宏定义、枚举、静态局部变量等)应该以小写字母 k 开头,使用驼峰格式分隔单词,如:kInvalidHandle,kWritePerm。

Preferred:

方法命名

方法名应遵守小驼峰原则,首字母小写,其他单词首字母大写,每个空格分割的名称以动词开头。执行性的方法应该以动词开头,小写字母开头,返回性的方法应该以返回的内容开头,但之前不要加get。

- (void)insertModel:(id)model atIndex:(NSUInteger)atIndex;
- (instancetype)arrayWithArray:(NSArray *)array;
@interface UIViewController : UIResponder <NSCoding, UIAppearanceContainer>@property (nonatomic, assign) id<ChooseProvinceViewControllerdDelegate> delegate;

Delegate命名

类的实例必须为回调方法的参数之一, 如

-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section

回调方法的参数只有类自己的情况,方法名要符合实际含义, 如:

-(NSInteger)numberOfSectionsInTableView:(UITableView*)tableView

以类的名字开头(回调方法存在两个以上参数的情况)以表明此方法是属于哪个类的, 如:

-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

使用did和will通知Delegate已经发生的变化或将要发生的变化, 如:

-(NSIndexPath*)tableView:(UITableView*)tableView willSelectRowAtIndexPath:(NSIndexPath*)indexPath;
-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath;

Not Preferred:

图片资源文件名

  • 模块 功能命名法(公共使用:common 功能)。公共模块主要包括统一的背景,导航条,标签,公共的按钮背景,公共的默认图等等;私有模块主要根据app的业务功能模块划分,比如用户中心,消息中心等。
  • 单词全拼,或者大家公认无歧义的缩写(如:nav,bg,btn等)

个人中心模块中我的消息按钮示例:personal_btn_my_message
公共模块搜索按钮示例:common_icon_search(或者common_search_icon)

@interface UIViewController : UIResponder<NSCoding,UIAppearanceContainer>@property (nonatomic, assign) id <ChooseProvinceViewControllerdDelegate> delegate;

用枚举表示状态、选项、状态码

项目中,用枚举来表示一系列的状态、选项和状态码。例如 iOS SDK 中表示 UICollectionView 滑动方向的枚举定义如下:

typedef NS_ENUM(NSInteger, UICollectionViewScrollDirection) {
    UICollectionViewScrollDirectionVertical,
    UICollectionViewScrollDirectionHorizontal
};

定义的枚举类型名称应以 2~3 个大写字母开头,而这通常与项目设置的类文件前缀相同,跟随其后的命名应采用驼峰命名法则,命名应准确表述枚举表示的意义,枚举中各个值都应以定义的枚举类型开头,其后跟随各个枚举值对应的状态、选项或者状态码。

对于需要以按位或操作来组合的枚举都应使用 NS_OPTIONS 宏来定义,例如 SDK 中:

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

这样定义的选项能够以 按位或操作符 来进行组合,枚举中每个值均可启用或者禁用某一选项,在使用时,可以使用 按位与操作符 来检测是否启用了某一选项,如下:

UIViewAutoresizing resizing = UIViewAutoresizingFlexibleWidth | 
UIViewAutoresizingFlexibleHeight;

if (resizing & UIViewAutoresizingFlexibleWidth) {
    // UIViewAutoresizingFlexibleWidth is set
}

另外,我们可能使用 switch 语句时,会在最后加上 default 分支,但是若用枚举定义状态机,则最好不要使用 default 分支,因为如果稍后再加了一种状态,那么编译器就会发出警告,提示新加入的状态并未在 switch 分支中处理。假如写上了 default 分支,那么它就会处理这个新状态,从而导致编译器不发出警告,用 NS_ENUM 定义其他枚举类型时也要注意此问题。例如在定义代表 UI 元素样式的枚举时,通常要确保 switch 语句能正确处理所有样式。

  • 确实需要, 才加注释

  • 注释是用来解释why, 而不是what

编程实践

Preferred:

Init方法

Init方法应该遵循Apple生成代码模板的命名规则,返回类型应该使用instancetype而不是id。

- (instancetype)init {  
  self = [super init];  
  if (self) {  
      // ...  
  }  
  return self;  
}  
// alignment attributes must have a secondViewAttribute// therefore we assume that is refering to superview// eg make.left.equalToif (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) { secondLayoutItem = self.firstViewAttribute.view.superview; secondLayoutAttribute = firstLayoutAttribute;}

类构造方法

当类构造方法被使用时,它应该返回类型是instancetype而不是id。这样确保编译器正确地推断结果类型。

@interface Airplane  
  (instancetype)airplaneWithType:(RWTAirplaneType)type;  
@end  

Not Preferred:

CGRect函数

当访问CGRect里的x, y, width, 或 height时,应该使用CGGeometry函数而不是直接通过结构体来访问。
引用Apple的CGGeometry:

在这个参考文档中所有的函数,接受CGRect结构体作为输入,在计算它们结果时隐式地标准化这些
rectangles。因此,你的应用程序应该避免直接访问和修改保存在CGRect数据结构中的数据。相反,
使用这些函数来操纵rectangles和获取它们的特性。

应该:

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 }; 
/** * show head image */@property (nonatomic, strong) DDImageView *headView;// im conversation done edit- imConversationDoneEdit;

单例模式

单例对象应该使用线程安全模式来创建共享实例。

  (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。

  • 添加的注释要保持更新或删除

Xcode工程

物理文件应该与Xcode工程文件保持同步来避免文件扩张。任何Xcode分组的创建应该在本地文件系统体现。代码不仅是根据类型来分组,而且还可以根据功能来分组,这样代码更加清晰。

日志规范

只在debug的时候输出日志, release的时候不输出。减少性能损耗和安全隐患。
在-Prefix.pch(pch全称是“precompiled header”,也就是预编译头文件,该文件里存放的工程中一些不常被修改的代码,比如常用的框架头文件)文件中添加

#ifdef DEBUG
#define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
#define DLog(...)
#endif

其中DLog在开发中根据项目进行命名,在实际使用日志的时候,都使用DLog,不使用NSLog。并且该自定义日志可以输出具体的类和行数。实际开发中,只需要用 DLog(...) 就可以在输出需要信息的同时, 还输出所在类、 函数(方法)名以及行数。可以用这种方法很快找到输出所在的位置。

参考:http://zh-google-styleguide.readthedocs.io/en/latest/google-objc-styleguide/contents/
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html#//apple_ref/doc/uid/10000146-SW1
http://www.csdn.net/article/2015-06-01/2824818-objective-c-style-guide/1

  • 使用长且描述性强的命名方式

Preferred:

UIButton *settingsButton;

Not Preferred:

UIButton *setBut;
  • 在类名和常量名前加上3个字母的前缀(Core Data entity names去掉前缀)

需要统一整个工程的命名前缀, 因为OC里没有namespace, 工程越庞大后果越严重

  • 常量的命名采用"驼峰"的命名方式

Preferred:

static NSTimeInterval const RWTTutorialViewControllerNavigationFadeAnimationDuration = 0.3f;

Not Preferred:

static NSTimeInterval const fadetime = 1.7f;

成员的命名采用首字母小写的"驼峰"命名方式, 并且使用auto-synthesis方式声明成员, 而不是@synthesize方式

Preferred:

@property (nonatomic, copy) NSString *descriptiveVariableName;

Not Preferred:

id varnm;

命名时注意存储数据的数据结构发生改变的时候,会不会引起命名的改变。

Preferred:

@property (nonatomic, strong) NSArray *actionSheetItems;@property(nullable, nonatomic, copy) NSArray<UIBarButtonItem *> *items; 

Not Preferred:

@property (nonatomic, strong) NSArray *actionSheetItemArray;

图标资源命名

模块名 功能名 [状态],( [ ] : 表明可选)

图标在Xcode里面的名称需要与图标的物理命名保持一致

Preferred:

addressbook_isvnetwork // 指代没有状态的图标名addressbook_call_normal addressbook_call_highlight // 指代有多种状态的图标名conference_member_delete_normalconference_member_delete_highlight // 指代功能名较长且有多种状态的图标名

下划线

使用self.方式来访问对象的成员, 从视觉上就可以区分出哪些是本对象成员

注意:

  • 在初始化方法(init, initWithCoder:等), dealloc以及setters/getters方法中中必须使用保留的_variableName

  • 局部变量不要用下划线开头! 因为下划线开头的命名方式是Apple保留的命名方式

  • 方法类型的后面有个空格

  • 每一个参数都要有描述

注意:

  • 不要在多个参数的描述中添加"and", 例如下面的initWithWidth:height:方法

Preferred:

- setExampleText:(NSString *)text image:(UIImage *)image;- sendAction:aSelector to:anObject forAllCells:flag;- viewWithTag:(NSInteger)tag;- (instancetype)initWithWidth:width height:height;

Not Preferred:

-setT:(NSString *)text i:(UIImage *)image;- sendAction:aSelector :anObject :flag;- taggedView:(NSInteger)tag;- (instancetype)initWithWidth:width andHeight:height;- (instancetype)initWith:width and:height; // Never do this.

变量命名要可读性强, 避免用单个单词的命名方式, 除了在for()循环里

指针的星号和变量名连在一起, 例如: NSString *text, 而不是NSString* text, NSString * text

避免直接使用_viriableName方式访问对象的成员, 除了在初始化方法(init, initWithCoder:等), dealloc方法以及setters/getters方法中点语法

了解更多关于在初始化和dealloc中使用accessor方法, 请参考这里.

Preferred:

@interface RWTTutorial : NSObject@property (nonatomic, copy) NSString *tutorialName;@property (nonatomic, copy) NSArray<UIBarButtonItem *> *items;@end 

Not Preferred:

@interface RWTTutorial : NSObject { NSString *tutorialName;}

如果变量是一个度量的话(如按时间长度或者字节数),那么最好把名字带上它的单位

Preferred:

NSString *durationOfSeconds;

Not Preferred:

NSString *duration;

属性要按照先atomicity后storage的顺序, 这是为了和Apple的Interface Builder生成的代码保持一致.不用对齐,根据功能用空格实现分组

Preferred:

@property (nonatomic, weak) IBOutlet UIView *containerView;@property (nullable, readonly, nonatomic, copy) NSString *tutorialName;

Not Preferred:

@property (weak, nonatomic) IBOutlet UIView *containerView;@property (nonatomic) NSString *tutorialName;

注意:

  • 控件的storage属性通常设置为weak, 而非strong

如果成员引用的对象是可变对象, 那么需要使用copy而非strong

Why?

因为即使成员的类型是NSString, 但实际传入的如果是NSMutableString的对象

该对象在你不知情的情况下, 仍然会被修改

Preferred:

@property (nonatomic, copy) NSString *tutorialName;

Not Preferred:

@property (nonatomic, strong) NSString *tutorialName;

点语法实际是对accessor方法的封装

了解更多点语法, 请参考这里

访问和操作对象成员, 推荐使用点语法; Bracket notation is preferred in all other instances.

Preferred:

NSInteger arrayCount = [self.array count];view.backgroundColor = [UIColor orangeColor];[UIApplication sharedApplication].delegate;

Not Preferred:

NSInteger arrayCount = self.array.count;[view setBackgroundColor:[UIColor orangeColor]];UIApplication.sharedApplication.delegate;

使用Object Literals(@)方式来快速创建NSString, NSDictionary, NSArray, NSNumber实例

注意:

  • 如果传给NSArray, NSDictionary的值是nil会导致crash

Preferred:

NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};NSNumber *shouldUseLiterals = @YES;NSNumber *buildingStreetNumber = @10018;

Not Preferred:

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];
  • 容器类型最好标明存储数据的类型

Preferred:

@property (nonatomic, readonly, nullable) NSArray<NSIndexPath *> *indexPathsForVisibleRows;- (nullable NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView ;
  • 在数组和字典中, 使用索引和关键字来获取数据

Preferred:

NSString *name = names[1];NSString *product = [productManagers valueForKey:@"kate"]

Not Preferred:

NSString *name = [names objectAtIndex:1];NSString *product = [productManagers objectForKey:@"@kate"]

使用static而非#define来声明常量; unless explicitly being used as a macro

用const修饰时,const右边的总是不能被修改.声明时不用对齐,根据功能用空格实现分组

Preferred:

static NSString * const RWTAboutViewControllerCompanyName = @"RayWenderlich.com";static CGFloat const RWTImageThumbnailHeight = 50.0f;

Not Preferred:

#define CompanyName @"RayWenderlich.com"#define thumbnailHeight 2.0f
  • 宏定义

使用大写字母,用_分割单词,宏定义中如果包含表达式或变量,表达式和变量必须用小括号括起来

不到万不得已不推荐使用宏定义像数字常量,通知的参数一般不推荐使用宏定义,推荐使用static const 的形式

Preferred:

#define SCREEN_RECT ([UIScreen mainScreen].bounds)#define SCREEN_WIDTH ([UIScreen mainScreen].bounds.size.width)
  • 浮点数

使用浮点数在数值的后面加上f,用来区别double类型

Preferred:

static NSTimeInterval const RWTTutorialViewControllerNavigationFadeAnimationDuration = 0.3f;

Not Preferred:

static NSTimeInterval const RWTTutorialViewControllerNavigationFadeAnimationDuration = 0.3;

使用NS_ENUM()声明枚举, 因为它具有更强的类型检查机制

For Example:

typedef NS_ENUM(NSInteger, RWTLeftMenuTopItemType) { RWTLeftMenuTopItemMain, RWTLeftMenuTopItemShows, RWTLeftMenuTopItemSchedule};

当然你还是可以显式地设置枚举的值

typedef NS_ENUM(NSInteger, RWTGlobalConstants) { RWTPinSizeMin = 1, RWTPinSizeMax = 5, RWTPinCountMin = 100, RWTPinCountMax = 500,};

不要使用老式的枚举定义方式, 除非编写CoreFoundation C的代码

Not Preferred:

enum GlobalConstants { kMaxPinSize = 5, kMaxPinCount = 500,};

Case表达式通常不需要加括号

Case内容有if判断句, for循环和局部变量时, 需要加上括号 (左括号的右边, break放在括号外). break与下一个case之间有空行.

switch (condition) { case 1: { for () { // ... } } break; case 2: { if { // } } break; case 3: // ... break; default: // ... break; }

多个Case表达式执行相同的逻辑, 使用fall-through方式(即删除表达式里的break)

此时, 需要加上注释说明注释

switch (condition) { case 1: // ** fall-through! ** case 2: // code executed for values 1 and 2 break; default: // ... break;}

私有属性要在类的实现中声明: 放在class extension(即匿名category)中

For Example:

@interface RWTDetailViewController ()@property (nonatomic, strong) GADBannerView *googleAdView;@property (nonatomic, strong) ADBannerView *iAdView;@property (nonatomic, strong) UIWebView *adXWebView;@end

Objective-C使用YESNO, 而truefalse只用在CoreFoundation, C或C 代码里

Since nil resolves to NO it is unnecessary to compare it in conditions. Never compare something directly to YES, because YES is defined to 1 and a BOOL can be up to 8 bits.

This allows for more consistency across files and greater visual clarity.

Preferred:

if (someObject) {}if (![anotherObject boolValue]) {}

Not Preferred:

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

如果BOOL类型的属性是一个形容词, 那么可以去掉"is"前缀, 但get accessor中仍然需要保留前缀

@property (assign, getter=editable) BOOL editable;

例子来自Cocoa Naming Guidelines.

条件语句要加上括号, 即使是一行语句, 否则会存在隐患: even more dangerous defect

Preferred:

if  { return success;}

Not Preferred:

if  return success;

or

if  return success;

三元运算符

如果可以使代码变得简洁和高效, 那么可以考虑使用三元运算符?:, 否则还是用if表达式

通常, 给变量赋值时, 可以考虑使用三元运算符?:

Preferred:

NSInteger value = 5;result = (value != 0) ? x : y;BOOL isHorizontal = YES;result = isHorizontal ? x : y;

Not Preferred:

result = a > b ? x = c > d ? c : d : y;

初始化方法要和Apple模板保持一致

- (instancetype)init { self = [super init]; if  { // Do something } return self;}

返回值类型是'instancetype'而不是'id'

关于instancetype, 请参考类构造方法

类构造方法返回值类型是'instancetype'而不是'id', 这样可以帮忙编译器推导出返回值得类型

@interface Airplane  (instancetype)airplaneWithType:(RWTAirplaneType)type;@end

了解更多关于instancetype: NSHipster.com.

要获取CGRectx, y, width, height, 不要直接访问结构体的成员, 而应该使用CGGeometry functions

Apple官方对于CGGeometry的解释:

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.

Preferred:

CGRect frame = self.view.frame;CGFloat x = CGRectGetMinX;CGFloat y = CGRectGetMinY;CGFloat width = CGRectGetWidth;CGFloat height = CGRectGetHeight;CGRect frame = CGRectMake(0.0f, 0.0f, width, height);

Not Preferred:

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 = { .origin = CGPointZero, .size = frame.size };

如果把需要执行的逻辑比作"golden"或"happy" path

那么在判断条件不满足时, 直接return退出方法, 而不是判断条件满足时, 执行该Golden Path

Preferred:

- someMethod { if (![someOther boolValue]) { return; } // Do something important}

Not Preferred:

- someMethod { if ([someOther boolValue]) { // Do something important }}

通过方法的返回值而不是返回的error引用, 来做错误处理的判断条件

Preferred:

NSError *error;if (![self trySomethingWithError:&error]) { // Handle Error}

Not Preferred:

NSError *error;[self trySomethingWithError:&error];if  { // Handle Error}

单例对象的实例化必须要确保线程安全

  (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.

头文件中的方法, 使用冒号对齐的方式

Preferred:

 - setViewWithHeadImageUrl:(NSString *)headImageUrl name:(NSString *)name phoneNumber:(NSString *)phoneNumber;

Not Preferred:

- setViewWithHeadImageUrl:(NSString *)headImageUrl name:(NSString *)name phoneNumber:(NSString *)phoneNumber;

实现文件或方法调用时, 不用冒号对齐

Preferred:

[[MSDBHelper sharedInstance] updateCallRecordsWithNumber:number isPersonalContact:YES oldName:oldContact.displayName toNewName:@""];

Not Preferred:

[[MSDBHelper sharedInstance] updateCallRecordsWithNumber:number isPersonalContact:YES oldName:oldContact.displayName toNewName:@""];

如果语句过长, 需要考虑代码的分解和优化

Preferred:

NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];[ctpManager connectToHost:kHostNameCloudPhoneNoport onPort:kCTPPort userInfo:userInfo;[userInfo setObject:[AccountLoginModel sharedInstance].userInfo.mobilephone forKey:CTP_AUTHUSER_USER_NAME, nil]; 

Not Preferred:

 [ctpManager connectToHost:kHostNameCloudPhoneNoport onPort:kCTPPort userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[AccountLoginModel sharedInstance].userInfo.mobilephone, CTP_AUTHUSER_USER_NAME, nil];
  • 自定义头文件放在系统头文件的前面

  • 按功能分开,加空格以示区分

  • 按字母表排序

Preferred:

#import "DailModel"#import "DialView.h"#import "DialViewController.h"#import <QYCTPManager/CTPManager.h>

Not Preferred:

#import <QYCTPManager/CTPManager.h>#import "DialView.h"#import "DailModel"#import "DialViewController.h"
  • 不要引入无关的头文件

  • 尽量使用向前声明取代引入,这样不仅可以缩减编译时间,而且还能降低彼此依赖程度

Preferred:

// MSDetailRecordsViewController.m// CloudPhone//// Created by chenguang (guochenguang@qiyoukeji.com) on 15-12-6.// Copyright  2015年 QIYOU Ltd. All rights reserved.//

Not Preferred:

// MSDetailRecordsViewController.m// chenguang//// Created by chenguang on 15-12-6.// Copyright  2015年 CloudPhone. All rights reserved.//
  • Xcode Group要和文件系统里的文件夹关联起来

  • 代码不仅要按照类型分组, 也要按照功能和特性进行分组

  • 如果可以的话, 打开Build Settings里的"Treat Warnings as Errors"选项

  • 并且尽可能多的打开additional warnings

如果需要忽略特定的warning, 请参考Clang's pragma feature.

  • Robots & Pencils
  • New York Times
  • Google
  • GitHub
  • Adium
  • Sam Soffes
  • CocoaDevCentral
  • Luke Redpath
  • Marcus Zarra

更多文章, 请支持我的个人博客

本文由新葡京8455发布,转载请注明来源

关键词: