OC代码规范

作者: 浪人残风 | 来源:发表于2021-02-26 15:48 被阅读0次

    Version: 0.01

    本文部份章节摘自《苹果 Cocoa 编码规范》(即Apple’s Cocoa Coding Guidelines)、《Google的Objective-C编码规范》(即Google Objective-C Style Guide)。

    • 命名
      * 一般性原则
      * 清晰性
      * 一致性
      * 不要自我指涉
      * 前缀
      * 书写约定
      * 类与协议命名
      * 头文件
    • 方法命名
      * 一般性规则
      * 访问方法
      * 委托方法
      * 集合方法
      * 方法参数
      * 私有方法
      * 函数命名
    • 实例变量与数据类型命名
      * 实例变量
      * 枚举常量
      * const 常量
      * 字符串常量
      * 其他常量
    • 异常与通知
      * 异常
      * 通知
    • 可接受的缩略名
      * 常见的缩写
      * 常见的略写
    • 编码
      * 花括号的使用
      * 空格的使用
      * 空行的使用
      * 缩进的使用
      * 折行的使用
    • 注释
      * 文档注释
      * 代码注释
    • 约定俗成的习惯
      * 成员变量定义
      * 属性定义
      * 比较操作

    命名

    在面向对象软件库的设计过程中,开发人员经常忽视对类、方法、函数、常量以及其他编程接口元素的命名。本节讨论大多数 Cocoa 接口的一些命名约定。

    一般性原则

    清晰性

    • 最好是既清晰又简短,但不要为简短而丧失清晰性;
    代码 点评
    insertObject:atIndex: good
    insert:at: 不清晰;要插入什么?“at”表示什么?
    removeObjectAtIndex: good
    removeObject: 这样也不错,可以针对特定的对象进行移除。
    remove 不清晰;要移除什么?
    • 名称通常不使用缩写,即使名称很长,也要拼写完全;
    代码 点评
    destinationSelection good
    destSel 不清晰
    setBackgroundColor: good
    setBkgdColor: 不清晰

    你可能会认为某个缩写广为人知,但有可能并非如此,尤其是当你的代码被来自不同文化和语言背景的开

    发人员所使用时。

    • 然而,你可以使用少数非常常见,历史悠久的缩写。请参考:“可接受的缩略名”一节;

    • 避免使用有歧义的 API 名称,如那些能被理解成多种意思的方法名称。

    代码 点评
    sendPort 是发送端口还是返回一个发送端口?
    displayName 是显示名称还是返回用于展示的名称?

    一致性

    • 尽可能使用与 Cocoa 编程接口命名保持一致的名称。如果你不太确定某个命名是否一致,可以浏览一下头文件或参考文档中的范例;

    • 在使用多态方法的类中,命名的一致性非常重要。在不同类中实现相同功能的方法应该具有相同的名称。

    代码 点评
    - (int)tag 在 NSView, NSCell, NSControl 中有定义
    - (void)setStringValue:(NSString *) 在 Cocoa 的许多类中有定义

    请参考“方法参数”一节。

    不要自我指涉

    • 不要自我指涉;
    代码 点评
    NSString okey
    NSStringObject 自我指涉
    • 掩码(可使用位操作进行组合)和用作通知名称的常量受该约定限制。
    代码 点评
    NSUnderlineByWordMask okey
    NSTableViewColumnDidMoveNotification okey

    前缀

    前缀是名称的重要组成部分。它们可以区分软件的功能范畴。通常,软件会被打包成一个框架或多个紧密相关的框架(如 Foundation 和 Application Kit 框架)。前缀可以防止第三方开发者与苹果公司之间的命名冲

    突(同样也可防止苹果内部不同框架之间的命名冲突)。

    • 前缀有规定的格式。它由两到三个大写字符组成,不能使用下划线或子前缀;
    前缀 Cocoa 框架
    NS Foundation
    NS Application Kit
    AB Address Book
    IB Interface Builder
    • 命名 class, protocol, structure, function, constant, enumeration 时使用前缀;

    • 命名 method 时不使用前缀,因为方法已经在它所在类的命名空间中;同理,命名结构体成员时也不使用前缀。

    书写约定

    在为 API 元素命名时,请遵循如下一些简单的书写约定:

    • 对于包含多个单词的名称,不要使用标点符号作为名称的一部分或作为分隔符(下划线,破折号等);
      此外,大写每个单词的首字符并将这些单词连续拼写在一起。请注意以下限制:

    • 方法名小写第一个单词的首字符,大写后续所有单词的首字符。方法名不使用前缀,如: fileExistsAtPath:isDirectory: ;
      如果方法名以一个广为人知的大写首字母缩略词开头,该规则不适用,如: NSImage 中的 TIFFRepresentation ;

    • 函数名和常量名使用与其关联类相同的前缀,并且要大写前缀后面所有单词的首字符,如: NSRunAlertPanel, NSCellDisabled ;

    • 避免使用下划线来表示名称的私有含义。苹果公司保留该方式的使用。如果第三方这样使用可能会导致命名冲突,他们可能会在无意中用自己的方法覆盖掉已有的私有方法,这会导致严重的后果。请参考“私有方法”一节以了解私有 API 的命名约定。

    类与协议命名

    类名应包含一个明确描述该类(或类的对象)是什么或做什么的名词。类名要有合适的前缀(请参考“前缀”一节)。 Foundation 及 Application Kit 有很多这样例子,如: NSString, NSData, NSScanner, NSApplication, NSButton 以及 NSEvent 。

    协议应该根据对方法的行为进行分组的方式来命名。

    • 大多数协议仅包含一组相关的方法,而不关联任何类,这种协议的命名应该使用动名词(ing),以避免不与类名混淆;
    代码 点评
    NSLocking good
    NSLock 糟糕,它看起来像类名
    • 有些协议包含一些彼此无关的方法(这样做是避免创建多个独立的小协议)。这样的协议倾向于与某个类关联在一 起,该类是协议的主要体现者。在这种情况下,我们约定协议的名称与该类同名。 NSObject 协议就是这样一个例子。这个协议组合一组彼此无关的方法,有用于查询对象在其类层次中位置的方法,有使之能调用特殊方法的方法以及用于增减引用计数的方 法。由于 NSObject 是这些方法的主要体现者,所以我们用类的名称命名这个协议。

    头文件

    头文件的命名方式很重要,我们可以根据其命名知晓头文件的内容。

    • 声明孤立的类或协议:将孤立的类或协议声明放置在单独的头文件中,该头文件名称与类或协议同名;
    头文件 声明
    NSApplication.h NSApplication 类
    • 声明相关联的类或协议:将相关联的声明(类、类别及协议)放置在一个头文件中,该头文件名称与主要的类/类别/协议的名字相同;
    头文件 声明
    NSString.h NSString 和 NSMutableString 类
    NSLock.h NSLocking 协议和 NSLock, NSConditionLock, NSRecursiveLock 类
    • 包含框架头文件:每个框架应该包含一个与框架同名的头文件,该头文件包含该框架所有公开的头文件;
    头文件 框架
    Foundation.h Foundation.framework
    • 为已有框架中的某个类扩展 API :如果要在一个框架中声明属于另一个框架某个类的范畴类的方法, 该头文件的命名形式为:原类名+“Additions”。如 Application Kit 中的 NSBundleAdditions.h ;

    • 相关联的函数与数据类型:将相联的函数、常量、结构体以及其他数据类型放置到一个头文件中,并以合适的名字命名。如 Application Kit 中的 NSGraphics.h 。

    方法命名

    一般性规则

    为方法命名时,请考虑如下一些一般性规则:

    • 小写第一个单词的首字符,大写随后单词的首字符,不使用前缀。请参考“书写约定”一节。有两种例外情况:

      1. 方法名以广为人知的大写字母缩略词(如: TIFF or PDF )开头;
      2. 私有方法可以使用统一的前缀来分组和辨识,请参考“私有方法”一节。
    • 表示对象行为的方法,名称以动词开头:

      - (void)invokeWithTarget:(id)target:
      - (void)selectTabViewItem:(NSTableViewItem *)tableViewItem
      
      

      名称中不要出现 do 或 does,因为这些助动词没什么实际意义。也不要在动词前使用副词或形容词修饰。

    • 如果方法返回对象的某个属性,直接用属性名称命名。不要使用 get,除非是间接返回一个或多个值。请参考“访问方法”一节;

    代码 点评
    - (NSSize)cellSize;
    - (NSSize)calcCellSize;
    - (NSSize)getCellSize;
    • 参数要用描述该参数的关键字命名;
    代码 点评
    - (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;
    - (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;
    • 参数前面的单词要能描述该参数;
    代码 点评
    - (id)viewWithTag:(int)aTag;
    - (id)taggedView:(int)aTag;
    • 细化基类中的已有方法:创建一个新方法,其名称是在被细化方法名称后面追加参数关键词;
    代码 所在类
    - (id)initWithFrame:(NSRect)frameRect; NSView
    - (id)initWithFrame:(NSRect)frameRect mode:(ind)aMode cellClass:(Class)factoryId numberOfRows:(int)rowsHigh numberOfColumns:(int)colsWide; NSMatrix - NSView的子类
    • 不要使用 and 来连接各个参数;
    代码 点评
    - (int)runModalForDirectory:(NSString *)path file:(NSString *)name types:(NSArray *)fileTypes;
    - (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;

    虽然上面的例子中使用 and 看起来也不错,但当你的方法有许多参数时,就会显得有些繁琐累赘不够简洁。

    • 如果方法描述两种独立的行为,使用 and 来串接它们。
    代码 点评
    - (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag; NSWorkspace

    访问方法

    访问方法是对象属性的读取与设置方法。其命名有特定的格式,依赖于属性描述的内容。

    • 如果属性是用名词描述的,则命名格式为:

      - (void)setNoun:(type)aNoun;
      - (type)noun;
      
      

      例如:

      - (void)setgColor:(NSColor *)aColor;
      - (NSColor *)color;
      
      
    • 如果属性是用形容词描述的,则命名格式为:

      - (void)setAdjective:(BOOL)flag;
      - (BOOL)isAdjective;
      
      

      例如:

      - (void)setEditable:(BOOL)flag;
      - (BOOL)isEditable;
      
      
    • 如果属性是用动词描述的,则命名格式为:(动词要用现在时时态)

      - (void)setVerbObject:(BOOL)flag;
      - (BOOL)verbObject;
      
      

      例如:

      - (void)setShowAlpha:(BOOL)flag;
      - (BOOL)showsAlpha;
      
      
    • 不要使用动词的过去分词形式作形容词使用;

    代码 点评
    - (void)setAcceptsGlyphInfo:(BOOL)flag;
    - (BOOL)acceptsGlyphInfo;
    - (void)setGlyphInfoAccepted:(BOOL)flag;
    - (BOOL)glyphInfoAccepted;
    • 可以使用情态动词(can/should/will等)来提高清晰性,但不要使用 do 或 does ;
    代码 点评
    - (void)setCanHide:(BOOL)flag;
    - (BOOL)canHide;
    - (void)setShouldCloseDocument:(BOOL)flag;
    - (void)shouldCloseDocument;
    - (void)setDoseAcceptGlyphInfo:(BOOL)flag;
    - (BOOL)doseAcceptGlyphInfo;
    • 只有在方法需要间接返回多个值的情况下,才使用 get ;
    代码 点评
    - (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase; NSBezierPath

    像上面这样的方法,在其实现里应允许接受 NULL 作为其 in/out 参数,以表示调用者对一个或多个返回值不感兴趣。

    委托方法

    委托方法是那些在特定事件发生时可被对象调用,并声明在对象的委托类中的方法。它们有独特的命名约定,这些命名约定同样也适用于对象的数据源方法。

    • 名称以标示发送消息的对象的类名开头,省略类名的前缀并小写类第一个字符;

      - (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
      - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
      
      
    • 冒号紧跟在类名之后(随后的那个参数表示委派的对象)。该规则不适用于只有一个 sender 参数的方法;

      - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;
      
      
    • 上面的那条规则也不适用于响应通知的方法。在这种情况下,方法的唯一参数表示通知对象;

      - (void)windowDidChangeScreen:(NSNotification *)notification;
      
      
    • 用于通知委托对象,操作即将发生或已经发生的方法名中要使用 did 或 will ;

      - (void)browserDidScroll:(NSBrowser *)sender;
      - (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window;
      
      
    • 用于询问委托对象可否执行某操作的方法名中可使用 did 或 will,但最好使用 should ;

      - (BOOL)windowShouldClose:(id)sender;
      
      

    集合方法

    管理对象(集合中的对象被称之为元素)的集合类,约定要具备如下形式的方法:

    - (void)addElement:(elementType)anObj;
    - (void)removeElement:(elementType)anObj;
    - (NSArray *)elements;
    
    

    例如:

    - (void)addLayoutManager:(NSLayoutManager *)anObj;
    - (void)removeLayoutManager:(NSLayoutManager *)anObj;
    - (NSArray *)layoutManagers;
    
    

    集合方法命名有如下一些限制和约定:

    • 如果集合中的元素无序,返回 NSSet,而不是 NSArray ;

    • 如果将元素插入指定位置的功能很重要,则需具备如下方法:

      - (void)insertElement:(elementType)anObj atIndex:(int)index;
      - (void)removeElementAtIndex:(int)index;
      
      

    集合方法的实现要考虑如下细节:

    • 以上集合类方法通常负责管理元素的所有者关系,在 add 或 insert 的实现代码里会 retain 元素,在 remove 的实现代码中会 release 元素;

    • 当被插入的对象需要持有指向集合对象的指针时,通常使用 set… 来命名其设置该指针的方法,且不要 retain 集合对象。比如上面的 insertLayerManager:atIndex: 这种情形, NSLayoutManager 类提供如下方法来获取管理其的集合对象,并且是由集合对象在添加其时进行调用:

      - (void)setTextStorage:(NSTextStorage *)textStorage;
      - (NSTextStorage *)textStorage;
      
      

    通常你不会直接调用 setTextStorage: ,而是覆写它。

    另一个关于集合约定的例子来自 NSWindow 类:

    - (void)addChildWindow:(NSWindow *)childWin ordered:(NSWindowOrderingMode)place;
    - (void)removeChildWindow:(NSWindow *)childWin;
    - (NSArray *)childWindows;
    - (NSWindow *)parentWindow;
    - (void)setParentWindow:(NSWindow *)window;
    
    

    方法参数

    命名方法参数时要考虑如下规则:

    • 参数名小写第一个单词的首字符,大写后继单词的首字符。如: - (void)removeObject:(id)anObject;
    • 不要在参数名中使用 pointer 或 ptr,让参数的类型来说明它是指针;
    • 避免使用 one, two, … 作为参数名;
    • 避免为节省几个字符而缩写。

    按照 Cocoa 惯例,以下关键字与参数联合使用:

    …action:(SEL)aSelector
    …alignment:(int)mode
    …atIndex:(int)index
    …content:(NSRect)aRect
    …doubleValue:(double)aDouble
    …floatValue:(float)aFloat
    …font:(NSFont *)fontObj
    …frame:(NSRect)frameRect
    …intValue:(int)anInt
    …keyEquivalent:(NSString *)charCode
    …length:(int)numBytes
    …point:(NSPoint)aPoint
    …stringValue:(NSString *)aString
    …tag:(int)anInt
    …target:(id)anObject
    …title:(NSString *)aString
    

    私有方法

    大多数情况下,私有方法命名与公共方法命名约定相同,但通常我们约定给私有方法添加前缀,以便与公共方法区分开来。即使这样,私有方法的名称很容易 导致特别的问题。当你设计一个继承自 Cocoa framework 某个类的子类时,你无法知道你的私有方法是否不小心覆盖了框架中基类的同名方法。

    Cocoa framework 的私有方法名称通常以下划线作为前缀(如: _fooData ),以标示其私有含义。基于这样的事实,遵循以下两条建议:

    • 不要使用下划线作为你自己的私有方法名称的前缀, Apple 保留这种用法;
    • 若要继承 Cocoa framework 中一个超大的类(如: NSView ),并且想要使你的私有方法名称与基类中的区别开来,你可以为你的私有方法名称添加你自己的前缀。这个前缀应该具有唯一性,如基于你 公司的名称,或工程的名称,并以“XX_”形式给出。比如你的工程名为”Byte Flogger”,那么就可以是“BF_addObject:”。

    尽管为私有方法名称添加前缀的建议与前面类中方法命名的约定冲突,但这里的意图与之有所不同:为了防止不小心地覆盖了基类中的私有方法。

    函数命名

    Objective-C 允许通过函数( C 形式的函数)描述行为,就如成员方法一样。

    函数命名应该遵循如下几条规则:

    • 函数命名与成员方法命名相似,但有两点不同:

      1. 它们有前缀,其前缀与你使用的类和常量的前缀相同
      2. 大写前缀后紧跟的第一个单词首字符
    • 大多数函数名称以动词开头,这个动词描述该函数的行为。

      NSHighlightRect
      NSDeallocateObject
      
      

    查询属性的函数有个更多的规则要遵循:

    • 查询第一个参数的属性的函数,省略动词;

      unsigned int NSEventMaskFromType(NSEventType type);
      float NSHeight(NSRect rect);
      
      
    • 返回值为引用的方法,使用 Get ;

      const char *NSGetSizeAndAlignment(const char *typePtr, unsigned int *sizep, unsigned int *alignp);
      
      
    • 返回 boolean 值的函数,名称使用判断动词 is/does 开头。

      BOOL NSDecimalIsNotANumber(const NSDecimal *decimal);
      
      

    实例变量与数据类型命名

    这一节描述实例变量、常量、异常以及通知的命名约定。

    实例变量

    在为类添加实例变量要注意如下几点:

    • 避免创建 @public 的实例变量;
    • 使用 @private, @protected 显式限定实例变量的访问权限;
    • 确保实例变量的名称简明扼要地描述了它所代表的属性。

    如果实例变量设计为可被访问的,确保编写了访问方法。

    枚举常量

    • 使用枚举来定义一组相关的整数常量;

    • 枚举常量与其 typedef 命名遵守函数命名规则。如:来自 NSMatrix.h 中的例子:(本例中的 typedef <tag> ,即 _NSMatrixMode 不是必须的);

      typedef enum _NSMatrixMode {
          NSRadioModeMatrix = 0,
          NSHighlightModeMatrix = 1,
          NSListModeMatrix = 2,
          NSTrackModeMatrix = 3
      } NSMatrixMode;
      
      
    • 位掩码常量可以使用匿名枚举;

      enum {
          NSBorderlessWindowMask = 0,
          NSTitledWindowMask = 1 << 0,
          NSClosableWindowMask = 1 << 1,
          NSMiniaturizableWindowMask = 1 << 2,
          NSResizableWindowMask = 1 << 3
      };
      
      

    const 常量

    • 使用 const 来修饰浮点常量或彼此没有关联的整数常量,以及字符串常量等;

    • const 常量命名规则与函数命名规则相同。

      const float NSLightGray;
      
      

    字符串常量

    • 通常不使用 #define 宏定义来定义字符串常量;

    • 具体特殊使用场景的字符串常量,使用其含义来命名,命名约定与整数常量相同;

      const NSString *SDApplicationName = @”SDMobileCRM”;
      
    • 一般场量下使用的字符串常量,且字符串内容不含特殊符号的,则使用CS前缀后接字符串内容来命名。并且名称中的大小写也与字符串内容一致。这类字符串常量通常会统一地放在一个公共的头文件个用extern进行声明,并在另一个实现文件中进行赋值:

      const NSString *CSretcode = @”retcode”;
      const NSString *CSRetCode = @”RetCode”;
      

    其他常量

    • 通常不使用 #define 来创建常量。如上面所述,整数常量请使用枚举,浮点数常量请使用 const ;

    • 使用大写字母来定义预处理编译宏。如: #ifdef DEBUG

    • 编译器定义的宏名首尾都有双下划线。如: __MACH__

    • 为 notification 及 dictionary key 定义字符串常量,从而能够利用编译器的拼写检查,减少书写错误。 Cocoa 框架提供了很多这样的范例:

      APPKIT_EXTERN NSString *NSPrintCopies;
      
      

    实际的字符串值在实现文件中赋予。(注意: APPKIT_EXTERN 宏等价于 Objective-C 中 extern 。)

    异常与通知

    异常与通知的命名遵循相似的规则,但是它们有各自推荐的使用模式。

    异常

    虽然你可以基于任何目的而使用异常(由 NSException 类及相关类实现),但 Cocoa 通常不使用异常来处理常规的,可预料的错误。在这些情形下,使用诸如 nil/NULL/NO 或错误代码之类的返回值。异常的典型应用是类似数组越界之类的编程错误。

    异常由具有如下形式的全局 NSString 对象标识:

    [Prefix] + UniquePartOfName + Exception
    
    

    UniquePartOfName 部分是由连续的首字符大写的单词组成。例如:

    NSColorListIOException
    NSColorListNotEditableException
    NSDraggingException
    NSFontUnavailableException
    NSIllegalSelectorException
    
    

    通知

    如果一个类有委托,那它的大部分通知可能由其委托的委托方法来处理。这些通知的名称应该能够反应其响应的委托方法。比如,当应用程序提交 NSApplicationDidBecomeActiveNotification 通知时,全局 NSApplication 对象的委托对象会注册对该通知的处理,从而能够接收 applicaitonDidBecomeActive: 消息。

    通知由具有如下形式的全局 NSString 对象标识:

    [相关联类的名称] + [Did 或 Will] + [UniquePartOfName] + Notification
    
    

    例如:

    NSApplicationDidBecomeActiveNotification
    NSWindowDidMiniaturizeNotification
    NSTextViewDidChangeSelectionNotification
    NSColorPanelColorDidChangeNotification
    
    

    可接受的缩略名

    在设计编程接口时,通常名称不要缩写。然而,下面列出的缩写要么是固定下来的,要么是过去被广泛使用的,所以你可以继续使用。关于缩写有一些额外的注意事项:

    • 标准 C 库中长期使用的缩写形式是可以接受的。如: alloc, getc ;
    • 你可以在参数名中更自由地使用缩写。如: imageRep, col(column), obj, otherWin 。

    常见的缩写

    缩写 含义
    alloc Allocate
    app Application
    calc Calculate
    dealloc Deallocate
    dict Dictionary
    err Error
    func Function
    horiz Horizontal
    info Information
    init Initialize
    max Maximum
    msg Message
    nib Interface Builder archive
    pboard Pasteboard
    rect Rectangle
    ret Return
    temp Temporary
    vert Vertical

    常见的略写

    ASCIIPDFXMLHTMLURLRTFHTTPTIFF
    JPGGIFLZWROMRGBCMYKMIDIFTP

    编码

    花括号的使用

    • 把左花括号放在前一条语句的末尾;

      if (someExpression) {
          DoSomething();
      }
      
      
    • 函数及方法的左花括号单独放在一行;

      void DoSomething()
      {
          // do something here.
      }
      
      
    • 把右花括号单独放在一行,并且后面一般不再跟其它语句, do-while 语句除外;

      if (someExpression) {
          DoSomething();
      }
      else {
          DoSomethingElse();
      }
      
      do {
          DoSomething();
      } while (someExpression);
      
      
    • 不要省略花括号,即使花括号内只有一行语句;

      错误示范:

      if (someExpression)
          DoSomething();
      
      

      正确示范:

      if (someExpression) {
          DoSomething();
      }
      
      

    空格的使用

    • 在左括号“(”及右括号“)”的前后均不保留空格,除非与之连接的语句元素有特殊的留空要求,此规则同样适用于“@{”, “}”、“[”, “]”以及“<”, “>”;

      result = 2 * (3 + 4);
      id object = NSAllocateObject(aClass, extraBytes, zone);
      NSDictionary *dict = @{@”key”: @”value”};
      NSString *value = dict[@”key”];
      self = [self init];
      
      @interface MainViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
      
    • 在逗号“,”的前面不保留空格,后面保留一个空格,此规则同样适用于字典中的冒号“:”和for语句中的分号“;”;

      id object = NSAllocateObject(aClass, extraBytes, zone);
      NSDictionary *dict = @{@”key”: @”value”};
      
      for (i = 0; i < 10; ++i) {
          printf(“%d\n”, i);
      }
      
    • 在运算符的前后均保留一个空格,单目运算符除外;

      result = 2 * (3 + 4);
      result = (value >= 0) ? 1 : -1;
      
      
    • 在单目运算符前后均不保留空格,除非与之连接的语句元素有特殊的留空要求;

      result = !enable;
      result = [*someObject isEnable];
      
      
    • 在“@”的前后均不保留空格,除非与之连接的语句元素有特殊的留空要求;

      @interface MainViewController : UIViewController {
        @private
          int _value;
      }
      - (int)value;
      @end
      
      SEL action = @selector(onClick:);
      
      
    • 方法中的“+”、“-”,放在一行的开始位置,并与后面的内容保留一个空格;

      + (id)alloc;
      - (id)init;
      
      
    • 方法参数中的“:”,前后均不保留空格;

      - (void)observeValueForKeyPath:(NSString *)keyPath
                            ofObject:(id)object
                              change:(NSDictionary *)change
                             context:(void *)context;
      
      
    • 指针声明中的“”,前面保留一个空格,后面不保留*空格;

      NSDictionary *dict = @{@”key”: @”value”};
      const char *NSGetCString(NSString *string);
      
      - (void)observeValueForKeyPath:(NSString *)keyPath
                            ofObject:(id)object
                              change:(NSDictionary *)change
                             context:(void *)context;
      
    • 关键字的前后如果有其它连接的内容,则均保留一个空格,如: if, else, for, do, while, switch, return 等;

      do {
          DoSomething();
      } while (someExpression);
      
      
    • 在其它没有特别说明的情况下,如果符号的前后与之连接的语句元素没有特殊的留空要求,则均不保留空格;

    • 行尾一般不建议留空格(在 xcode->Preferences->Text Editing 中勾上 Automatically trim trailing whitespace 及 Including whitespace-only lines )。

    空行的使用

    • 函数或方法(包括放在前面的注释)的实现前后均保留一空行;

      // Show tips.
      void showTips(NSString *tips)
      {
          // show tips here.
      }
      
      // Make tips.
      void makeTips(NSError *error) {
          // make tips here.
      }
      
      
    • 函数或方法的声明,如果使用了文档注释,则应该在前后均保留一空行,否则可以不用;

      /**
       *  响应协议。
       */
      @protocol AuthenticationSystemDelegate <NSObject>
      
      @optional
      
      /**
       *  实名认证系统。
       *
       *  @param cmias                实名认证系统。
       *  @param applicationURLString 启动App的URL路径。
       */
      - (void)authenticationSystem:(ChinaMobileIdentityAuthenticationSystem *)cmias
                                          beginInvoke:(NSString *)applicationURLString;
      
      /**
       *  已经结束调用认证系统。
       *
       *  @param cmias    实名认证系统。
       *  @param userInfo 返回的用户信息。
       *
       *  @note 调用失败时,userInfo仅包含失败信息,没有用户信息。
       */
      - (void)chinaMobileIdentityAuthenticationSystem:(ChinaMobileIdentityAuthenticationSystem *)cmias
                                            endInvoke:(NSDictionary *)userInfo;
      
      @end
      
      
    • 逻辑代码中的注释前后均保留一空行,用以突出注释内容,及划分代码块;

      // 调用实名认证系统。
      - (void)invokeByBillid:(NSString *)billid
                     account:(NSString *)account
      {
          // 先将入参缓存起来,在调用createcheckorder服务成功后,拉起APP需要这些参数。
      
          self.billid = billid;
          self.account = account;
      
          // 调用createcheckorder服务,获取拉起APP所需要的参数。
      
          self.CRMService.action = @selector(createCheckOrderSuccess:);
          self.CRMService.errorAction = @selector(createCheckOrderFail:);
          [self.CRMService request10085CreateCheckOrder:billid account:account];
      }
      
      
    • 文件末尾保留一空行。

    缩进的使用

    • 不要使用制表符(即tab键),而是用4个连续的空格来进行缩进;

    • 成员变量中访问限制修饰 @public, @package, @protected, @private 与行首保留两个空格。

      @interface MainViewController : UIViewController {
        @private
          int _value;
      }
      @end
      
      

    折行的使用

    • 方法的声明/调用中参数太多,而无法放在一行上,应该对每个参数进行折行,并以冒号“:”对齐;

      - (void)observeValueForKeyPath:(NSString *)keyPath
                            ofObject:(id)object
                              change:(NSDictionary *)change
                             context:(void *)context;
      
      [self invokeByTransactionid:transactionid
                           billid:self.billid
                          account:self.account
                        channelid:channelid
                     provincecode:provincecode
                        signature:signature];
      
      

      如果第一行的长度较短,而令其它行无法以冒号“:”对齐,则无法对齐的行以左对齐方式对齐,其它行仍以冒号“:”对齐。(剪切这部份代码,再粘贴时,xcode会自动完成这个工作。)

      - (void)application:(UIApplication *)application
      observeValueForKeyPath:(NSString *)keyPath
                 ofObject:(id)object
                   change:(NSDictionary *)change
                  context:(void *)context;
      
      
    • 函数的声明/调用中参数太多,而无法放在一行上,应该对每个参数进行折行,并与第一个参数进行左对齐。

      NSCalculationError NSDecimalAdd(NSDecimal *result,
                                      const NSDecimal *leftOperand,
                                      const NSDecimal *rightOperand,
                                      NSRoundingMode roundingMode);
      
      NSDecimalAdd(&result,
                   &leftOperand,
                   &rightOperand,
                   roundingMode);
      
      

    注释

    文档注释

    • 类、函数、方法等的文档注释采用业内流行的javadoc风格格式;

      /**
       *  与“实名认证系统“对接的出入口。
       */
      @interface ChinaMobileIdentityAuthenticationSystem : NSObject
      
      /**
       *  该类使用单例模式,这里返回其引用。
       *
       *  @return 该类使用单例模式,这里返回其引用。
       */
      + (instancetype)sharedInstance;
      
      /**
       *  调用实名认证系统。
       *
       *  @param billid  业务号码
       *  @param account 操作员工号
       */
      - (void)invokeByBillid:(NSString *)billid
                     account:(NSString *)account;
      
      /**
       *  调用实名认证系统。
       *
       *  @param transactionid 全网唯一操作流水号
       *  @param billid        业务号码
       *  @param account       操作员工号
       *  @param channelid     渠道ID
       *  @param provincecode  省编号
       *  @param signature     签名
       */
      - (void)invokeByTransactionid:(NSString *)transactionid
                             billid:(NSString *)billid
                            account:(NSString *)account
                          channelid:(NSString *)channelid
                       provincecode:(NSString *)provincecode
                          signature:(NSString *)signature;
      
      /**
       *  接收实名认证系统返回信息。
       *
       *  @param message 返回信息
       *
       *  @note 一般是在UIApplicationDelegate的application:openURL:sourceApplication:annotation:方法中调用。
       */
      - (void)receiveReturnMessage:(NSString *)message;
      
      @end
      
      

    xcode中可以使用扩展插件 VVDocumenter 自动生成文档注释,只需要在生成注释的地方按三个斜杠///

    下载编译后会自动安装为xcode的插件,重启xcode后可以看到 Window->VVDocumenter 的配置菜单

    在需要生成注释的地方按三个斜杠///,会自动生成文档注释

    • 仅在函数、方法的声明中使用文档注释,在函数、方法的实现中仅使用单行注释;

      @implementation ChinaMobileIdentityAuthenticationSystem
      
      // 该类使用单例模式,这里返回其引用。
      + (instancetype)sharedInstance
      {
          static id instance = nil;
          if (!instance) {
              instance = [[ChinaMobileIdentityAuthenticationSystem alloc] init];
          }
          return instance;
      }
      
      // 调用实名认证系统。
      - (void)invokeByBillid:(NSString *)billid
                     account:(NSString *)account
      {
          …
      }
      
      // 调用实名认证系统。
      - (void)invokeByTransactionid:(NSString *)transactionid
                             billid:(NSString *)billid
                            account:(NSString *)account
                          channelid:(NSString *)channelid
                       provincecode:(NSString *)provincecode
                          signature:(NSString *)signature
      {
          …
      }
      
      // 接收实名认证系统返回信息。
      - (void)receiveReturnMessage:(NSString *)message
      {
          …
      }
      
      @end
      
    • 重写父类中的方法,可以不用撰写文档注释,如init等;

    • 实现委托协议中的方法,可以不用撰写文档注释,仅在协议定义的地方撰写文档注释。

    代码注释

    • 属性/变量声明的注释,如果较短,可放在变量声明的后面,并与相邻几行属性/变量声明的注释对齐;

      @interface IndicatorModel : NSObject
      
      @property (nonatomic, copy) NSString *cityname;         // 地市名称,默认为天津。
      @property (nonatomic, copy) NSString *updatedate;       // 指标数据截止日期,格式为yyyy-MM-dd。
      @property (nonatomic, copy) NSString *periodcode;       // 指标周期类型,D-日,M-月,Y-年。
      @property (nonatomic, copy) NSString *jfindcode;        // 经分指标编号。
      @property (nonatomic, copy) NSString *jfindname;        // 经分指标名称。
      @property (nonatomic, copy) NSString *value;            // 指标值。
      @property (nonatomic, copy) NSString *rate;             // 比例变动,百分比值,不带百分号。
      @property (nonatomic, copy) NSString *ratetype;         // 比例变动类型,1-同比,2-环比。
      @property (nonatomic, copy) NSString *unit;             // 单位,比如:万元。
      @property (nonatomic, copy) NSString *ranking;          // 指标排名。
      @property (nonatomic, copy) NSString *rankingchange;    // 指标排名变动,正数为排名上升,负数为排名下降,排名不变则为零。
      @property (nonatomic, copy) NSString *picurl;           // 指标图片地址,暂时未使用。
      @property (nonatomic, copy) NSString *sortorder;        // 指标排序,暂时使用。
      
      @end
      
      
    • 属性/变量声明的注释,如果较长,则应该放在声明的前面,并紧贴着声明,这样的属性/变量声明应该在前后均保留一空行;

    • 代码的注释不应该用于描述显而易见的信息;

    • 代码的注释应该言而有物,不能仅描述表面内容;

      错误示范:

      // 将入参缓存起来。
      
      self.billid = billid;
      self.account = account;
      
      

      正确示范:

      // 先将入参缓存起来,在调用createcheckorder服务成功后,拉起APP需要这些参数。
      
      self.billid = billid;
      self.account = account;
      
      
    • 仅通过代码无法获知的信息应该进行详细的注释,如出入参的取值、格式等;

      @property (nonatomic, copy) NSString *updatedate;       // 指标数据截止日期,格式为yyyy-MM-dd。
      @property (nonatomic, copy) NSString *periodcode;       // 指标周期类型,D-日,M-月,Y-年。
      
      if (![SDUtility IsServiceBadBack:result]) {
          NSDictionary *tagset = result[RES_BODY][CRM_10085_CREATECHECKORDER_RESP][WORKBENCH_COMMON_TAGSET];
          NSString *retcode = tagset[CRM_10085_CREATECHECKORDER_RETCODE];
      
          // 根据返回信息中的返回码判断调用是否成功。
          // 其中retcode = 100 - 成功;
          //     retcode = -1 - 代理商号码不合法(河北无此场景);
          //     retcode = -2 - 用户号码不合法;
          //     retcode = -3 - 代理商渠道归属的分公司与开户手机号码归属分公司不一致。
      
          if (![retcode isEqualToString:@”100”]) {
      

    约定俗成的习惯

    成员变量定义

    • 不建议使用成员变量,应使用属性来代替成员变量。如果是私有成员变量,可在类的私有类别定义中进行声明。

    属性定义

    • 字符串NSString类型的属性,应该使用copy修饰。避免使用strong/retain等引用的方式时,用户赋值NSMutableString类型的对象后,又对字符串内容进行了修改,导致不可预料的错误;

      @property (nonatomic, copy) NSString *cityname;
      
      
    • 用作委托对象的属性,应该使用weak/assign修饰;

      @property (nonatomic, weak) id<UIMyDelegate> delegate;
      
      

    比较操作

    • 永远不要对YES进行“==”、“!=”的比较;

      错误示范:

      if (YES == enable) {
          …
      }
      

      正确示范:

      if (enable) {
          …
      }
      
    • 没必要对nil/NULL/NO进行“==”、“!=”的比较;

      错误示范:

      if (nil != object) {
          …
      }
      

      正确示范:

      if (object) {
          …
      }
      

    如果觉得可以就点个👍吧,欢迎粉丝收藏,土豪打赏,您的关注就是我们创作的动力!

    读者有什么想看的相关技术篇章,欢迎评论留言!

    QQ交流群:908058499

    相关文章

      网友评论

        本文标题:OC代码规范

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