11.Objective-C 编码规范指南

@(〓〓 iOS-实用技术)[Objective-C编码规范]


目录

  • 11.Objective-C 编码规范指南
  • 项目工程结构
  • 代码结构
  • 项目Xode相关配置
    • 代码缩进配置
    • 代码行号显示配置
    • 项目编码建议
  • 注释
    • 方法注释
    • 属性注释
  • 命名与编码规范
    • 类名命名规则
    • 协议编码规则
    • 控件/控制器定义命名规则
  • 方法
    • 方法名和参数命名规则
    • 方法声明和编码规范
    • 方法调用父类方法编码规则
  • 函数
  • 变量
    • 成员属性编码规则
  • 常量
    • Foundation框架常量赋值规则
    • 定义普通常量以字母k开头
  • 枚举与宏定义
    • 枚举编码规则
    • NS_ENUM定义普通枚举
    • NS_OPTIONS定义位枚举
  • 通知和异常
    • 通知NSNotification常量命名规则
    • 异常命名规则
  • 布尔值
  • 条件语句
    • if else 条件语句
    • switch case 语句
  • 其他规则
    • 初始化方法(构造方法)规则
    • 直接放回对应数据时,无需添加get, calc单词
    • 单例的声明和使用规则
    • 设置常用属性,直接使用点语法
    • 协议和代理方法命名规则
    • 可以使用({})语法模块化设置控件属性(可选)

项目工程结构


代码结构

  • 实现文件中的代码结构,提倡以下约定:

    • #pragma mark -将函数或方法按功能进行分组;分组之间空2行,方法之间空1行.
    • delgate或协议相关方法放到一般内容之后。
#pragma mark - Lifecycle (生命周期)
- (void)dealloc {}

- (instancetype)init {}

- (void)viewDidLoad {}

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

- (void)didReceiveMemoryWarning {}


#pragma mark - Private (私有方法,比如初始控件设置方法/类内部业务处理方法)
- (CGFloat)setupTableView {}


#pragma mark - Public (对外公开方法)
- (CGFloat)reloadAllData {}


#pragma mark - Network (加载网络数据)
- (void)loadMoreData {}
- (void)loadNewData {}
- (void)loadOtherData {}


#pragma mark - Property Setter/Getter (成员属性的setter和getter方法)
- (void)setCustomProperty:(id)value {}

- (id)customProperty {}


#pragma mark - Events (UIControl响应函数,如按钮点击事件)
- (void)saveButtonClick:(UIButton *)saveButton {}


#pragma mark - KVO/Notification (KVO/通知响应函数)
- (void)dataSourceRefreshNotification:(NSNotification *)notification {}

- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id> *)change context:(nullable void *)context {}


#pragma mark - Protocol/Delegate 如UITableViewDataSource/UITableViewDelegate (协议和代理)
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {}

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

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {}


#pragma mark - LazyLoad (懒加载)
- (UILabel *)nameLabel {}


#pragma mark - Other (其他)
- (id)copyWithZone:(NSZone *)zone {}
- (NSString *)description {}


项目Xode相关配置


代码缩进配置

  • 只用空格缩进1个TAB = 4个空格字符

XCode->Preferences->Text Editing->Indentation中进行如下设置:
ps. 设置成4个,是因为Xcode的默认缩进是4个空格。

01.代码格式化配置.png

代码行号显示配置

  • Xcode行号显示配置

勾选上XCode->Preferences->Text Editing->Editing中的Line numbers,开启行号提示。

02.代码行号显示配置.png

项目编码建议

  • 建议:每行代码的长度最多不超过120个字符;

勾选XCode->Preferences->Text Editing->Editing,并将长度设置成120个字符来打开行宽指示。设置成功时Xcode会出现一条竖线! (可选配置)

  • 建议:为了简洁和便于阅读,请尝试将单个函数或方法的实现代码控制在50行内;单个实现文件里的代码行数控制在500~600行内;

  • 建议:为了简洁和便于阅读,请尝试将单个函数或方法的实现代码控制在50行内;当接近或超过800行时,就应当开始考虑分割实现文件了。


注释


方法注释

  • 方法注释规则
    • 注释应该尽量保持简洁,代码应该尽量达到能自我解释的程度;
    • 注释必须和代码保持同步。不要出现代码修改了,注释不更新的情况;
    • 对外公开方法 使用 /** 方法描述 */VVDocumenter-Xcode注释,公开方法需尽量注释清楚;如果公开方法有带参数,可按需对参数进行说明;
    • 其他方法可使用 // 方法描述VVDocumenter-Xcode 注释;
    • 方法内部行注释需对齐;
// .m文件
// 按钮点击事件处理 (非对外公开的方法)
- (void)saveButtonClick:(UIButton *)saveButton {
    NSLog(@"Hello Liwx");               // 打印日志
    NSLog(@"Hello Liwx");               // 打印日志
}

/** 刷新全部数据 (对外公开的方法)*/
- (void)reloadAllData {
    //Do Something
}

// .h文件
/** 刷新全部数据 (对外公开的方法)*/
- (void)reloadAllData;

属性注释

  • 建议外部属性注释建议使用/** 属性描述 */注释, 因为Xcode对此类注释有提示功能,便于开发人员在编码时能更快的了解该属性的作用;

  • 建议属性与@interface ... @end之间各空1行;

@interface ViewController () 

/** 时间 */
@property (nonatomic, copy) NSString *time;

@end
03.外部属性注释提示.png
  • 私有属性注释可使用// 属性描述/** 属性描述 */注释;

命名与编码规范


类名命名规则

  • 类名命名规则
    • 类名、类别名字及协议名字,都采用大驼峰式命名规则

    • 文件名要能反映出它所包含的类的名称

      如:NSString.h 和 NSString.m 包含了NSString类的定义和实现

    • Category的文件名要包含它所扩展的那个类的名称,并且类别名称要尽量能够描述它的功能

      UIImage+Resize.h 或 UIImage+TintColor.h

    • 在面向特定应用的代码中,类名尽量避免使用前缀,每个类都使用相同的前缀会影响可读性

      面向特定应用的代码,指那些只会在一个项目中使用的代码,不会被用于其他项目中的代码。

    • 在面向多应用的代码中,类名要使用前缀,防止命名冲突

      面向多应用的代码,指那些会被多个项目共同使用的代码。

      比如CRKit这个类库中,使用了CR前缀。

    • 建议:前缀至少使用三个字母

      此条是为了减少命名冲突。但鉴于目前流行前缀大多都是两个字母,所以此条不做强制要求


协议编码规则

  • 协议编码规则
    • 协议声明或定义中,类型标识符、协议名称、尖括号之间不留空格;
// 协议声明协议名称、尖括号之间不留空格
@protocol UITableViewDelegate<NSObject, UIScrollViewDelegate>

@end
// 定义属性遵守协议,不留空格
@property (nonatomic, weak, nullable) id <UITableViewDelegate> delegate;


控件/控制器定义命名规则

  • 控件命名尽量使用控件全称命名,不建议使用缩写;命名规则: [功能(name)]+[控件名(Label)]
// 建议使用控件全称命名
/** 名称Label */
@property (nonatomic, weak) UILabel *nameLabel;
/** 保存Button */
@property (nonatomic, weak) UIButton *saveButton;

// 不建议控件名使用缩写
/** 名称Label */
@property (nonatomic, weak) UILabel *nameLbl;
/** 保存Button */
@property (nonatomic, weak) UIButton *saveBtn;

  • 定义或声明类时如果名称过长采用后缀缩写;注意大小写;
    • 如控制器ViewController可缩写为Vc;
    • TableViewCell可缩写为TvCell;
    • CollectionViewCell可缩写为CvCell;
// TableViewCell后缀缩写为TvCell
TopicTableViewCell *topicTvCell = nil;
// CollectionViewCell后缀缩写为CvCell
ItemCollectionViewCell *itemCvCell = nil;
// ViewController可缩写为Vc
HomeViewController *homeVc = nil;

方法


方法名和参数命名规则

  • 方法名和参数名都采用小驼峰式命名规则;

如:- (BOOL)isFileExistedAtPath:(NSString *)filePath;

  • 方法名可以使⽤用情态动词( can , should , will 等)来提⾼高清晰性,但不要使⽤用 do 或 does;
// 正确
- (BOOL)canHide;
- (BOOL)shouldRefreshData;
- (void)willChangeData

方法声明和编码规范

  • 方法声明中,-/+返回值类型之间要空1个空格方法名参数类型之间以及参数类型和参数名之间不留空格;
- (instancetype)initwithTitle:(NSString *)title;    // 正确
-(instancetype)initwithTitle:(NSString *)title;     // 错误
- (instancetype) initwithTitle:(NSString *)title;   // 错误
- (instancetype)initwithTitle: (NSString *)title;   // 错误
- (instancetype)initwithTitle:(NSString *) title;   // 错误
  • 方法名和参数名应该尽量读起来像一句话。

如:convertPoint:fromRect: 或者 replaceCharactersInRange:withString:

  • 当各个参数是接收者的某个属性时,方法名中不要用"and"来连接;
// 正确
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil;
// 错误
- (instancetype)initWithNibName:(NSString *)nibNameOrNil andBundle:(NSBundle *)nibBundleOrNil;
  • 方法名之后空1格,紧随左大括号{,无需换号;但是右大括号}必须另取一行
- (CGFloat)setupTableView {
    // Do Something
}

方法调用父类方法编码规则

  • 重载父类方法时,遇到必须调用父类方法时。调用super的代码和重载的代码之间留一行空行。将super方法的调用和重载代码区隔开来.
- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];

    // Do Something
}

函数

  • 函数指纯C函数,这里提倡与苹果风格类似的约定。

    • 函数名采用大驼峰式命名方式,``参数名采用小驼峰式`命名方式

    • 如果函数和某个特定类型相关,那么函数名前缀要和类型前缀一样

CGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height)


变量

成员属性编码规则

  • 属性名变量名都采用小驼峰式命名规则;
  • 禁止使用匈牙利标记法或含糊不清的缩写单词来命名变量;
  • 指针符号 "*" 靠近变量名字。(常量定义除外)
  • property属性括号两边各空1格, 属性关键字以逗号加空格隔开
  • 除了xib拖控件的方式除外,nonatomic需放在最前面, strong,weak,assign,copy应放在nonatomic后面;
  • 成员变量和局部变量声明Demo
// 成员变量声明
/** 标题Label (正确) */
@property (nonatomic, copy) NSString *titleLabel;
/** 标题Label (错误) */
@property (nonatomic, copy) NSString *titleLbl;
/** 标题Label (错误) */
@property(nonatomic, copy) NSString *titleLabel;
/** 标题Label (错误) */
@property (nonatomic,copy) NSString *titleLabel;
/** 标题Label (错误) */
@property (copy, nonatomic) NSString *titleLabel;

// 局部变量声明
NSString *titleLabel = nil;     //  正确
NSString* titleLabel = nil;     //  错误
NSString * titleLabel = nil;    //  错误
NSString*titleLabel = nil;      //  错误
  • 使用property时,优先使用点语法;
/** 名称 */
@property (nonatomic, copy) NSString *name;

// 访问成员属性时,优先使用点语法
self.name = @"Liwx";
  • 赋值操作符 "="两边各空1格;
self.name = @"Liwx";    // 正确
self.name= @"Liwx";     // 错误
self.name=@"Liwx";      // 错误
  • 如果使用property修饰的是属性BOOL值,建议为getter方法加上一个"is"开头的别名。

@property (assign, getter = isSelected) BOOL selected;

  • 如果网络获取的属性数据为数值型的,则定义属性也应该为数值型;如果是枚举类型,则应定义对应枚举类型;不应将数值型定义为NSString字符串类型;
// 正确
@property (copy,nonatomic) UserStatus *userStatus;
// 错误
@property (copy,nonatomic) NSString *userStatus;

常量


Foundation框架常量赋值规则

  • 为了提高代码简洁度,创建NSString, NSDictionary, NSArray, 以及NSNumber等常量时,使用Literals语法;
// 正确
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 *zipCode = [NSNumber numberWithInteger:10018];

定义普通常量以字母k开头

  • 如果定义的常量是类,则格式为: [类名] + [*] + [const] + [k+常量描述];
  • 如果定义的常量属于基本数据类型, 则格式为: [基本数据类型] + [const] + [k+常量描述];
// 普通常量定义
NSString * const kUserKey = @"kUserKey";
CGFloat const kTopViewHeight = 50;
  • 只在某一个特定文件里面使用的常量,用static

static关键字保证变量只有文件作用域,可以避免变量名重名造成的链接错误问题。

如: static CGFloat const RWImageThumbnailHeight = 50.0;


枚举与宏定义


枚举编码规则

  • 定义枚举常量时,使用NS_ENUMNS_OPTIONS;

因为NS_ENUMNS_OPTIONS都提供了类型检查;

  • 定义枚举时,一定要注释,并且格式为: [枚举类型名(UITableViewStyle)] + [类型(Plain)];

NS_ENUM定义普通枚举

  • 使用NS_ENUM定义普通枚举Demo,建议枚举值 = 对应数值
// 应用皮肤样式
typedef NS_ENUM(NSInteger, AppStyle) {
    AppStyleLight = 0,              // 白天模式
    AppStyleDark = 1                // 夜间模式
};

NS_OPTIONS定义位枚举

  • 使用NS_OPTIONS定义位枚举
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,         // 注释
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,    // 注释
    UIViewAutoresizingFlexibleWidth        = 1 << 1,    // 注释
    ...
};

通知和异常


通知NSNotification常量命名规则

  • 通知常量命名格式: [相关联的类名字] + [Did | Will] + [独一无二的一段名称] + Notification
    • 建议:定义通知常量不采用宏定义的方法;
    • .h文件, 全局通知声明时需加上UIKIT_EXTERN关键字
    • .m文件, 通知常量名和值必须保持一致
  • 通知命名Demo
// .h文件
// 全局通知声明时需加上UIKIT_EXTERN关键字
UIKIT_EXTERN NSString *const UIKeyboardDidChangeFrameNotification ;

// .m文件, 通知常量名和值必须保持一致
NSString *const UIKeyboardDidChangeFrameNotification = @"UIKeyboardDidChangeFrameNotification";

异常命名规则

  • 异常名字的命名规则:[前缀] + [独一无二的一段名称] + Exception

如:NSColorListIOException


布尔值

  • Objective-C的布尔值只使用YESNO;
  • 注意: truefalse只能用于CoreFoundation,C或C++的代码中;
  • 禁止将某个值或表达式的结果与YES进行比较
// 正确
if (self.isLogin) {}
// 错误
if (self.isLogin == YES) {}
  • 如果返回值为BOOL值,必须确保返回值为YES或NO,最好不要存在多值的情况
// 正确
- (BOOL)isLogin {
    return self.userToken.length != 0 ? YES : NO;
}
// 错误
- (BOOL)isLogin {
    return self.userToken.length;
}

条件语句


if else 条件语句

  • 条件语句的语句体,即便只有一行,也不能省略花括弧;
  • 判断条件之后空1格,紧随左大括号{,无需换号;但是右大括号}必须另取一行;
  • else或 elseif应紧随在右大括号}之后,并且中间空1格;
// 正确
if (isLogin) {
    // Do Something
} else {
    // DO Something
}

// 错误
if (isLogin) 
{
    // Do Something
} 
else {
    // DO Something
}
// 正确
if (error == nil) {
    return success;
}
// 错误
if (error == nil)
    return success;
  • 多层嵌套的条件语句,优先考虑条件不成立可以立即跳出的情况
// 优先考虑可以跳出的流程
if (!a) {
    return;
}

if (!b) {
    return;
}

if (!c) {
    return;
}

// 不建议使用嵌套的方式
if (a) {
    if (b) {
        if (c) {
        } 
    } 
} 
  • 三目运算只有在能增加代码清晰度和整洁度的时候才推荐使用
// 正确
NSInteger value = 5;
result = (value != 0) ? x : y;
// 错误
result = a > b ? x = c > d ? c : d : y;

switch case 语句

  • switch case如果判断条件是枚举类型的值,则case也应该为枚举值,而不是 case 1;并且可以省略default处理
RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain;

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

其他规则


初始化方法(构造方法)规则

  • 初始化方法的返回类型用instancetype,而不是用id;
  • 如果重写init方法,必须调用[super init]方法;

直接放回对应数据时,无需添加get, calc单词

- (CGFloat)cellHeight;      // 正确
- (CGFloat)getCellHeight;   // 错误
- (CGFloat)calcCellHeight;  // 错误

单例的声明和使用规则

  • 获取单例的类方法
+ (instancetype)sharedInstance {
    static id sharedInstance = nil;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    
    return sharedInstance;
}
  • UIApplication的使用规则
    • 获取单例的方法不使用点语法;
// 正确
[UIApplication sharedApplication].delegate;
// 错误
UIApplication.sharedApplication.delegate;

设置常用属性,直接使用点语法

// 正确
self.view.backgroundColor = [UIColor redColor];
// 错误
[self.view setBackgroundColor:[UIColor redColor]];

协议和代理方法命名规则

  • 协议方法名开头应与类名一样(不包含前缀)
  • 必须将本身作为参数传递给外部,如(UITableView *)tableView
  • 传递所需参数给外部,如(NSIndexPath *)indexPath;

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

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


可以使用({})语法模块化设置控件属性(可选)

  • 要使用({})语法,前提: 该属性需由strong修饰;
// 前提: 该属性需由strong修饰
/** 名称Label */
@property (nonatomic, strong) UILabel *nameLabel;

self.nameLabel = ({
        UILabel *label = [[UILabel alloc] init];
        label.textAlignment = NSTextAlignmentCenter;
        label.font = [UIFont systemFontOfSize:12];
        label.textColor = [UIColor orangeColor];
        label;
    });

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

推荐阅读更多精彩内容