GithubHelp home page GithubHelp logo

objective-c-style-guide's Introduction

Objective-C Style Guide

介绍

苹果关于 style guide 的文档:

本文遵守 IETF 的 RFC 2119。简单介绍一下各关键字的意思,用于对照:

  1. MUST / REQUIRED / SHALL
    表示绝对要求这样做。
  2. MUST NOT / SHALL NOT
    表示绝对不要求这样做。
  3. SHOULD / RECOMMENDED
    表示一般情况下应该这样做,但是在某些特定情况下可以忽视这个要求。
  4. SHOULD NOT / NOT RECOMMENDED
    表示一般情况下不应该这样做,但是在某些特定情况下可以忽视这个要求。
  5. MAY / OPTIONAL
    表示这个要求完全是可选的,你可以这样做,也可以不这样做。

目录

点语法

点语法介绍

RECOMMENDED 使用点语法来访问或者修改属性,使用方括号来调用方法。

推荐:

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

反对:

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

间距

  • MUST 一个缩进使用 4 个空格,永远不要使用制表符(tab)缩进。请确保在 Xcode 中设置了此偏好。
  • MUST 方法的大括号和其他关键字的大括号(if/else/switch/while 等等)始终和声明在同一行开始,在新的一行结束。

推荐:

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

反对:

if (user.isHappy)
{
    //Do something
}
else
{
    //Do something else
}
  • SHOULD 方法之间应该正好空一行,这有助于视觉清晰度和代码组织性。
  • MAY 方法中的功能块之间应该使用空白分开,但往往可能应该拆成几个子方法。
  • MAY 如果方法名过长,可以在方法名和方法体之间加一个空行,在视觉上进行分隔。
  • MUST 优先使用自动生成的 @synthesize。如果自己声明,@synthesize@dynamic 在实现中每个都应该占一个新行。
  • SHOULD 通常应该避免冒号对齐式的方法调用。只有在方法签名有 >= 3 个冒号,且冒号对齐能使代码更具可读性时才用冒号对齐。
  • MUST NOT �包含 block 的方法不要冒号对齐,因为 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
                 }];

条件判断

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

推荐:

if (!error) {
    return success;
}

反对:

if (!error)
    return success;

if (!error) return success;

三目运算符

SHOULD 三目运算符?:的作用是让代码更清晰或更整洁。应该只在单一条件时使用。多条件时通常使用 if 语句会更易懂,或者重构为带名字的变量。通常三目运算符用于赋值时二选一。

SHOULD 非布尔变量要和其他值进行比较,并加括号来提高可读性。布尔类型的变量不用加括号。

推荐:

result = (a > b) ? x : y;

BOOL isHorizontal = YES;
result = isHorizontal ? x : y;

反对:

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

错误处理

MUST 当方法通过错误参数的引用返回错误时,必须先检查方法的返回值,一定不要先检查错误参数。

推荐:

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

反对:

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

一些苹果的 API 在成功的情况下会写一些垃圾值给错误参数(如果非空),所以针对错误变量可能会造成虚假结果(以及接下来的崩溃)。

方法

  • SHOULD 在方法签名中,在 -/+ 符号后应该有一个空格。方法段之间应该有一个空格(符合 Apple 的规范)。

  • MUST 参数的前面有描述参数的关键字。

  • SHOULD NOT 单词 “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.

???:

  • SHOULD 方法超过 80 个字符应该折行显示,每行一个参数。

推荐:

- (void)setExampleText:(NSString *)text
                 image:(UIImage *)image
                 color:(UIColor *)color
       alternativeText:(NSString *)altText;

变量

SHOULD 变量的命名应该具有描述性,能清楚地表示这个变量是什么,以及开发者要正确使用它应该知道的相关信息。

例如

  • NSString *title: 从 “title” 这个名字可以推断出它是一个字符串。
  • NSString *titleHTML: 它表示标题可能包含需要解析的 HTML。开发者知道 HTML 这一信息可以更有效地使用该变量。
  • NSAttributedString *titleAttributedString: 一个带格式的标题。AttributedString表示它不只是一个文本标题,添加带格式的标题可能需要依赖上下文。
  • NSDate *now: 无需额外说明。
  • NSDate *lastModifiedDate: 只有lastModified是意思不明确的;可被理解为多种类型。
  • NSURL *URL vs. NSString *URLString: 当变量名可以表示另一种类型,应该加入类型信息来消除歧义。
  • NSString *releaseDateString: 同上。

NOT RECOMMENDED 除了循环中简单的计数变量外,其他情况都应该避免使用单字母的变量名。

MUST 星号表示的指针类型必须跟随变量,例如NSString *text 不要写成 NSString* text 或者 NSString * text,常量除外(NSString * const NYTConstantString)。

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

推荐:

@interface NYTSection : NSObject

@property (nonatomic) NSString *headline;

@end

反对:

@interface NYTSection : NSObject {
    NSString *headline;
}

变量限定符

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

命名

SHOULD 尽可能遵守苹果的命名约定,尤其那些涉及到内存管理规则NARC)的。

使用长的、描述性的方法名和变量名是好的做法。

推荐:

UIButton *settingsButton;

反对:

UIButton *setBut;

MUST 类名和常量必须使用三个字母的前缀(例如 TBK),但 Core Data 实体名可以省略前缀。两个字母的前缀(如NS)由Apple 保留使用权

MUST 常量必须使用驼峰命名法,并以相关类的名字作为前缀。

推荐:

static const NSTimeInterval NYTArticleViewControllerNavigationFadeAnimationDuration = 0.3;

反对:

static const NSTimeInterval fadetime = 1.7;

MUST 属性和局部变量应该使用驼峰命名法并且首字母小写。

MUST 为了保持一致,实例变量应该使用驼峰命名法命名,并且首字母小写,以下划线为前缀。这与 LLVM 自动合成的实例变量相一致。 如果 LLVM 可以自动合成变量,那就让它自动合成。

推荐:

@synthesize descriptiveVariableName = _descriptiveVariableName;

反对:

id varnm;

Categories

RECOMMENDED Category 应该用于将功能分段,它的名字应该描述这部分功能。

For example:

@interface UIViewController (NYTMediaPlaying)
@interface NSString (NSStringEncodingDetection)

Not:

@interface NYTAdvertisement (private)
@interface NSString (NYTAdditions)

MUST Category 中的方法和属性的名字必须带有 app 或 organization 的前缀。这样可以避免覆盖已存在的方法,而且降低了来自不同库的 category 添加同名方法的可能性。(对于后者,Objective-C 运行时不指定调用哪个方法,这会导致意外的后果。)

For example:

@interface NSArray (NYTAccessors)
- (id)nyt_objectOrNilAtIndex:(NSUInteger)index;
@end

Not:

@interface NSArray (NYTAccessors)
- (id)objectOrNilAtIndex:(NSUInteger)index;
@end

下划线

MUST 总是通过属性读写实例变量。所以有 self. 前缀的都是属性。

SHOULD 例外:在初始化方法里,应该直接使用实例变量(如 _variableName)来避免 getter/setter 的副作用。

SHOULD NOT 局部变量不应该包含下滑线。

注释

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

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

init 和 dealloc

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

SHOULD init 方法参考 Apple 的代码模板。返回 instancetype 类型而不是 id。结构如下:

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

字面量

SHOULD 每当创建 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 = 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 };

常量

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

推荐:

static NSString * const NYTAboutViewControllerCompanyName = @"The New York Times Company";

static const CGFloat NYTImageThumbnailHeight = 50.0;

反对:

#define CompanyName @"The New York Times Company"

#define thumbnailHeight 2

枚举类型

MUST 当使用 enum 时,必须使用新的固定底层类型的规范,因为它具有更强的类型检查和代码补全功能。现在 SDK 包含了一个宏来鼓励使用固定底层类型:NS_ENUM()

推荐:

typedef NS_ENUM(NSInteger, NYTAdRequestState) {
    NYTAdRequestStateInactive,
    NYTAdRequestStateLoading
};

也可以显式赋值:

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

SHOULD NOT 避免使用老式 k 开头的常量定义,除非写 CoreFoundation C 代码(几乎不可能)。

反对:

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

位掩码

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

举例:

typedef NS_OPTIONS(NSUInteger, NYTAdCategory) {
    NYTAdCategoryAutos      = 1 << 0,
    NYTAdCategoryJobs       = 1 << 1,
    NYTAdCategoryRealState  = 1 << 2,
    NYTAdCategoryTechnology = 1 << 3
};

私有属性

SHALL 私有属性应该声明在类实现文件的类扩展(匿名 category)中。类扩展不能有名字(如 TBKPrivateprivate),有名字的 category 是用于扩展另一个类。匿名 category 可以使用 <headerfile>+Private.h 的文件命名转换来分享 / 暴露给测试。

推荐:

@interface NYTAdvertisement ()

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

@end

图片命名

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

推荐:

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

SHOULD 用于类似目的的图片应该�被分组,放入 Images 文件夹或 Asset Catalog。

布尔

MUST Objective-C 使用 YESNO。而 truefalse 只用于 CoreFoundation,C 或 C++ 代码。

MUST NOT 永远不要直接和 YES 进行比较,因为 YES 被定义为 1,而 BOOL 在 Objective-C 中是一个 CHAR 类型,多达 8 位(所以如果 11111110YES 比较会返回 NO)。

对于对象指针:

if (!someObject) {
}

if (someObject == nil) {
}

对于 BOOL 值:

if (isAwesome)
if (!someNumber.boolValue)
if (someNumber.boolValue == NO)

反对:

if (isAwesome == YES) // Never do this.
if (isAwesome == true) {} // Never do this.

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

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

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

单例

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

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

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

    return sharedInstance;
}

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

导入

MUST 如果有一个以上的 import 语句,就对这些语句进行分组MAY 每个分组的注释是可选的。

注:对于模块使用 @import 语法。

// Frameworks
@import QuartzCore;

// Models
#import "NYTUser.h"

// Views
#import "NYTButton.h"
#import "NYTUserView.h"

协议

SHOULD委托或数据源协议中,各方法的第一个参数应该是发送消息的对象。

当一个对象是多个类型相似的对象的委托时,这有助于消除歧义;而且有助于明确一个类实现这些委托方法的意图。

推荐:

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

反对:

- (void)didSelectTableRowAtIndexPath:(NSIndexPath *)indexPath;

Xcode 工程

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

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

语言

SHOULD 使用 US English。

代码组织

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

属性的特性

SHOULD 属性的特性应该显式列出,能帮助开发者阅读代码。顺序是先存储类型后原子性,这和连接 Interface Builder 的 UI 元素时自动生成的代码一致。

推荐:

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

反对:

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

SHOULD 有对应可变类型的属性(如 NSString)应该优先使用 copy 而不是 strong。因为即使声明的属性类型是 NSString,也可以传过来 NSMutableString 的实例,这样会发生改变而没有任何通知。

推荐:

@property (copy, nonatomic) NSString *tutorialName;

反对:

@property (strong, nonatomic) NSString *tutorialName;

Case 语句

Case 语句不需要大括号,除非编译器强制要求。 当 case 包含多行代码,应该加括号。

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

SHOULD 当多个 case 可使用相同的代码时,应该使用 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

RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain;

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

类构造方法

MUST 使用类构造方法总是返回 instancetype 类型而不是 id。这样保证编译器能正确推断结果类型。

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

关于 instancetype 可参考 NSHipster.com

Golden Path

SHOULD 不要嵌套 if 语句。可以有多个 return 语句。即 Early Exit。

推荐:

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

    //Do something important
}

反对:

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

换行

SHOULD 很长的一行代码应该换到第二行,然后遵守本规范的间距部分(四个空格)。

推荐:

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

反对:

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

其他 Objective-C 风格指南

objective-c-style-guide's People

Contributors

luna-moonfang avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.