Objc风格指南

前言

制定风格指南主要的目的是统一团队的代码风格与样式,提高工作效率与阅读性还有维护性;
这篇文章虽然是OC风格指南,但是有些风格是所有编程语言通用的,另外它还参考了阿里巴巴Java代码规范,想不到吧。

原则

优化阅读体验,而非写代码的体验

    代码库通常具有较长的生命周期,并且花在阅读代码上的时间也远多于编写代码的时间。所以我们应该明确一个目标:去优化别人阅读、维护我们代码时的体验,而不是优化写代码时的体验(例如随便使用缩写进行编码,代码量变少了,但是增加了阅读和维护的难度)。

    适当的规范限制和标准不是消灭代码内容的创造性、优雅性,而是限制过度个性化,以一种普遍认可的方式一起做事,进而提高工作效率,降低沟通成本。提高稳定性。

与上下文尽量保持一致

    尽量保持代码库中的风格样式一致。在整个代码库中始终保持一种风格可以让工程师更专注于其他(更重要的)问题。一致性还可以实现更好的自动化。

    如果风格指南中没有说明,那么就按照代码库之前的风格样式或者参考 Apple SDK 的风格样式。

命名风格

通用命名风格

  • [必须] 清晰和简洁(好的名称应该是能自我描述的),简洁的原则是为了保持更好的清晰度和可读性,记住 不要本末倒置

    正例:
    // 移除一个对象。
    1. removeObject:
    // 使用指定字符串替换出现过的字符串。
    2. stringByReplacingOccurrencesOfString: withString:

    反例:
    // 要移除什么?
    1. remove:
    // 要将什么替换成什么?是全部都替换还是只替换第一个匹配的位置?
    2. replace("1", "2")
  • [必须] 一致性:命名应该和上下文乃至全局保持一致性,相同类型或者具有相同作用方法的名称应该相同或类似。

    正例:
    1. NSDictionary、NSArray、NSSet这些类中名叫 count 的方法所体现的作用都是一样的。
    2. 系统的一些代理方法和通知的名称也会刻意保持一致,
    例如 `UIApplicationDelegate`协议中的 `applicationDidBecomeActive` 方法名
    就和 `UIApplicationDidBecomeActiveNotification`通知的名称是一致的。
  • [必须] 禁止自我指涉:命名不要自我指涉(通知、掩码或常量等除外)。

    正例:
    NSString

    反例:
    NSStringObject
  • [必须] 禁止过度缩写和自创缩写,一些通用缩写名除外(例如ATM、GPS、max、min等),具体请参考 可接受的缩写词列表

    Tips:
    阅读者可能来自不同的地方接受不同的教育和不同的文化,
    他们不一定会明白你写的缩写的意思。

    正例:
    setBackgroundColor

    反例:
    setBgColor
  • [必须] 禁止使用无意义的拼音,国际通用名、地名、人名除外。

    正例:
    Beijing、Alibaba

    反例:
    // 打折
    DaZhePromotion
  • [必须] 代码和注释中避免使用任何语言的种族歧视性词语。

    正例:
    secondary、main

    反例:
    slave、master
  • [必须] 禁止以new、alloc、copy、mutableCopy等关键字作为名称的开始部分。

  • [必须] 由于 OC 没有命名空间的概念,所以全局名称(类名、协议名、全局常量名、全局变量名、函数名、typedef名称)必须添加前缀,前缀由3个及以上字符组成且全部大写。

    Tips: 
    由于系统保留任意两个字符作为前缀的使用权(NS、UI、CG、CF、CA、WK、MK、CI、NC等等),
    为了避免和系统命名冲突,所以前缀至少由3个字符组成。

    正例:
    ZTYLoginViewController

    反例:
    ZTLoginViewController
  • [必须] 如果某个全局名称(例如函数名、通知名、协议方法名)和某个类有所关联,那么请使用相关类名作为其前缀,否则请使用通用前缀。

    正例:
    UIApplicationDidBecomeActiveNotification
    MAGUserLoginSuccessNotification
  • [必须] 命名风格统一使用 驼峰命名方式 ,局部变量名等特殊名称可以不遵守。

    正例:
    totalRemain

    反例:
    total_remain
  • [必须] 成员变量名称必须以_作为开始部分。

    正例:
    _nameString

    反例:
    nameString
  • [建议] 在给常量或变量命名时,请将表示类型的名词放在词尾,以提高辨识度。

    正例:
    nameLabel、nameString

    反例:
    name
  • [建议] 如果模块、接口、类、方法使用了某种模式,在命名时尽量体现出具体模式。

    正例:
    OrderFactory、LoginProxy
  • [建议] 建议给临时变量名称添加前缀,以提高辨识度(特殊情况除外,例如for循环里面的i)。

    正例:
    // t表示temp
    t_label

    反例:
    label

类名风格

  • [必须] 一个完整的类名由 前缀+名称+类型 3个部分组成。

    正例:
    MAGLoginViewControler

    前缀:MAG
    名称:Login
    类型:ViewControler

分类命名风格

  • [必须] 分类命名风格和类名类似,由 前缀+名称 2个部分组成。

    正例:
    UIView (MAGAdd)

    反例:
    UIView (Add)
  • [必须] 分类中的方法名必须要添加前缀,防止覆盖系统或3方库的私有方法,前缀需要保持唯一性(例如mp_)。

    正例:
    mp_substringFromString

    反例:
    substringFromString
  • [建议] 分类中建议不要声明属性,尽量挪到主类中声明。

    Tips: 
    尽管从技术上来讲可以在分类中声明属性,但是这么做需要格外小心,
    因为它很容易出现内存上或其他一些问题,而且出现问题很难排查。
  • [建议] 如果一个类比较复杂,建议使用分类重新组织结构和代码(可以参考系统的UIView)。

枚举命名风格

  • [必须] 枚举名称的前缀应该和 typedef 的名称保持一致。

    正例:
    typedef NS_ENUM(NSInteger, MGradientChangeDirection) {
    MGradientChangeDirectionLevel,
    };

    反例:
    typedef NS_ENUM(NSInteger, MGradientChangeDirection) {
    level,
    };

方法命名风格

  • [必须] 方法名称的开头一般以小写字母开始,特殊单词除外(例如HTTP、URL)。

  • [必须] 方法名称禁止直接使用_作为开始部分。

    Tips: 
    由于系统的私有方法通常以_作为开始部分,这么做可以避免不小心覆盖系统私有方法。
  • [必须] 私有方法必须添加前缀,前缀需要保持唯一性(例如mp_)。

    Tips: 
    给私有方法添加前缀有如下好处:
    1. 提高辨识度,提高代码可读性。
    2. 避免不小心覆盖系统或框架的私有方法。
  • [必须] 如果方法返回某个属性值,那么请直接使用属性名作为方法名。

    正例:
    - (CGSize)cellSize;

    反例:
    - (CGSize)getCellSize;
  • [必须] 方法的每个参数前必须添加有效关键字。

    正例:
    - (void)sendAction:(SEL)aSelector
    toObject:(id)anObject
    forAllCells:(BOOL)flag;

    反例:
    - (void)sendAction:(SEL)aSelector
    :(id)anObject
    :(BOOL)flag;
  • [必须] 如果某个方法是由通知触发的,那么请使用 Notification 关键字作为名称后缀。

    正例:
    appDidBecomeActiveNotification

    反例:
    appDidBecomeActive
  • [建议] 尽量不要使用“and”连接接收者属性,尽管and读起来还算顺口,但随着你创建的方法参数的增加,这将会带来一系列的问题。

    正例:
    - (int)runModalForDirectory:(NSString *)path
    file:(NSString *)name
    types:(NSArray *)fileTypes;

    反例:
    - (int)runModalForDirectory:(NSString *)path
    andFile:(NSString *)name
    andTypes:(NSArray *)fileTypes;
  • [建议] 如果方法描述了两个独立的动作,则可以使用 and 连接。

    正例:
    - (BOOL)openFile:(NSString *)fullPath
    withApplication:(NSString *)appName
    andDeactivate:(BOOL)flag;
  • [建议] 尽量不要使用 get 作为方法名称的开始部分,除非这个方法间接返回对象或值。

    正例:
    - (UIColor *)backgroundColor;
    - (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase;

    反例:
    - (UIColor *)getBackgroundColor;

协议命名风格

  • [必须] 协议中的方法名以触发消息的对象名开头,省略类名前缀并首字母小写,如果它没有关联任何类则可以忽略这个规则。

    正例:
    - (BOOL)tableView:(NSTableView *)tableView
    shouldSelectRow:(int)row;
  • [必须] 除非协议方法只有一个参数,否则冒号需紧跟在类名后面。

    正例:
    1. - (BOOL)tableView:(NSTableView *)tableView
    shouldSelectRow:(int)row;

    2. - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;

通知命名风格

  • [必须] 一个完整的通知名称由 关联的类名/通用前缀+名称+Notification 3个部分组成。

    正例:
    UIApplicationDidBecomeActiveNotification

    关联的类:UIApplication
    通知名称:DidBecomeActive
    固定后缀:Notification

常量命名风格

  • [必须] 如果常量局限于某个 编译单元(通常指某个类的实现文件内) 之内,通常在前面加小写字母k作为前缀,若常量在全局可见,通常以类名作为前缀,然后采用首字母大写驼峰命名方式。

    正例:
    // 局部可见
    const CGFloat kAnimationDuration = 2.0;
    // 全局可见
    const CGFloat UIActivityIndicatorViewAnimationDuration = 2.0;
  • [必须] const如果修饰的是基本数据类型,则放在最左侧,如果修饰的是对象,则放在变量名前面。

    正例:
    const NSInteger age
    NSString * const name

异常类命名风格

  • [必须] 一个完整的异常类名称由 前缀+名称+Exception 3个部分组成。

文件命名风格

  • [必须] 文件名全部小写,使用_连接不同的模块,模块中的单词可以使用驼峰命名。

  • [必须] 文件名称由 所属模块+描述 2个部分组成。

    正例:
    public_back@2x.png

注释风格

  • [必须] 注释请遵守 Doxygen 风格。

    一个好的例子:
    /**
    * @brief 关于这个方法的一个简短说明。
    * @discussion 一段详细的描述。
    * @warning 一些警告信息。
    * @note 描述一些需要注意的事情。
    * @param obj1 参数1的说明。
    * @return 返回值的说明。
    * @code
    * 示例代码:
    * id temp = [self testFunction:@"111"];
    * @endcode
    *
    * @par 分割线,上面是一些常用的注释语法,下面是不常用的注释语法。
    *
    * @todo 即将要做的事情。
    * @bug 可能存在的BUG,或者对缺陷的说明。
    * @since 说明从什么版本、什么时间加入此代码。
    * @exception 对可能存在的异常的解释。
    * @pre 用来说明执行方法所需要的前提条件。
    * @post 用来说明执行方法之后的使用条件。
    * @author 作者名称。
    * @remark 一些评论信息。
    * @copyright 版权信息。
    * @version 当前的工程版本。
    * @par 一个新的名称
    *
    * 开始一个新段落。
    */
    - (id)testFunction:(NSString *)obj1 {
    return nil;
    }

    效果图
    图片

  • [必须] 注释的作用是用于解释那些复杂不容易理解的逻辑,以及需要注意的地方,而不是告诉别人这个方法的作用,作用应该在名称中体现出来,提供一个合理的名称比使用晦涩的名称然后试图通过注释来解释它们要好的多。

  • [必须] 如果修改了实现细节,请记得修改注释。

  • [必须] 注释不要写的太冗长或太简短,这样都不利于别人快速理解。

  • [必须] 注释的双斜线和内容之间有且仅有一个空格。

    正例:
    // 这是示例注释,请注意在双斜线后有一个空格。
  • [必须] 注释中的语句也需要添加适量的标点符号和空格帮助别人理解。

  • [必须] 对于代码注释需要谨慎,代码被注释一般有2种可能,1. 后续会恢复此段代码逻辑; 2. 永久不用;对于第1种情况需添加相应注释,如果没有注释信息难以知晓注释动机,后者建议直接删除。如果有需要可以通过代码仓库查阅历史代码。

  • [必须] 如果某个方法是有问题的,可以使用特殊注释来提醒别人和自己。

    正例:
    // MARK: - 方法集
    // TODO: 等待实现
    // FIXME: 有bug,需要修改
    // !!!: 逻辑混乱,需要完善
    // ???: 具体干什么用的?
    - (void)testFunction;
  • [必须] 行尾注解和代码保持2个空格,如果后续行有多个注释,将它们排列起来通常会更具可读性。

    正例:
    [self doSomethingWithALongName]; // Two spaces before the comment.
    [self doSomethingShort]; // More spacing to align the comment.
  • [建议] 别给糟糕的代码加注释,重构它。

    Tips: 
    注释不能美化糟糕的代码。当企图使用注释前,先考虑是否可以通过调整结构,命名等操作,消除写注释的必要。
  • [建议] 如果一个类比较复杂或者有需要注意的地方,那么请在声明它的地方加上注释帮助别人快速理解,如果有必要可以使用 Monodraw 工具绘制ASCII图形提高可读性,如下图所示。

    monodraw示例图(这张图很清楚的向别人表达这个类的作用)
    monodraw示例图

编码风格

  • [必须] 不要增加多余空格来使上下代码的等号对齐。

    正例:
    int a1 = 1;
    long a2 = 3;
    NSString *a3 = @"";

    反例:
    int a1 = 1;
    long a2 = 3;
    NSString *a3 = @"";
  • [必须] 逗号(,)后面、二元运算符的左右应该添加1个空格,小括号左右不要有空格。

    正例:
    kColorRGB(255, 255, 255);
    int a = 3 + 4;

    反例:
    kColorRGB(255,255,255)
    int a = 3+4;
  • [建议] 尽量使用 if return 代替 if else,if 嵌套最好不超过5层。

    正例:
    if (x == 1) {
    ……
    return;
    }

    if (x == 2) {
    ……
    return;
    }

    反例:
    if (x == 1) {
    ……
    } else if (x == 2) {
    ……
    }
  • [建议] 尽量避免采用取反逻辑运算符,因为取反逻辑不利于快速理解。

    正例:
    if (array == nil) {
    ……
    }
    反例:
    if (!array) {
    ……
    }

结构风格

  • [必须] 文件内部使用的常量、静态变量在@interface之前声明,如果没有@interface就在@implementation前声明。

  • [必须] 同一类型的属性声明放在一块显示,中间用一行空格区分,不同类型的声明用2行空格隔开,属性声明的开始和末尾都要添加一行空格。

    正例:
    @interface MineViewController ()

    @property (nonatomic, weak) UIView *headView;

    @property (nonatomic, weak) UITableView *tableView;


    @property (nonatomic, copy) NSArray *dataSourceArray;

    @end
  • [必须] 不同作用的方法按照顺序进行排序,生命周期相关的方法 > 公开方法 > 私有方法 > 继承方法 > 通知方法 > 协议方法 > getter/setter方法,简单的说就是越重要越常用的方法越靠前。

    #pragma mark - LifeCycle(生命周期相关的代码放在最上面)
    - (void)dealloc {}

    - (void)viewDidLoad {}

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


    #pragma mark - Public(公开方法)
    // code...
    // 下空两行


    #pragma mark - Private(私有方法)


    #pragma mark - Override(需要覆盖父类的方法)


    #pragma mark - Notification(通知方法)


    #pragma mark - Delegate(Delegate需要实现的方法)


    #pragma mark - Getter/Setter
  • [必须] 方法的声明顺序应该为类方法 > 初始化方法 > 实例方法。

  • [建议] 不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开以提升可读性。

    正例:
    [self createSubviews];
    [self createTableview];

    [self netRequest];
  • [建议] 头文件包含顺序应该是系统头文件 > SDK头文件 > 其他依赖的头文件,用一个空行分隔逻辑上不同的头文件,在每个组中,包含的内容建议按首字母顺序排列。

    正例:
    #import "ProjectX/BazViewController.h"

    #import <Foundation/Foundation.h>

    #include <unistd.h>
    #include <vector>

    #include "base/basictypes.h"
    #include "base/integral_types.h"
    #include "util/math/mathutil.h"

    #import "ProjectX/BazModel.h"
    #import "Shared/Util/Foo.h"

一个好的头文件例子:

#import <UIKit/UIKit.h>

#import "WXYZ_ADDefine.h"

@class WXYZ_ADModel;

NS_ASSUME_NONNULL_BEGIN

/// 提供给外部使用的广告实例对象,所有需要使用广告的地方都应该使用 WXYZ_ADView 创建。
@interface WXYZ_ADView : UIView

/// 广告相关属性,key表示广告标题,value表示广告宽度。
@property(nonatomic, copy) NSDictionary<NSString *, NSNumber *> *attributes;


/// 根据广告类型和广告位置返回是否一个布尔值,表示是否需要展示广告。
+ (BOOL)canLoadADWithADType:(WXYZ_ADViewType)adType adPosition:(WXYZ_ADViewPosition)adPosition;

/// 根据广告类型和广告位置创建并返回一个adView对象。
/// @discussion 如果广告开关是关闭状态则返回nil。
/// @param adType 广告类型
/// @param adPosition 广告位置
+ (nullable instancetype)createADViewWithType:(WXYZ_ADViewType)adType adPosition:(WXYZ_ADViewPosition)adPosition;

/// 设置adModel将会更新正在显示的广告内容。
- (void)setAdModel:(WXYZ_ADModel *)adModel;

+ (instancetype)allocWithZone:(struct _NSZone *)zone UNAVAILABLE_ATTRIBUTE;

+ (instancetype)new UNAVAILABLE_ATTRIBUTE;

+ (instancetype)alloc UNAVAILABLE_ATTRIBUTE;

- (instancetype)initWithFrame:(CGRect)frame UNAVAILABLE_ATTRIBUTE;

- (instancetype)init UNAVAILABLE_ATTRIBUTE;

@end

NS_ASSUME_NONNULL_END
  1. 使用 @class 向前声明 WXYZ_ADModel而不是用 #import包含。
  2. @interface 之前有关于类的说明注释,帮助别人在使用这个类时快速了解这个类的作用以及需要注意的地方。
  3. 字典使用了泛型声明字典包含的类型,并且在注释中详细的说明了字典包含的内容。
  4. 属性和第一个方法之间有2个空格来区分它们没有关联性。
  5. 每个属性和返回值都有是否为空的状态,例如 + createADViewWithType 方法使用 nullable 表明了返回值可能为空的状态,并且在注释中说明了什么情况下会返回空对象。
  6. 遵守了类方法 > 初始化方法 > 实例方法的规则。

结语

  1. 可以转载,但是请注明来源。

参考

  1. Cocoa编码规范
  2. 阿里巴巴Java代码规范
  3. Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法
  4. Google的Objective-C风格指南
  5. 网上发布的有关代码规范的文章
文章作者: 布多
文章链接: https://internetwei.github.io/2021/08/07/Objc风格指南/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 布多的博客