写点什么

iOS 编码规范

作者:刁架构
  • 2022 年 4 月 14 日
  • 本文字数:7654 字

    阅读完需:约 25 分钟

iOS编码规范

前言

车同轨,书同文。编码规范既是高效合作的基础,也是深度创新的开始。编写此编码规范的目的如下:


  1. 统一开发风格,提升协作效率,让项目看起来是同一个人写的,方便快速修改别人的代码

  2. 作为代码 review 依据,不断打磨代码,使代码更健壮

  3. 为新人熟悉项目提供快速上手的抓手,迅速了解项目结构,熟悉一个模块则可以类推所有模块


本规范参考了业界一些比较知名公司的 iOS 开发规范做了适当的修改,以更适应本公司的业务开发需求。关于开发规范部分根据约束力强弱,依次分为以下 2 类:


  • 【强制】必须遵守,违反本约定或将会引起严重的后果

  • 【推荐】尽量遵守,长期遵守有助于系统稳定性和合作效率的提升


虽说一千个人眼中就有一千个哈姆雷特,但此规范更推荐全部使用【推荐】以上级别来严格要求自己,让自己的代码就像艺术品一样。


为使读者更容易理解此规范,响应规范下都对条目进行了举例说明。


  1. “正例”里面举例说明了什么样的编码格式是友好的、被人喜爱的

  2. “反例”里面举例说明的是什么样的编码格式是不够优雅的、让人迷惑的

一、美式英语规范

【强制】编程所使用的英语,请使用 US 英语(美式英语),因为美式英语更简洁,更符合我们日常生活中所使用的英语,而英式英语相对来说有些繁琐。可以参考如下示例:color 一词,iOS 系统控件 UIColor 使用的也是美式英语。遵循规则与 iOS 系统控件名称一致


/// 正例:
UIColor *myColor = [UIColor whiteColor];
复制代码


/// 反例:
UIColor *myColour = [UIColor whiteColor];
复制代码

二、代码组织结构

1. 核心思想

【强制】iOS 工程中最重要的类UIViewController中的代码组织应遵循以下规范在 ViewController 中按照不同功能划分函数,使相关功能写在一起,方便查找修改。根据功能不同,主要分为以下几个模块。如有新的无法归类的模块,则添加到懒加载后面


  • ViewController 中使用#pragma mark - xxx对方法进行分隔

  • 项目的生命周期应该在项目的最上部分,需要把 ViewController 所有生命周期相关的方法都放在一起

  • 接下来是按钮点击事件

  • 自定义方法

  • 系统代理方法

  • 自定义代理方法

  • 懒加载

  • 暂时无法归类的模块,一般都是帮助类(应该放到新的类中)


/// 使用 #pragma mark - xxx 分割项目/// /// 从上往下依次为:/// * 生命周期 Lifecycle/// * 按钮点击事件 Actions/// * 方法 Methods/// * 系统自身代理 UITableViewDataSource/// * 自定义代理 CustomDelegate/// * 懒加载 Property#pragma mark - Lifecycle
#pragma mark - Actions
#pragma mark - Methods
#pragma mark - UITableViewDataSource
#pragma mark - CustomDelegate
#pragma mark - Property
复制代码


2. 样例代码

/// 注意事项/// 1.pragma mark - 后面的关键字要写对,使用大驼峰命名,写成一个单词不要分开,方便搜索跳转/// 2.核心东西尽量放到VC的上面,即Lifecycle方法,需要对外暴露的方法等/// 3.VC尽量简洁,通用方法需要写成工具方法调用,比如加密算法、自定义弹框等,不要在VC中写,视图也应该写成子视图再引入/// 4.生命周期中的方法,需要按照顺序添加,即按照视图加载顺序从上到下依次引入
#pragma mark - Lifecycle- (instancetype)init {}- (void)viewDidLoad {}- (void)viewWillAppear:(BOOL)animated {}- (void)didReceiveMemoryWarning {}- (void)dealloc {}
#pragma mark - Actions- (void)sureButtonClicked {}
#pragma mark - Methods- (void)refreshUI {}
#pragma mark - UITableViewDataSource#pragma mark - UITableViewDelegate#pragma mark - UITextFieldDelegate#pragma mark - CustomDelegate
#pragma mark - Property- (NSMutableArray *)dataArray { if (!_dataArray) { _dataArray = [NSMutableArray array]; } return _dataArray;}
复制代码

三、命名规范

1. 基本原则

【推荐】清晰,尽可能遵守 Apple 的命名约定,命名应该尽可能的清晰和简洁,但在 Objective-C 中,清晰比简洁更重要。


  1. 类名采用大驼峰(UpperCamelCase)

  2. 类成员、方法小驼峰(lowerCamelCase)

  3. 局部变量大小写首选小驼峰,也可使用小写下划线的形式(snake_case)

  4. C 函数的命名用大驼峰


/// 正例:
/// 计算机中通用的约定俗成的缩写等,使用约定的样式,不要修改大小写ID, URL, JSON, WWW
/// 成员变量、方法名等使用小驼峰命名UIButton *settingsButton;setBackgroundColor:destinationSelection
复制代码


/// 反例:
/// 计算机中通用的约定俗成的缩写等,使用约定的样式,不要修改大小写id, Url, json, www
/// 不要使用简写,看名称不知道是干啥用的UIButton *setBut;setBkgdColor:destSel
复制代码

2. 一致性

1. 命名一致

  1. 【推荐】整个工程的命名风格要保持一致性,最好和苹果 SDK 的代码保持统一

  2. 不同类中完成相似功能的方法应该叫一样的名字。比如我们总是用 count 来返回集合的个数,不能在 A 类中使用 count 而在 B 类中使用 getNumber

2. 使用前缀

【推荐】如果代码需要打包成 Framework 给别的工程使用,或者工程项目非常庞大,需要拆分成不同的模块,使用命名前缀是非常有用的。

3. 前缀事项

  1. 【推荐】前缀由大写的字母缩写组成,比如 Cocoa 中 NS 前缀代表 NSFounation 框架中的类,IB 则代表 Interface Builder 框架

  2. 【推荐】可以在为类、协议、函数、常量以及 typedef 宏命名的时候使用前缀,但注意不要为成员变量或者方法使用前缀,因为他们本身就包含在类的命名空间内

  3. 【推荐】命名前缀的时候不要和苹果 SDK 框架冲突

3. 方法名

  1. 【推荐】在方法签名中,应该在方法类型(-/+ 符号)之后有一个空格。

  2. 【推荐】在方法各个段之间应该也有一个空格(符合 Apple 的风格)。

  3. 【推荐】在参数之前应该包含一个具有描述性的关键字来描述参数。

  4. 【推荐】"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.
复制代码

4. 变量名

1. 变量命名原则

【推荐】变量尽量以描述性的方式来命名。单个字符的变量命名应该尽量避免,除了在 for()循环。

2. 星号(*)的使用

【推荐】在 Objective-C 中,星号(*)应该紧贴变量


/// 正例:
NSString *text;
复制代码


/// 反例:
NSString* text;NSString * text;
复制代码

四、语法规范

1. 属性修饰符

【推荐】所有属性特性应该显式地列出来,有助于新手阅读代码。属性特性的顺序应该是atomic/nonatomicstrong/weakreadwrite/readonly


注:原版本与在Interface Builder连接 UI 元素时自动生成代码一致。此处经过讨论及参考第三方库的写法 ,决定修改为atomic/nonatomic放在strong/weak前面,代码看起来会更清晰。


/// 正例:
@property (nonatomic, copy) NSString *code;@property (nonatomic, strong) NSDictonary *dict;@property (nonatomic, readonly, getter=isIdType) BOOL idType;@property (nonatomic, readonly, getter=isNumberType) BOOL numberType;@property (nonatomic, readonly, getter=isBoolType) BOOL boolType;
/// Interface Builder 拉入的ViewController的控件直接使用即可,不需要修改/// 注意 nonatomic 和 weak 顺序与推荐顺序不一致@property (weak, nonatomic) IBOutlet UIView *containerView;
复制代码


/// 反例:
/// 从xib或者storyboard中拉入的控件,无需修改@property (nonatomic, weak) IBOutlet UIView *containerView;
/// 默认的 nonatomic、strong、readwrite 不要省略@property (nonatomic) NSString *tutorialName;
复制代码

2. 字符串使用 copy 修饰

【强制】NSString 应该使用 copy 而不是 strong 的属性特性。为什么?即使你声明一个 NSString 的属性,有人可能传入一个 NSMutableString 的实例,然后在你没有注意的情况下修改它。


/// 正例:
@property (nonatomic, copy) NSString *tutorialName;
复制代码


/// 反例:
@property (nonatomic, strong) NSString *tutorialName;
复制代码

3. 点符号语法

【推荐】点语法是一种很方便封装访问方法调用的方式。当你使用点语法时,通过使用gettersetter方法,属性仍然被访问或修改。点语法应该总是被用来访问和修改属性,因为它使代码更加简洁。[]符号方法更偏向于用在其他例子。应该遵循系统的使用习惯,如果是属性则使用点语法调用。例如:self.age = 20; 是调用的 set 方法,而 person.age = self.age; 是调用的 get 方法


/// 正例:
NSInteger arrayCount = [self.array count];view.backgroundColor = [UIColor orangeColor];[UIApplication sharedApplication].delegate;
复制代码


/// 反例;
NSInteger arrayCount = self.array.count;[view setBackgroundColor:[UIColor orangeColor]];UIApplication.sharedApplication.delegate;
复制代码

4. 常量集合

【推荐】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];
复制代码

5. 常量

【推荐】常量是容易重复被使用和无需通过查找和代替就能快速修改值。常量应该使用static来声明而不是使用#define,除非显式地使用宏。此处的星号()使用和上面的有所不同,星号()独占一个位置,不用贴近 const。


/// 正例:
static NSString * const RWTAboutViewControllerCompanyName = @"RayWenderlich.com";static CGFloat const RWTImageThumbnailHeight = 50.0;
复制代码


/// 反例:
#define CompanyName @"RayWenderlich.com"#define thumbnailHeight 2
复制代码

6. 枚举类型

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


/// 正例:
typedef NS_ENUM(NSInteger, RWTLeftMenuTopItemType) { RWTLeftMenuTopItemMain, RWTLeftMenuTopItemShows, RWTLeftMenuTopItemSchedule};
/// 或者也可以显式地赋值typedef NS_ENUM(NSInteger, RWTGlobalConstants) { RWTPinSizeMin = 1, RWTPinSizeMax = 5, RWTPinCountMin = 100, RWTPinCountMax = 500,};
复制代码


/// 反例:
enum GlobalConstants { kMaxPinSize = 5, kMaxPinCount = 500,};
复制代码

7. Case 语句

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


1.【强制】同一个 case 语句下,有多行代码时,需要添加大括号


/// 正例:
switch (condition) { case 1: // ... break; case 2: { // ... // Multi-line example using braces // (多行代码时,需要使用大括号) break; } case 3: // ... break; default: // ... break;}
复制代码


2.【推荐】当相同代码被多个 case 引用时,需要添加一个注释


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


3.【强制】当在 switch 使用枚举类型时,default是不需要的,这样当你修改枚举时,对应的使用位置则会报错提示你进行修改


/// 正例:
RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain;switch (menuType) { case RWTLeftMenuTopItemMain: // ... break; case RWTLeftMenuTopItemShows: // ... break; case RWTLeftMenuTopItemSchedule: // ... break;}
复制代码

8. 布尔值

1.【强制】在条件判断中,直接使用, Objective-C中使用YESNO


/// 正例:
if (someObject) {}if (![anotherObject boolValue]) {}
复制代码


/// 反例:
if (someObject == nil) {}if ([anotherObject boolValue] == NO) {}if (isAwesome == YES) {} // Never do this.if (isAwesome == true) {} // Never do this.
复制代码


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


/// 正例:
@property (nonatomic, assign, getter=isEditable) BOOL editable;
复制代码

9. 条件语句

【推荐】为防止出错,主体全部添加大括号


/// 正例:
if (!error) { return success;}
/// 或写成一行if (!error) return success;
复制代码


/// 反例:
if (!error) return success;
复制代码

10. 三元操作符

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


/// 正例:
NSInteger value = 5;result = (value != 0) ? x : y;
BOOL isHorizontal = YES;result = isHorizontal ? x : y;
复制代码


/// 反例:
result = a > b ? x = c > d ? c : d : y;
复制代码

五、方法规范

1. Init 方法

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


/// 正例:
- (instancetype)init { self = [super init]; if (self) { // ... } return self;}
复制代码

2. 类构造方法

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


/// 正例:
@interface Airplane+ (instancetype)airplaneWithType:(RWTAirplaneType)type;@end
复制代码

3. CGRect 函数

【推荐】当访问CGRect里的xywidthheight时,应该使用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 };
复制代码

4. 黄金路径

【强制】当使用条件语句时,尽量不要嵌套 if 语句,多个返回也是 ok 的减少层级,代码更简洁


/// 正例:
- (void)someMethod { if (![someOther boolValue]) { return; }
//Do something important}
复制代码


/// 反例:
- (void)someMethod { if ([someOther boolValue]) { //Do something important }}
复制代码

5. 错误处理

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


/// 正例:
NSError *error;if (![self trySomethingWithError:&error]) { // Handle Error}
复制代码


/// 反例:
NSError *error;[self trySomethingWithError:&error];if (error) { // Handle Error}
复制代码

6. 单例模式

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


/// 正例:
+ (instancetype)sharedInstance { static id sharedInstance = nil;
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[self alloc] init]; });
return sharedInstance;}
复制代码

7. 换行符

【推荐】换行符是一个很重要的主题,因为它大大的提高了可读性一行很长的代码应该分成两行代码,下一行用两个空格隔开


/// 正例:
self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
复制代码


/// 反例:
self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
复制代码

8. 大括号换行

【推荐】方法大括号和其他大括号ifelseswitchwhile等等总是在同一行语句打开但在新行中关闭。


/// 正例:
if (user.isHappy) { //Do something} else { //Do something else}
复制代码


/// 反例:
if (user.isHappy){ //Do something}else { //Do something else}
复制代码

9. 方法换行

  1. 【推荐】在方法之间应该有且只有一行,这样有利于在视觉上更清晰和更易于组织。

  2. 【推荐】在方法内的空白 主要用来分离功能,但通常都抽离出来成为一个新方法。

  3. 【推荐】如果一个方法有多个参数,使用冒号对齐参数,直接在每个参数后面回车就行,Xcode会自动处理相应格式

  4. 【推荐】如果方法中包含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 }];
复制代码

10. 注释

【推荐】当需要注释时,注释应该用来解释这段特殊代码为什么要这样做。任何被使用的注释都必须保持最新或被删除。


一般都避免使用块注释,因为代码尽可能做到自解释,只有当断断续续或几行代码时才需要注释。例外:这不应用在生成文档的注释


六、参考链接

  1. https://github.com/google/styleguide/blob/gh-pages/objcguide.md

  2. https://github.com/raywenderlich/objective-c-style-guide/blob/master/README.md#dot-notation-syntax

  3. https://github.com/NYTimes/objective-c-style-guide

  4. https://github.com/samlaudev/Objective-C-Coding-Style

发布于: 刚刚阅读数: 2
用户头像

刁架构

关注

叫我刁架构 2017.10.25 加入

预备备网红首席架构师,移动端开发者,边缘设计支持者。

评论

发布
暂无评论
iOS编码规范_ios_刁架构_InfoQ写作平台