iOS客户端开发编码规范

Objective-C 编码规范,内容来自苹果的文档翻译,自己的编码经验和对其它资料的总结。

一.命名规范

基本原则

1.清晰
命名应该尽可能的清晰和简洁,但在 Objective-C 中,清晰比简洁更重要。由于 Xcode 强大的自动补全功能,我们不必担心名称过长的问题。

// 清晰
insertObject:atIndex:
// 不清晰, insert 的对象类型和 at 的位置属性没有说明
insert:at:
// 清晰
removeObjectAtIndex:
// 不清晰, remove 的对象类型没有说明,参数的作用没有说明
remove:

不要使用单词的简写,拼写出完整的单词:

// 清晰
setBackgroundColor:
// 不清晰,不要使用简写
setBkgdColor:

然而,有部分单词简写在 Objective-C 编码过程中是非常常用的,以至于成为了一种规范,这些简写可以在代码中直接使用,下面列举了部分

alloc == Allocate 
max == Maximum
alt == Alternate 
min == Minimum
app == Application 
msg == Message
calc == Calculate 
nib == Interface Builder archive
dealloc == Deallocate 
pboard == Pasteboard
func == Function 
rect == Rectangle
horiz == Horizontal 
Rep == Representation (used in class name such as NSBitmapImageRep).
info == Information 
temp == Temporary
init == Initialize 
vert == Vertical
int == Integer

命名方法或者函数时要避免歧义
功能的英文词义命名, 以它[做什么]来命名,而不是[怎么做]来命名

// 有歧义,是返回 sendPort 还是 send 一个 Port ?
sendPort
// 有歧义,是返回一个名字属性的值还是 display 一个 name 的动作?
displayName
// 正确 很明显这个函数是返回一个消息的时间戳
getAmsgTimeStamp;

2.一致性
整个工程的命名风格要保持一致性,最好和苹果 SDK 的代码保持统一。不同类中完成相似功能的方法应该叫一样的名字,比如我们总是用 count 来返回集合的个数,不能在 A 类中使用 count 而在 B 类中使用 getNumber 。

  • 如果代码需要打包成 Framework 给别的工程使用,或者工程项目非常庞大,需要拆分成不同的模块,使用命名前缀是非常有用的。
  • 可以在为类、协议、函数、常量以及 typedef 宏命名的时候使用前缀,但注意不要为成员变量或者方法使用前缀,因为他们本身就包含在类的命名空间内。
  • 命名前缀的时候不要和苹果 SDK 框架冲突

前缀由大写的字母缩写组成,比如 Cocoa 中 NS 前缀代表 Founation 框架中的类, IB 则代表 Interface Builder 框架。

所以要选择符合自己项目的前缀,

执行准则

1.命名类和协议( Class&Protocol )

  • 类名以大写字母开头,应该包含一个名词来表示它代表的对象类型,同时可以加上必要的前缀,比如 NSString,NSDate,NSScanner,NSApplication 等等。

  • 协议名称应该清晰地表示它所执行的行为,而且要和类名区别开来,所以通常使用 ing 词尾来命名一个协议,比如 NSCopying,NSLocking 。

  • 有些协议本身包含了很多不相关的功能,主要用来为某一特定类服务,这时候可以直接用类名来命名这个协议,比如 NSObject 协议,它包含了 id 对象在生存周期内的一系列方法。

2.命名头文件( Headers )
源码的头文件名应该清晰地暗示它的功能和包含的内容

  • 如果头文件内只定义了单个类或者协议,直接用类名或者协议名来命名头文件,比如 NSLocale.h 定义了 NSLocale 类。
  • 如果头文件内定义了一系列的类、协议、类别,使用其中最主要的类名来命名头文件,比如 NSString.h 定义了 NSString 和 NSMutableString 。
  • 每一个 Framework 都应该有一个和框架同名的头文件,包含了框架中所有公共类头文件的引用,比如 Foundation.h
  • Framework 中有时候会实现在别的框架中类的类别扩展,这样的文件通常使用被扩展的框架名 +Additions 的方式来命名,比如 NSBundleAdditions.h 。

3.命名方法( Methods )
Objective-C 的方法名通常都比较长,这是为了让程序有更好地可读性,按苹果的说法 “_ 好的方法名应当可以以一个句子的形式朗读出来_ ” 。

方法一般以小写字母打头,每一个后续的单词首字母大写,方法名中不应该有标点符号(包括下划线),有两个例外:

  • 可以用一些通用的大写字母缩写打头方法,比如 PDF,TIFF 等。
  • 可以用带下划线的前缀来命名私有方法或者类别中的方法。

如果方法表示让对象执行一个动作,使用动词打头来命名,注意不要使用 do , does 这种多余的关键字,动词本身的暗示就足够了:

// 动词打头的方法表示让对象执行一个动作
- (void)invokeWithTarget:(id)target;
- (void)selectTabViewItem:(NSTabViewItem *)tabViewItem;

如果方法是为了获取对象的一个属性值,直接用属性名称来命名这个方法,注意不要添加 get 或者其他的动词前缀:

// 正确,使用属性名来命名方法
- (NSSize)cellSize;
// 错误,添加了多余的动词前缀
- (NSSize)calcCellSize;
- (NSSize)getCellSize;

对于有多个参数的方法,务必在每一个参数前都添加关键词,关键词应当清晰说明参数的作用不要用 and 来连接两个参数,通常 and 用来表示方法执行了两个相对独立的操作(从设计上来说,这时候应该拆分成两个独立的方法).

方法的参数命名也有一些需要注意的地方 :

  • 和方法名类似,参数的第一个字母小写,后面的每一个单词首字母大写
  • 不要再方法名中使用类似 pointer,ptr 这样的字眼去表示指针,参数本身的类型足以说明
  • 不要使用只有一两个字母的参数名
  • 不要使用简写,拼出完整的单词

下面列举了一些常用参数名:

...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

4.命名常量( Constants )

如果要定义一组相关的常量,尽量使用枚举类型( enumerations ),枚举类型的命名规则和函数的命名规则相同:

//下拉刷新
typedef enum{
    HWRefreshStatuPulling           = 0,//下拉
    HWRefreshStatuRefrshing         = 1,//刷新
    HWRefreshStatuFinish            = 2//刷新完成
}HWRefreshStatus;
//这是SDWebImage的枚举
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
    SDWebImageDownloaderLowPriority = 1 << 0,
    SDWebImageDownloaderProgressiveDownload = 1 << 1,

    /**
     * By default, request prevent the of NSURLCache. With this flag, NSURLCache
     * is used with default policies.
     */
    SDWebImageDownloaderUseNSURLCache = 1 << 2,

    /**
     * Call completion block with nil image/imageData if the image was read from NSURLCache
     * (to be combined with `SDWebImageDownloaderUseNSURLCache`).
     */

    SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
    /**
     * In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for
     * extra time in background to let the request finish. If the background task expires the operation will be cancelled.
     */

    SDWebImageDownloaderContinueInBackground = 1 << 4,

    /**
     * Handles cookies stored in NSHTTPCookieStore by setting 
     * NSMutableURLRequest.HTTPShouldHandleCookies = YES;
     */
    SDWebImageDownloaderHandleCookies = 1 << 5,

    /**
     * Enable to allow untrusted SSL ceriticates.
     * Useful for testing purposes. Use with caution in production.
     */
    SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,

    /**
     * Put the image in the high priority queue.
     */
    SDWebImageDownloaderHighPriority = 1 << 7,
};

枚举也要选取适合自己项目的前缀

5.命名通知( Notifications)
通知常用于在模块间传递消息,所以通知要尽可能地表示出发生的事件,通知的命名范式是:

[ k ] + Notification + [ 动作 ] + [Did | Will] + [ 状态 ]

kNotificationMessageDidSendFail

6.文件注释
每一个文件都必须写文件注释,文件注释通常包含:

  • 作者信息
  • 文件包含的内容,作用
  • 历史版本
  • 文件所在模块
/*******************************************************************************
 Author: Sean Xue (Xue yunqiang)
 E-mail: [email protected]
 Description:
 This file provide some covenient tool in calling library tools. One can easily include
 library headers he wants by declaring the corresponding macros.
 I hope this file is not only a header, but also a useful Linux library note.
 History:
 2012-??-??: On about come date around middle of Year 2012, file created as "commonLib.h"
 2013-01-07: Add basic data type such as "sint8_t"
 2013-01-18: Add CFG_LIB_STR_NUM.
 2013-01-22: Add CFG_LIB_TIMER.
 2013-01-22: Remove CFG_LIB_DATA_TYPE because there is already AMCDataTypes.h
 ********************************************************************************/

//
//  ViewController.m
//  LRMacroDefinition
//
//  Created by lu on 16/7/4.
//  Copyright © 2016年 scorpio. All rights reserved.
//

7.代码注释

按住Command+Option+/即可得到

/**
 <#Description#>

 @param imageName <#imageName description#>
 @return <#return value description#>
 */

依次注释即可

//代码样式举例
/**
 输入图片名获取图片

 @param imageName 图片名成
 @return UIImage对象
 */

8.import 和 include

import 是 Cocoa 中常用的引用头文件的方式,它能自动防止重复引用文件,什么时候使用 import ,什么时候使用 include 呢?

  • 当引用的是一个 Objective-C 或者 Objective-C++ 的头文件时,使用 import
  • 当引用的是一个 C 或者 C++ 的头文件时,使用 include ,这时必须要保证被引用的文件提供了保护域( define guard )。

二.代码格式

1.将空格设置为四个空格

在 Xcode > Preferences > Text Editing 将 Tab 和自动缩进都设置为 4 个空格。

2.每一行的最大长度

同样的,在 Xcode > Preferences > Text Editing > Page guide at column: 中将最大行长设置为 80 ,过长的一行代码将会导致可读性问题。

3.函数的书写

一个典型的 Objective-C 函数应该是这样的:

- (void)writeVideoFrameWithData:(NSData *)frameData timeStamp:(int)timeStamp {
...
}

在 - 和 (void) 之间应该有一个空格,第一个大括号 { 的位置在函数所在行的末尾,同样应该有一个空格。

如果一个函数有特别多的参数或者名称很长,应该将其按照 : 来对齐分行显示:

-(id)initWithModel:(IPCModle)model
        ConnectType:(IPCConnectType)connectType
        Resolution:(IPCResolution)resolution
            AuthName:(NSString *)authName
            Password:(NSString *)password
                MAC:(NSString *)mac
                AzIp:(NSString *)az_ip
                AzDns:(NSString *)az_dns
                Token:(NSString *)token
                Email:(NSString *)email
                Delegate:(id)delegate;

在分行时,如果第一段名称过短,后续名称可以以 Tab 的长度( 4 个空格)为单位进行缩进:

    - (void)short:(int)theFoo
      longKeyword:(NSRect)theRect
evenLongerKeyword:(float)theInterval
            error:(NSError **)theError;

4.函数调用
函数调用的格式和书写差不多,可以按照函数的长短来选择写在一行或者分成多行:

// 写在一行
[myObject doFooWith:arg1 name:arg2 error:arg3];
// 分行写,按照 ':' 对齐
[myObject doFooWith:arg1
               name:arg2
              error:arg3];
// 第一段名称过短的话后续可以进行缩进
[myObj short:arg1
 longKeyword:arg2
evenLongerKeyword:arg3
        error:arg4];

5.闭包( Blocks )

  • 较短的 block 可以写在一行内。
  • 如果分行显示的话, block 的右括号 } 应该和调用 block 那行代码的第* 一个非空字符对齐。
  • block 内的代码采用 4 个空格 的缩进。
  • 如果 block 过于庞大,应该单独声明成一个变量来使用。
  • ^ 和 ( 之间, ^ 和 { 之间都没有空格,参数列表的右括号 ) 和 { 之间有一个空格。
// 较短的 block 写在一行内
[operation setCompletionBlock:^{ [self onOperationDone]; }];
// 分行书写的 block ,内部使用 4 空格缩进
[operation setCompletionBlock:^{
    [self.delegate newDataAvailable];
}];
// 使用 C 语言 API 调用的 block 遵循同样的书写规则
dispatch_async(_fileIOQueue, ^{
    NSString* path = [self sessionFilePath];
    if (path) {
        // ...
    }
});
// 较长的 block 关键字可以缩进后在新行书写,注意 block 的右括号 '}' 和调用 block 那行代码的第一个非空字符对齐
[[SessionService sharedService]
loadWindowWithCompletionBlock:^(SessionWindow *window) {
    if (window) {
        [self windowDidLoad:window];
    } else {
        [self errorLoadingWindow];
    }
}];
// 较长的 block 参数列表同样可以缩进后在新行书写
[[SessionService sharedService]
loadWindowWithCompletionBlock:
^(SessionWindow *window) {
    if (window) {
        [self windowDidLoad:window];
    } else {
        [self errorLoadingWindow];
    }
}];
// 庞大的 block 应该单独定义成变量使用
void (^largeBlock)(void) = ^{
    // ...
};
[_operationQueue addOperationWithBlock:largeBlock];
// 在一个调用中使用多个 block ,注意到他们不是像函数那样通过 ':' 对齐的,而是同时进行了 4 个空格的缩进
[myObject doSomethingWith:arg1
firstBlock:^(Foo *a) {
    // ...
}
secondBlock:^(Bar *b) {
    // ...
}];

三.代码组织

在函数分组和protocol/delegate实现中使用#pragma mark -来分类方法,要遵循以下一般结构:

#pragma mark - Lifecycle

- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}

#pragma mark - Public

- (void)publicMethod {}

#pragma mark - Private

- (void)privateMethod {}

#pragma mark - NSCopying

- (id)copyWithZone:(NSZone *)zone {}

#pragma mark - NSObject

- (NSString *)description {}

你可能感兴趣的:(iOS客户端开发编码规范)