IOS编码规范

作者: 一片姜汁 | 来源:发表于2018-08-09 17:32 被阅读19次

    一.总体原则

    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 <UIKit/UIKit.h>
    @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 <QuartzCore>;
    
    // 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编码规范

        本文链接:https://www.haomeiwen.com/subject/fxycbftx.html