IOS编码规范

一.总体原则

  1. 需求是暂时的,只有变化才是永恒的,面向变化编程,而不是面向需求编程。
  2. 不要过分追求技巧,降低程序的可读性
  3. 简洁的代码可以让bug无处藏身。要写出明显没有bug的代码,而不是没有明显bug的代码
  4. 先把眼前的问题解决掉,解决好,再考虑将来的扩展问题。

二.编码规范

1.if表达式写法

推荐:

if (!error) {
    return success;
}

不推荐:

if (!error)
    return success;

和:

if (!error) return success;
  • 变量和常量比较
    推荐:
[myValue isEqual:@42];

不推荐:

[@42 isEqual:myValue];
  • nil和BOOL值检查
    推荐:
if (someObject) { ...
if (![someObject boolValue]) { ...
if (!someObject) { ...

不推荐:

if (someObject == YES) { ... // Wrong
if (myRawValue == YES) { ... // Never do this.
if ([someObject boolValue] == NO) { ...
  • 避免嵌套if语句,合理使用return可以避免增加代码复杂度,提高代码可读性。

推荐:

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

不推荐:

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

2.三元运算符

三元运算符 ? 应该只用在它能让代码更加清楚的地方。 一个条件语句的所有的变量应该是已经被求值了的。

推荐:

result = a > b ? x : y;

不推荐:

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

当三元运算符的第二个参数(if 分支)返回和条件语句中已经检查的对象一样的对象的时候,下面的表达方式更灵巧:

推荐:

result = object ? : [self createObject];

不推荐:

result = object ? object : [self createObject];

3.Case语句

除非编译器强制要求,括号在 case 语句里面是不必要的。但是当一个 case 包含了多行语句的时候,需要加上括号。

switch (condition) {
    case 1:
        // ...
        break;
    case 2: {
        // ...
        // 多行语句需要加上括号
        break;
       }
    case 3:
        // ...
        break;
    default:
        // ...
        break;
}

有时候可以使用 fall-through 在不同的 case 里面执行同一段代码。一个 fall-through 是指移除 case 语句的 “break” 然后让下面的 case 继续执行。

switch (condition) {
    case 1:
    case 2:
        // code executed for values 1 and 2
        break;
    default:
        // ...
        break;
}

当在 switch 语句里面使用一个可枚举的变量的时候,default 是不必要的。比如:

switch (menuType) {
    case ZOCEnumNone:
        // ...
        break;
    case ZOCEnumValue1:
        // ...
        break;
    case ZOCEnumValue2:
        // ...
        break;
}

4.函数

  • 单一原则

    一个函数只做一件事,每个函数的职责都应该划分的很明确。

    推荐:

dataConfiguration()
viewConfiguration()

不推荐:

void dataConfiguration()
{   
   ...
   viewConfiguration()
}
  • 需要对参数的正确性和有效性进行检查
  • 对相同功能进行封装
  • 将函数内部比较复杂的逻辑提取出来作为单独的函数

三.命名规范

1.统一要求

推荐使用长的、描述性的方法和变量名。尽可能遵守Apple命名约定

推荐:

UIButton *settingsButton;

不推荐:

UIButton *setBut;

2.类名

大驼峰式命名:每个单词的首字母都采用大写字母

例如:MainViewController

3.私有变量

  • 小驼峰式命名:第一个单词以小写字母开始,后面的单词的首字母大写。
  • 私有变量:以下划线开头 例如:NSString *_peopleName

4.属性

  • 小驼峰式命名:第一个单词以小写字母开始,后面的单词的首字母大写。
  • 关键字顺序:原子性、读写权限、内存管理
  • Block、NSString属性使用copy修饰

5.常量

  • 常量应该以驼峰法命名,并以相关类名作为前缀。

    推荐:

static const NSTimeInterval VAReportViewControllerFadeOutTime = 0.4;

不推荐:

static const NSTimeInterval fadeOutTime = 0.4;
  • 推荐使用常量来代替字符串字面值和数字,这样能够方便复用,而且可以快速修改而不需要查找和替换。常量应该用 static声明为静态常量,而不要用 #define,除非它明确的作为一个宏来使用。
    推荐:
static const NSTimeInterval VAReportViewControllerFadeOutTime = 0.4;
static NSString * const VAReportViewControllerCellIdentifier = @"VAReportViewControllerCell";

不推荐:

#define VAReportViewControllerFadeOutTime 0.4
#define VAReportViewControllerCellIdentifier @"VAReportViewControllerCell"

对于外部可见的常量,在头文件中以这样的形式暴露给外部:

extern NSString *const VAReportViewControllerCellIdentifier;

并在实现文件中为其赋值。

只有公有的常量才需要添加命名空间作为前缀。尽管实现文件中私有常量的命名可以遵循另外一种模式,你仍旧可以遵循这个规则。

6.枚举

  • 命名规则和类的命名一致。
  • 枚举内容的命名需要以该枚举类型名称开头。
    NS_ENUM 定义通用枚举 NS_OPTIONS 定义位移枚举

例如:

typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
    UIViewAnimationTransitionNone,
    UIViewAnimationTransitionFlipFromLeft,
    UIViewAnimationTransitionFlipFromRight,
    UIViewAnimationTransitionCurlUp,
    UIViewAnimationTransitionCurlDown,
};
typedef NS_OPTIONS(NSUInteger, UIControlState) {
    UIControlStateNormal       = 0,
    UIControlStateHighlighted  = 1 << 0,
    UIControlStateDisabled     = 1 << 1,
    UIControlStateSelected     = 1 << 2        
};

7.指定初始化方法和间接初始化方法

Objective-C 有指定初始化方法(Designated Initializer)和间接(Secondary Initializer)初始化方法的观念。designated 初始化方法是提供所有的参数,secondary 初始化方法是一个或多个,并且提供一个或者更多的默认参数来调用 designated 初始化的初始化方法。

  • 在你希望提供你自己的初始化函数的时候,需要遵循下列原则:

    1. 定义你的 Designated Initializer,确保调用了直接父类的Designated Initializer。
    2. 重写直接父类的Designated Initializer。调用你的新的Designated Initializer。
    3. 为新的Designated Initializer 进行文档注释。

    正确的例子:

#import 
@interface VAMessageTableViewCell : UITableViewCell
- (__kindof VAMessageTableViewCell *)initWithName:(NSString *)name date:(NSDate *)date identifier:(NSString *)identifier VA_DESIGNATED_INITIALIZER;
- (__kindof VAMessageTableViewCell *)initWithName:(NSString *)name identifier:(NSString *)identifier;
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier VA_UNAVAILABLE_INITIALIZER;
- (instancetype)initWithCoder:(NSCoder *)aDecoder VA_UNAVAILABLE_INITIALIZER;
- (instancetype)init VA_UNAVAILABLE_INITIALIZER;
@end
    
#import "VAMessageTableViewCell.h"
@interface VAMessageTableViewCell ()
@property (nonatomic,copy) NSString *name;
@property (nonatomic,strong) NSDate *date;
@end

@implementation VAMessageTableViewCell
- (VAMessageTableViewCell *)initWithName:(NSString *)name date:(NSDate *)date identifier:(NSString *)identifier {
    //调用直接父类的designated initializer
    self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
    if (self) {
        _name = name;
        _date = date;
    }
    return self;
}

//Secondary Initializer
- (VAMessageTableViewCell *)initWithName:(NSString *)name identifier:(NSString *)identifier {
    return [self initWithName:name date:nil identifier:identifier];
}

//重写直接父类的Designated Initializer
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    return [self initWithName:nil date:nil identifier:reuseIdentifier];
}

//重写直接父类的Designated Initializer
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    return [self initWithName:nil date:nil identifier:nil];
}
@end

其中VA_DESIGNATED_INITIALIZERVA_UNAVAILABLE_INITIALIZER定义如下:

#define VA_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
#define VA_UNAVAILABLE_INITIALIZER __attribute__((unavailable("please use designated initializer")))

相关宏介绍:

__attribute__((objc_designated_initializer)):用来修饰类的designated initializer初始化方法,如果修饰的方法里没有调用父类的 designated initializer,编译器会发出警告。

__attribute__((unavailable)):可以用来修饰变量,方法,类和协议,表明不可用,如果使用,编译器会发出错误。同deprecated,可以添加说明。

8.方法

推荐:

- (__kindof People *)initWithName:(NSString *)name age:(NSInteger)age birthday:(NSDate *)birthday;

不推荐:

-(instancetype)initWithName:(NSString *)name andAge:(NSInteger)age andBirthday:(NSDate *)birthday;

建议所有返回类的实例的类方法和实例方法使用__kindof ,不要使用id或者instancetype,原因如下:

  • id修饰

    1. 用id修饰返回值类型,不会在编译时进行类型判断。
    2. 返回值类型没有确切提示。
  • instancetype修饰

    虽然会自动识别当前对象的类,但是仍然没有类型提示。

9.通知

当你定义你自己的 NSNotification 的时候你应该把你的通知的名字定义为一个字符串常量,就像你暴露给其他类的其他字符串常量一样。你应该在公开的接口文件中将其声明为 extern 的, 并且在对应的实现文件里面定义。

因为你在头文件中暴露了符号,所以你应该按照统一的命名空间前缀法则,用类名前缀作为这个通知名字的前缀。

同时,用一个 Did/Will 这样的动词以及用 "Notifications" 后缀来命名这个通知也是一个好的实践。

// Foo.h
extern NSString * const ZOCFooDidBecomeBarNotification

// Foo.m
NSString * const ZOCFooDidBecomeBarNotification = @"ZOCFooDidBecomeBarNotification";

四.注释规范

优秀的代码大部分是可以自描述的,我们完全可以用代码本身来表达它到底在干什么,而不需要注释的辅助。

但并不是说一定不能写注释,有以下三种情况比较适合写注释:

  • 公共接口(注释要告诉阅读代码的人,当前类能实现什么功能)
  • 涉及到比较深层专业知识的代码(注释要体现出实现原理和思想)。
  • 容易产生歧义的代码(但是严格来说,容易让人产生歧义的代码是不允许存在的)。

除了上述这三种情况,如果别人只能依靠注释才能读懂你的代码的时候,就要反思代码出现了什么问题。

1.import注释

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

// Frameworks
#import ;

// Models
#import "NYTUser.h"

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

2.属性注释

使用//注释,在属性之后,用一个空格隔开。

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

3.方法注释

使用Xcode快捷键command+option+/进行注释:

/**
 方法描述

 @param name 参数描述
 @param date 参数描述
 @param identifier 参数描述
 @return 返回值
 */
- (__kindof VAMessageTableViewCell *)initWithName:(NSString *)name date:(NSDate *)date identifier:(NSString *)identifier VA_DESIGNATED_INITIALIZER;

4.代码块注释

单行使用// 多行使用/**/

5.#pragma

  • #pragma mark -是一个在类内部组织代码并且帮助你分组方法实现的好办法。

    分离示范:

#pragma mark - Get

#pragma mark - Set

#pragma mark - Life Cycle
- (void)viewDidLoad {
    [super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
}

#pragma mark - Super Class

#pragma mark - Event Responder

#pragma mark - TableView Delegate DataSource


6.TODO MARK FIXME 标记

    //MARK:标记一下
    //TODO:通知即将要做
    //FIXME:你想要修改的bug

参考资料:
https://github.com/oa414/objc-zen-book-cn/
https://www.jianshu.com/p/21f059f04181
Coding Guidelines for Cocoa

你可能感兴趣的:(IOS编码规范)