Objective-C编程规范

引言

背景

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

目录

语言

使用美式英语

Preferred:

UIColor *myColor = [UIColor whiteColor];

Not Preferred:

UIColor *myColour = [UIColor whiteColor];

代码组织

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

#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

#pragma mark - UITextFieldDelegate
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate

#pragma mark - NSCopying

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

#pragma mark - NSObject

- (NSString *)description {}

如果private的方法过多

#pragma mark - Private

#pragma mark - AAAA

#pragma mark - BBBB

空格

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

Preferred:

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

Not Preferred:

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

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

Preferred:

// blocks are easily readable
[UIView animateWithDuration:1.0f 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
                 }];
  • 签代理

Preferred:

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

Not Preferred:

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

注释

  • 确实需要, 才加注释

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

Preferred:

// alignment attributes must have a secondViewAttribute
// therefore we assume that is refering to superview
// eg make.left.equalTo(@10)
if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
    secondLayoutItem = self.firstViewAttribute.view.superview;
    secondLayoutAttribute = firstLayoutAttribute;
}

Not Preferred:

/**
 * show head image
 */
@property (nonatomic, strong) DDImageView *headView;

// im conversation done edit
- (void)imConversationDoneEdit;
  • 添加的注释要保持更新或删除

命名

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

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_normal
conference_member_delete_highlight  // 指代功能名较长且有多种状态的图标名

下划线

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

注意:

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

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

方法

  • 方法类型(-/+)的后面有个空格

  • 每一个参数都要有描述

注意:

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

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.

变量

变量命名要可读性强, 避免用单个单词的命名方式, 除了在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

使用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表达式通常不需要加括号

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 this
if (isAwesome == true) {} // Never do this

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

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

例子来自Cocoa Naming Guidelines.

条件语句

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

Preferred:

if (!error) {
  return success;
}

Not Preferred:

if (!error)
  return success;

or

if (!error) 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 (self) {
    // Do something
  }
  return self;
}

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

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

类构造方法

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

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

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

CGRect函数

要获取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(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
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 = (CGRect){ .origin = CGPointZero, .size = frame.size };

Golden Path

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

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

Preferred:

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

Not Preferred:

- (void)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 (error) {
  // 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:

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

Not Preferred:

- (void)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 (c) 2015年 QIYOU Ltd. All rights reserved.
//

Not Preferred:

//  MSDetailRecordsViewController.m
//  chenguang
//
//  Created by chenguang on 15-12-6.
//  Copyright (c) 2015年 CloudPhone. All rights reserved.
//

Xcode Project

  • Xcode Group要和文件系统里的文件夹关联起来

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

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

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

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

其他Objective-C编程规范

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容

  • 引言 一直以来都是在谈如何开发, 如开发的小技巧小经验 今天为什么突然说起编程规范来了呢? 因为在我看来, 编程规...
    诺之林阅读 541评论 1 5
  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,118评论 0 13
  • Introduction 这个style guide规范描述了我们iOS开发团队喜欢的Objectiv-C编程习惯...
    小山Sam阅读 645评论 0 4
  • 从其他地方整理了一些编码规范的资料,分享给大家。YoY 1.语言(Language) 需要使用US English...
    大脸猫121阅读 432评论 0 2
  • 七月是火辣辣的季节,即便雨后也照样闷热,知了尽情地歌唱,好像觉得这潮热还不够给力。每个夜晚,世界杯带给人们无限激情...
    伊人自悦阅读 641评论 5 11