Skip to content

Instantly share code, notes, and snippets.

@VincentSit
Last active July 21, 2016 21:41
Show Gist options
  • Save VincentSit/3460151de556ff0b5ef5 to your computer and use it in GitHub Desktop.
Save VincentSit/3460151de556ff0b5ef5 to your computer and use it in GitHub Desktop.
Objective-C 编码指南

移动开发团队 Objective-C 编码指南

目录

组织

使用 #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 {}

间距

  • 一个缩进使用 2 个空格,请确保在 Xcode 中设置此偏好(Xcode->Preferences...->Text Editing->Indentation:Tab width:2, Indent width:2.);
  • 方法的大括号和其他的大括号(if / else / switch / while)始终和声明在同一行开始,在新的一行结束。并且在行末的大括号前面总是应该有一个空格。

推荐:

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

反对:

if (user.isHappy)
{
    // Do something
}
else {
    // Do something else
}
if (user.isHappy){
// Do something
}
else{
// Do something else
}
  • 方法之间应该正好空一行,这有助于视觉清晰度和代码组织性。在方法中的功能块之间应该使用空白分开,但往往可能应该创建一个新的方法
  • @synthesize@dynamic 在实现中每个都应该占一个新行
  • 通常应避免在调用方法时使用冒号对齐。有些地方的方法可能有 >= 3个冒号,此时冒号对齐使得代码更易读。然而在包含 block 的方法中请不要换使用冒号对齐,因为 Xcode 的缩进使得它难以辨认

推荐:

// blocks 会更易读
[UIView animateWithDuration:1.0 animations:^{
  // something
} completion:^(BOOL finished) {
  // something
}];

反对:

// 冒号对齐使得 block 缩进难以阅读
[UIView animateWithDuration:1.0
                 animations:^{
                     // something
                 }
                 completion:^(BOOL finished) {
                     // something
                 }];
  • 多个参数间应使用空格隔开

推荐:

NSLocalizedStringFromTable(key, table, comment);

反对:

NSLocalizedStringFromTable(key,table,comment);

引用文件

只要有可能就总是使用 @Class 来引用头文件,而不是 #import,因为它可以轻微地提升编译时的性能。
引用文件时应该保持当前类的头文件位于顶部。
如果有一个以上的 import 语句,就对这些语句进行分组。每个分组的注释是可选的。
注:对于模块使用 @import 语法。

推荐:

// 当前类
#import "EOTAWSManager.h"

// Frameworks
@import QuartzCore;

// Models
#import "EOTUser.h"

// Views
#import "EOTButton.h"
#import "EOTUserView.h"

反对:

@import QuartzCore;
#import "EOTButton.h"
#import "EOTAWSManager.h"
#import "EOTUser.h"
#import "EOTUserView.h"

条件判断

条件判断主体部分应该始终使用大括号括住来防止出错,即使它可以不用大括号(例如它只需要一行)。这些错误包括添加第二行(代码)并希望它是 if 语句的一部分时。还有另外一种更危险的,当 if 语句里面的一行被注释掉,下一行就会在不经意间成为了这个 if 语句的一部分。此外,这种风格也更符合所有其他的条件判断,因此也更容易检查。

推荐:

if (!error) {
    return success;
}

反对:

if (!error)
    return success;

if (!error) return success;

三目运算符

三目运算符,? ,只有当它可以增加代码清晰度或整洁时才使用。单一的条件都应该优先考虑使用。多条件时通常使用 if 语句会更易懂,或者重构为实例变量。

推荐:

result = a > b ? x : y;

反对:

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

预编译头文件

项目中大多数文件中都需要引用的头文件应该优先添加到预编译头文件中。如果一个头文件已经在预编译头文件中,那么就不应该再出现在其他项目文件中。
例如,一个常见的预编译头文件像下面这样:

#ifdef __OBJC__
    #import <Foundation/Foundation.h>
    #import <UIKit/UIKit.h>
#endif

那么 #import <Foundation/Foundation.h>永远都不应该出现在除预编译头文件以外的其他项目文件中。

错误处理

  • 不要在流程控制中使用异常
  • 仅在严重编程错误时使用异常
  • 要指示错误,请使用 NSError** 参数
  • 当引用一个返回错误参数的方法时,应该针对返回值,而非错误变量 原因:一些苹果的 API 在成功的情况下会写一些垃圾值给错误参数(如果非空),所以针对错误可以造成虚假结果(以及接下来的崩溃)。

推荐:

NSError *error;
if (![self trySomethingWithError:&error]) {
    // 处理错误
}

// 声明
- (BOOL)trySomethingWithError:(NSError **)error;

反对:

NSError *error;
[self trySomethingWithError:&error];
if (error) {
    // 处理错误
}

运算符

++ / -- 运算符外,其他运算符前后应该各有一个空格。

NSString *foo = @"bar";
NSInteger answer = 42;
answer += 9;
answer++;
answer = 40 + 2;

数据类型

尽量使用 NSIntegerNSUInteger 来代替 intlong,以及苹果的每个 64 位安全数据类型。同样的原因,CGFloat 应该代替 float ,这样做在 64 位平台会更好。
所有苹果的数据类型都应该用来代替原始的数据类型。例如,例如你正在使用(或计算)时间间隔,应该使用 NSTimeInterval 代替 double ,即使它是同义的。这样做代码会更清晰,并且在未来可能会做更少的改动。

点语法

应该 始终 使用点语法来访问或者修改属性,访问其他实例时首选括号。

推荐:

view.backgroundColor = [UIColor orangeColor];
[UIApplication sharedApplication].delegate;

反对:

[view setBackgroundColor:[UIColor orangeColor]];
UIApplication.sharedApplication.delegate;

方法

在方法签名中,在 - / + 符号后应该有一个空格。方法片段之间也应该有一个空格。
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;  // 永远别这么做。

变量

变量名应该尽可能命名为描述性的。除了 for() 循环外,其他情况都应该避免使用单字母的变量名。 星号表示指针属于变量,例如:NSString *text 不要写成 NSString* text 或者 NSString * text ,常量除外。

尽量定义属性来代替直接使用实例变量。。虽然使用属性会带来额外的开销,但这更有助于内存管理,编写出的代码更容易审查和维护。并且属性是高度优化的,所导致的开销是完全值得的。

除了初始化方法(initinitWithCoder:,等), dealloc 方法和自定义的 setters 和 getters 内部,应避免直接访问实例变量。更多有关在初始化方法和 dealloc 方法中使用访问器方法的信息,参见这里

推荐:

@interface EOTSection: NSObject

@property (nonatomic, copy) NSString *headline;

@end

反对:

@interface EOTSection : NSObject {
    NSString *headline;
}

变量限定符

当涉及到在 ARC 中被引入变量限定符时, 限定符 (__strong, __weak, __unsafe_unretained, __autoreleasing) 应该位于星号和变量名之间,如:NSString * __weak text

命名

工程的命名前缀使用 EOT。 只要有可能,尽量遵守苹果命名约定,尤其那些涉及到内存管理规则NARC)的。

长点的方法名和变量名都不错。

推荐:

UIButton *settingsButton;

反对:

UIButton *setBtn;

类名和常量应该一直使用三个字母的前缀(例如 EOT),但 Core Data 实体名称可以省略(映射的 Model 类名不要省略))。为了代码清晰,常量应该使用相关类的名字作为前缀并使用驼峰命名法。

推荐:

static const NSTimeInterval EOTArticleViewControllerNavigationFadeAnimationDuration = 0.3;

反对:

static const NSTimeInterval fadetime = 1.7;

属性应该使用驼峰命名法并且开头字母为小写。 如果 Xcode 可以自动合成变量,那就让它自动合成。(通常我们现在所用的 Xcode 版本都可以自动合成变量) 否则,为了保持一致,这些属性相对应的实例变量应该使用驼峰命名法命名,并且首字母小写,以下划线开头。这和 Xcode 默认的样式保持一致。

推荐:

@synthesize descriptiveVariableName = _descriptiveVariableName;

反对:

id varnm;

下划线

当使用属性的时候,应当始终使用 self. 来访问实例变量。这样所有的属性看起来就很清晰,因为它们都是以 self. 开头。

例外:内部初始化、setters / getters 方法中对应的实例变量(例如:_variableName)都应该直接被使用,以避免任何潜在的问题。

局部变量不应该包含下划线。

注释

当需要的时候,注释应该用来解释 为什么 某段代码做了那些事情。任何注释必须保持最新否则就删除掉。

通常应该避免一大块注释,代码就应该尽量作为自身的文档,只需要隔几行写几句说明。例外:这并不适用于用来生成文档的注释。

注释的风格应使用 Javadoc 风格。可以安装 VVDocumenter 来提升注释效率。

Javadoc 注释风格像下面这样:

/**   
 *  多行注释....
 */
/// 单行注释

举例:
对于方法或带参 Block :

/**
 *  快速构建分享菜单。
 *
 *  @param status       用户的 timeline 信息
 *  @param successBlock 分享成功回调
 *  @param errorBlock   分享失败回调
 *
 *  @since 1.2.1
 */
+ (void)showWithStatus:(EOTStatus *)status success:(EOTShareSuccessBlock)successBlock failed:(EOTShareFailedBlock)errorBlock;
/**
 *  获取群组标签个数
 *
 *  @param group 当前用户群组,如果传 nil,则返回全部用户群组的标签个数。
 *
 *  @return 标签个数
 *
 *  @since 1.2.0
 */
+ (NSInteger)numberOfTagsWithGroup:(EOTGroup *)group;
/**
 *  获取用户信息并处理
 *
 *  @param  result      回复标识,YES:获取成功,NO:获取失败
 *  @param  userInfo    用户信息
 *
 *  @since 1.2.1
 */
typedef void(^EOTGetUserInfoSuccessBlock)(BOOL result, NSDictionary *userInfo);

对于属性或变量:

/// 用户名
@property (nonatomic, copy) NSString *name;

对于条件判断语句:

// 解释说明此条件
if (something) {
    // do something
}

// 解释说明不满足条件时为何这么做
else {
    // do something else
}
// 综述
switch (something.state) {
    // 解释此条件
    case 0: {
        // Something
        break;
    }

    case 1: {
        // Something
        break;
    }
    // 解释为何可以省略 break
    case 2:
    case 3: {
        // Something
        break;
    }

    default: {
        // Something
        break;
    }
}

如果在工程中任意调用此方法的地方按下 option 键并单击此方法,Xcode 5 会自动读取你的注释并在 Quick Help 面板中显示给调用者。

init 和 dealloc

dealloc 方法应该放在实现文件的最上面,并且刚好在 @synthesize@dynamic 语句的后面。在任何类中,init 都应该直接放在 dealloc 方法的下面。

init 方法的结构应该像这样:

- (instancetype)init {
    self = [super init]; // 或者调用指定的初始化方法
    if (self) {
        // Custom initialization
    }

    return self;
}

字面量

每当创建 NSStringNSDictionaryNSArray,和 NSNumber 类的不可变实例时,都应该使用字面量。要注意 nil 值不能传给 NSArrayNSDictionary 字面量,这样做会导致崩溃。

推荐:

NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal", @"Mobile Web" : @"Bill"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @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 *buildingZIPCode = [NSNumber numberWithInteger:10018];

CGRect 函数

当访问一个 CGRectxywidthheight 时,应该使用CGGeometry 函数代替直接访问结构体成员。苹果的 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.

推荐:

CGRect frame = self.view.frame;

CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);

反对:

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;

常量

常量首选内联字符串字面量或数字,因为常量可以轻易重用并且可以快速改变而不需要查找和替换。常量应该声明为 static 常量而不是 #define ,除非非常明确地要当做宏来使用。只在类内部使用的以 k 为前缀,需要在类外部使用的以类名为前缀。

推荐:

static NSString * const kCompanyName = @"The East Open Technology Company";

static const CGFloat kImageThumbnailHeight = 50.0;

// .h
extern CGFloat const EOTBodySelfieTopicsViewInitialHeight;

// .m
CGFloat const EOTBodySelfieTopicsViewInitialHeight = 91.f;

反对:

#define CompanyName @"The East Open Technology Company"

#define thumbnailHeight 2

枚举类型

当使用 enum 时,建议使用新的基础类型规范,因为它具有更强的类型检查和代码补全功能。现在 SDK 包含了一个宏来鼓励使用使用新的基础类型 - NS_ENUM()

推荐:

typedef NS_ENUM(NSInteger, EOTAdRequestState) {
    EOTAdRequestStateInactive,
    EOTAdRequestStateLoading
};

位掩码

当用到位掩码时,使用 NS_OPTIONS 宏。

举例:

typedef NS_OPTIONS(NSUInteger, EOTAdCategory) {
EOTAdCategoryAutos      = 1 << 0,
EOTAdCategoryJobs       = 1 << 1,
EOTAdCategoryRealState  = 1 << 2,
EOTAdCategoryTechnology = 1 << 3
};

私有属性

私有属性应该声明在类实现文件的延展(匿名的类目)中。有名字的类目(例如 EOTPrivateprivate)永远都不应该使用,除非要扩展其他类。

推荐:

@interface EOTAdvertisement ()

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

@end

图片命名

图片名称应该被统一命名以保持组织的完整。它们应该被命名为一个说明它们用途的驼峰式字符串,其次是自定义类或属性的无前缀名字(如果有的话),然后进一步说明颜色 和/或 展示位置,最后是它们的状态。

推荐:

  • RefreshBarButtonItem / RefreshBarButtonItem@2xRefreshBarButtonItemSelected / RefreshBarButtonItemSelected@2x
  • ArticleNavigationBarWhite / ArticleNavigationBarWhite@2xArticleNavigationBarBlackSelected / ArticleNavigationBarBlackSelected@2x.

图片目录中被用于类似目的的图片应归入各自的组中。

使用

在项目中使用图片时,图片格式为 png 时应该省略此后缀名,而其他图片格式则应包括后缀名。

推荐:

// cat 为 png 格式(cat.png),mouse 为 jpg 格式(mouse.jpg)。
UIImage *cat = [UIImage imageNamed:@"cat"];
UIImage *mouse = [UIImage imageNamed:@"mouse.jpg"];

反对:

// cat 为 png 格式(cat.png),mouse 为 jpg 格式(mouse.jpg)。
UIImage *cat = [UIImage imageNamed:@"cat.png"];
UIImage *mouse = [UIImage imageNamed:@"mouse.jpg"];
UIImage *mouse = [UIImage imageNamed:@"mouse"];

管理

图片应使用 Asset catalogs 存储和管理。根据使用模块进行分组。如有必要,也可根据功能再次进行细分。
举例:

BodyPoke/   
    Timeline/   
        Hottest/
         RefreshBarButtonItem
    Message/
    Settings/
    Me/

布尔

因为 nil 解析为 NO,所以没有必要在条件中与它进行比较。永远不要直接和 YES 进行比较,因为 YES 被定义为 1,而 BOOL 可以多达 8 位。

这使得整个文件有更多的一致性和更大的视觉清晰度。

推荐:

if (!someObject) {
}

反对:

if (someObject == nil) {
}

对于 BOOL 来说, 这有两种用法:

if (isAwesome)
if (![someObject boolValue])

反对:

if ([someObject boolValue] == NO)
if (isAwesome == YES) // 永远别这么做

如果一个 BOOL 属性名称是一个形容词,属性可以省略 “is” 前缀,但为 get 访问器指定一个惯用的名字,例如:

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

内容和例子来自 Cocoa 命名指南

单例

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

+ (instancetype)sharedInstance {
   static id sharedInstance = nil;

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

   return sharedInstance;
}

这将会预防有时可能产生的许多崩溃

Xcode 工程

为了避免文件杂乱,物理文件应该保持和 Xcode 项目文件同步。Xcode 创建的任何组(group)都必须在文件系统有相应的映射。为了更清晰,代码不仅应该按照类型进行分组,也可以根据功能进行分组。

如果可以的话,尽可能一直打开 target Build Settings 中 "Treat Warnings as Errors" 以及一些额外的警告。如果你需要忽略指定的警告,使用 Clang 的编译特性

###版本控制

使用 Git 进行版本控制。工程版本号使用语义化版本

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment