iOS编码规范

面试被问到公司编码规范问题,感觉有很多东西,但是不知道该怎么说出来,今天突然找到 李明杰 老师的一份编码规范。重新又看一遍觉得受益良多。

关于命名的一般性的原则

  • 最少字符,就是要尽量的减少命名对象的长度,尽量选择字符少的名词
  • 名符其实,命名应该能直观的描述被命名对象是什么或者做什么
  • 避免歧义,尽量不要采用多义词,也不要使用命名组合之后产生多义的方式
  • 上下文一致,比如谓词的统一性,如果都是集合类,那么使用Remove表示删除操作,那么所有上下文就应该都用这个Remove,而不要再用Delete
  • 少用缩写,除非是很常见的缩写或者项目中定义好的缩写,否则不要使用缩写

Xcode配置

  • 由于空格缩进相比Tab缩进,所敲键盘次数过多,也没有Tab缩进直观,所以这里使用Tab缩进而非空格缩进,并且缩进宽度为2个字符。Xcode通过Preferences->Text Editing->Indentation,Prefer indent using选择Tabs,Tab width输入2,Indent width输入2来进行设置;
  • 工程名及代码文件名
  • 所有命名都只能采用英文字符,下划线,加号;
  • 所有命名采用Pascal命名方式;
  • 头文件的命名方式很重要,我们可以根据其命名知晓头文件的内容;
  • 扩展类的文件命名,原类文件名+类别,类似GTMNSString+Parsing.h

文件扩展名

.h  C/C++/Objective-C header file
.m  Objective-C implementation file
.mm Objective-C++ implementation file
.cc Pure C++ implementation file
.c  C implementation file

声明孤立的类或协议:

将孤立的类或协议声明放置在单独的头文件中,该头文件名称与类或协议同名。

声明相关联的类或协议:

将相关联的声明(类,类别及协议) 放置在一个头文件中,该头文件名称与主要的类/类别/协议的名字相同。

NsString.h NSString 和 NSMutableString 类
NsLock.h NSLocking 协议和 NSLock, NSConditionLock, NSRecursiveLock 类

为已有框架中的某个类扩展 API:

如果要在一个框架中声明属于另一个框架某个类的范畴类的方法,该头文件的命名形式为:原类名+“Additions”。如 Application Kit 中的 NSBundleAdditions.h。
相关联的函数与数据类型:将相联的函数,常量,结构体以及其他数据类型放置到一个头文件中,并以合适的名字命名。如 Application Kit 中的 NSGraphics.h。
工程中的group与本地文件夹要一一对应
类及协议

采用Pascal命名方式

  • 类名应包含一个明确描述该类(或类的对象)是什么或做什么的名词。类名要有合适的前缀(请参考“前缀”一节)。Foundation 及 Application Kit 有很多这样例子,如:NSString, NSData, NSScanner, NSApplication, NSButton 以及 NSEvent;
  • 协议应该根据对方法的行为分组方式来命名,大多数协议仅组合一组相关的方法,而不关联任何类,这种协议的命名应该使用动名词(ing),以不与类名混淆;
    NsLocking 好的做法
    NsLock 糟糕的做法,看起来像类名
  • 有些协议组合一些彼此无关的方法(这样做是避免创建多个独立的小协议)。这样的协议倾向于与某个类关联在一起,该类是协议的主要体现者。在这种情形,我们约定协议的名称与该类同名。NSObject 协议就是这样一个例子。这个协议组合一组彼此无关的方法,有用于查询对象在其类层次中位置的方法,有使之能调用特殊方法的方法以及用于增减引用计数的方法。由于 NSObject 是这些方法的主要体现者,所以我们用类的名称命名这个协议;

采用Camel命名方式

  • 避免歧义,如displayName,是返回一个displayName还是执行这个操作,会产生歧义,如果是执行这个操作,应该采用showName,避免采用可以是形容词的display
  • 表示对象行为的方法,名称以动词开头,名称中不要出现 do 或 does,因为这些助动词没什么实际意义。也不要在动词前使用副词或形容词修饰
  Objective-C
  - (void) invokeWithTarget:(id)target:
  - (void) selectTabViewItem:(NSTableViewItem *)tableViewItem
get属性方法不要带get前缀,也不要带其它前缀

  Objective-C
  - (NSSize) cellSize;  对
  - (NSSize) calcCellSize;  错
  - (NSSize) getCellSize;  错
参数要用描述该参数的标签命名

  Objective-C
  - (void) sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;  对
  - (void) sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;  错
第一个参数,方法名要能描述该参数

  Objective-C
  - (id) viewWithTag:(int)aTag;  对
  - (id) taggedView:(int)aTag;  错
不要使用 and 来连接用属性作参数标签,虽然上面的例子中使用 add 看起来也不错,但当你方法有太多参数关键字时就有问题。

  Objective-C
  - (int) runModalForDirectory:(NSString *)path file:(NSString *)name types:(NSArray *)fileTypes;  对
  - (int) runModalForDirectory:(NSString *)path addFile:(NSString *)name addTypes:(NSArray *)fileTypes;  错
如果方法描述两种独立的行为,使用 and 来串接它们

  Objective-C
  - (BOOL) openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag; NSWorkspace 
方法的参数个数尽量保持四个以内,如果需要超过四个的参数,应该考虑重构方法或者抽象参数为类型

方法的实现需要进行参数有效性检查;

在方法声明中,在(-/+)符号与返回值之间留1个空格。此外,参数与前面的冒号不留空,方法段与之间留1个空格
方法间保持一个空行
方法内的空行用于区分不同的功能代码,但是如果方法中又太多的功能区块那么需要考虑重构代码
不要使用冒号对齐含实现块的方法,因为Xcode的缩进会使代码更难读

推荐:

  Objective-C
  // blocks are easily readable
  [UIView animateWithDuration:1.0 animations:^{
    // something
  } completion:^(BOOL finished) {
   // something
  }];

不推荐:

  Objective-C
  // colon-aligning makes the block indentation hard to read
  [UIView animateWithDuration:1.0
             animations:^{
                 // something
             }
             completion:^(BOOL finished) {
                 // something
             }];

访问方法

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

  Objective-C
  - (void) setNoun:(type)aNoun;
  - (type) noun;
例如:

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

  Objective-C
  - (void) setAdjective:(BOOL)flag;
  - (BOOL) isAdjective;
例如:

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

  Objective-C
  - (void) setVerbObject:(BOOL)flag;
  - (BOOL) verbObject;
例如:

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

  Objective-C
  - (void) setAcceptsGlyphInfo:(BOOL)flag;  对
  - (BOOL) acceptsGlyphInfo;  对
  - (void) setGlyphInfoAccepted:(BOOL)flag;  错
  - (BOOL) glyphInfoAccepted;  错
可以使用情态动词(can, should, will 等)来提高清晰性,但不要使用 do 或 does

  Objective-C
  - (void) setCanHide:(BOOL)flag;  对
  - (BOOL) canHide;  对
  - (void) setShouldCloseDocument:(BOOL)flag;  对
  - (void) shouldCloseDocument;  对
  - (void) setDoseAcceptGlyphInfo:(BOOL)flag;  错
  - (BOOL) doseAcceptGlyphInfo;  错
方法调用时,如果参数太多,则遵照以下方式

  Objective-C
  [myObject doFooWith:arg1  
                 name:arg2  
                error:arg3];  
禁用以下方式

  Objective-C
  [myObject doFooWith:arg1 name:arg2  // some lines with >1 arg
                error:arg3];

  [myObject doFooWith:arg1
                 name:arg2 error:arg3];

  [myObject doFooWith:arg1
            name:arg2  // aligning keywords instead of colons
            error:arg3];
如果无法使用冒号对齐,则如下缩进两个空格

  Objective-C
  [myObj short:arg1
    longKeyword:arg2
    evenLongerKeyword:arg3];
委托方法 委托方法是那些在特定事件发生时可被对象调用,并声明在对象的委托类中的方法。它们有独特的命名约定,这些命名约定同样也适用于对象的数据源方法。

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

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

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

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

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

  Objective-C
  - (BOOL) windowShouldClose:(id)sender;
方法参数

不要在参数名中使用 pointer 或 ptr,让参数的类型来说明它是指针
避免使用 one, two,...,作为参数名,更不要用1,2,...
避免为节省几个字符而缩写
采用以下参数标签与参数组合的惯例

   Objective-C
  ...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 *0charCode
  ...length:(int)numBytes
  ...point:(NSPoint)aPoint
  ...stringValue:(NSString *)aString
  ...tag:(int)anInt
  ...target:(id)anObject
  ...title:(NSString *)aString 

常量及枚举

  • 避免使用k-style命名常量
  • 通常不实用宏来定义常量,整数常量用枚举,浮点常量使用const
  • 使用大写字母来定义预处理编译宏,类似的 #ifdef DEBUG
  • 编译器定义的宏名收尾都有双下划线,如 _ MACH _
  • 优先使用全局常量而非宏,应使用static方式声明常量
  • 定义一般枚举使用NS_ENUM,命名方式为:前缀+Pascal命名,具体为什么可参考nshipster
  Objective-C
  typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
      UITableViewCellStyleDefault,
      UITableViewCellStyleValue1,
      UITableViewCellStyleValue2,
      UITableViewCellStyleSubtitle
  };
定义位与枚举使用NS_OPTION,命名方式为:前缀+Pascal命名+Mask
  Objective-C
  typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
      UIViewAutoresizingNone                 = 0,
      UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
      UIViewAutoresizingFlexibleWidth        = 1 << 1,
      UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
      UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
      UIViewAutoresizingFlexibleHeight       = 1 << 4,
      UIViewAutoresizingFlexibleBottomMargin = 1 << 5
  };
const常量命名方式:前缀+Pascal命名

属性和变量

  • 数据成员保持最小公开原则
  • 在需要公开一个数据成员时将其声明为属性,反之声明为实例变量
  • 在不需要改变一个属性时,添加@readonly,需要时添加@readwrite
  • 属性采用Camel命名方式
  • 实例变量的命名方式同属性,不过首尾都加下划线,加尾部下划线可区分属性对应的实例变量
  • 局部变量的命名方式同属性,禁用下划线
  • 定义NSString时,如果不是明确的想要引用,则应该添加@copy,大多数情况下我们是把NSString当成值类型使用的
  • @public,@protected,@private缩进一个空格
    使用如下方式
  Objective-C
  view.backgroundColor = [UIColor orangeColor];
  [UIApplication sharedApplication].delegate;
而不是

  Objective-C
  [view setBackgroundColor:[UIColor orangeColor]];
 UIApplication.sharedApplication.delegate;

最好使用auto-synthesis。但是,如果有必要,@synthesize和@dynamic应各自在实现的新行定义

当使用属性,实例变量时应该使用self.来访问,这意味着所有的属性将很容易区分,因为它们都使用 self. 开头声明指针时星号应该靠近变量名

import和include

用#import 导入Objective-C或Objective-C++头文件,用#include 导入C或C++头文件;
导入框架根的头文件而不是分别导入框架头文件,

  Objective-C
  #import      // good
  #import         // avoid
  #import 
  ...

前缀

前缀有规定的格式。它由两到三个大写字符组成,不能使用下划线与子前缀
前缀 Cocoa 框架
NS Foundation
NS Application Kit
AB Address Book
IB Interface Builder

命名 class,protocol,structure,函数,常量时使用前缀;
命名成员方法时不使用前缀,因为方法已经在它所在类的命名空间中;
同理,命名结构体字段时也不使用前缀

Blocks VS Selectors VS Protocols

  • Protocol做为只作为接口使用,类似C#和Java中的interface,剥离老版本Objective-C中使用Protocol作为委托实现方式的功能。
  • 不建议在protocol中使用@optional,因为如果只将protocol作为接口使用,那么抽象好了,接口自然不需要@optional
  • block使得代码能更好的整合,尽可能的去使用block
  • 有了block,除了编译期无法确定需要使用NSSelectorFromString之外,可能真的不需要Selector了。
  • protocol对象声明使用weak,禁止使用retain。因为retain会导致循环索引导致内存泄露
  • 回调的最佳方式是使用代理模式,而不是使用Notification;
  • block语法
  Objective-C
  returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};
  @property (nonatomic, copy) returnType (^blockName)(parameterTypes);
- (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName;
  [someObject someMethodThatTakesABlock:^returnType (parameters) {...}];
  typedef returnType (^TypeName)(parameterTypes);
  TypeName blockName = ^returnType(parameters) {...};
NSException

同样的如C++中一样,强烈不建议使用异常
如果非要使用,代码格式如下

  Objective-C
  @try {
    foo();
  }
  @catch (NSException *ex) {
    bar(ex);
  }
  @finally {
    baz();
  }

异常由具有如下形式的全局 NSString 对象标识: [Prefix] + UniquePartOfName + Exception UniquePartOfName 部分是有连续的首字符大写的单词组成。例如:

NSColorListIOException NSColorListNotEditableException NSDraggingException NSFontUnavailableException NSIllegalSelectorException

初始化函数

  • 返回值必须为instancetype,不要再使用id了
  • 避免多次调用父类的初始化函数
  • 使用通用的、约定俗成的alloc和init的方式创建实例,而不是使用new方法
  • 在创建NSString,NSDictionary,NSArray和NSNumber等对象实例时,应使用Literals字面量。需要注意的是,不应将nil传给NSArray和NSDictionary字面量,否则会引起程序崩溃,使用
  Objective-C
  NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve"];
  NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal"};
  NSNumber *shouldUseLiterals = @YES;
  NSNumber *buildingZIPCode = @10018;

而不是

  Objective-C
  NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", nil];
  NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys:@"Kate", @"iPhone", @"Kamal",     @"iPad", nil];
  NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];
  NSNumber *buildingZIPCode = [NSNumber numberWithInteger:10018];
在初始化函数中,访问数据成员应该直接访问实例变量而不是属性

pragma

  • 使用#pragma将同一个protocol的方法归类
  • 使用#pragma将同一个父类的重写方法归类
  • 使用#pragma将一组相关联的方法归类
  Objective-C
  @implementation ViewController
  - (instancetype)init {
    ...
  }
  
  #pragma mark - UIViewController
  - (void)viewDidLoad {
    ...
  }

  #pragma mark - IBAction
  - (IBAction)cancel:(id)sender {
    ...
  }

  #pragma mark - UITableViewDataSource
  - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    ...
  }

  #pragma mark - UITableViewDelegate
  - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    ...
  }
nil / Nil / NULL / NSNull

Objective-C中默认所有的指针指向nil
nil最显著的行为是,它虽然为零,仍然可以有消息发送给它,结果返回0

Objective-C
// 举个例子,这个表达...
if (name != nil && [name isEqualToString:@"Steve"]) { ... }
// …可以被简化为:
if ([name isEqualToString:@"steve"]) { ... }
不需要显示初始化为nil,更不要初始化为其它几个关键词

需要知道如下

Symbol  Value   Meaning
NULL    (void *)0   literal null value for C pointers
nil (id)0   literal null value for Objective-C objects
Nil (Class)0    literal null value for Objective-C classes
NSNull  [NSNull null]   singleton object used to represent null
注释
  • 不推荐处处都是注释,不推荐写大块注释,好的代码应该自解释。
  • 关键的逻辑、跳转、判断,以及复杂的算法,非常有必要使用注释。
  • 多个注释应尽量左对齐。
  • 所有注释需要保持更新,不需要的注释需要删除
  • 安装VVDocument,使用其格式对方法进行注释
  • 在switch块中,如果一个case不添加break,请明确添加注释

表达式格式

方法大括号和其它大括号(比如for、if、while、switch等等)应在语句的同一行开始,而在新的一行关闭;

  Objective-C
  if (user.isHappy) {
      // Do something
  } else {
      // Do something else
  }

不要省略掉花括号,因为即使当前只有一行代码,可能后续其它人会添加代码,如果遗忘补上花括号会产生非预期结果

  Objective-C
  // good
  if (!error) {
      return success;
  }
  // bad
  if (!error)
      return success;
  // bad
  if (!error) return success;
  • 单目运算符与操作数间不要有空格
  • 多目运算符与操作数保持一个空格
  • 三目运算符不要嵌套
  • 强制类型转换、参数之间不要有空格

Booleans

  • Objective-C用BOOL来编码真值。它是signed char的typedef,并且用宏YES和NO来相应的表示真和假。
  • 在Objective-C中,当遇到处理真值的参数,属性和实例变量时,使用类型BOOL。当分配字面值时,使用宏YES和NO。
  • 如果BOOL属性的名称表示为一个形容词,该属性可以省略“is”字头,但需要为get方法按照常规指定名称
  Objective-C
  @property (assign, getter=isEditable) BOOL editable;
判断真假时不要与字面值比较

  Objective-C
  // good
  if (someObject) {
  }
  if (![anotherObject boolValue]) {
  }
  // bad
  if (someObject == nil) {}
  if ([anotherObject boolValue] == NO) {}
  if (isAwesome == YES) {} // Never do this.
  if (isAwesome == true) {} // Never do this.
Objective-C中的所有真值类型和数值:

Name    Typedef Header  True Value  False Value
BOOL    signed char objc.h  YES NO
bool    _Bool (int) stdbool.h   true    false
boolean unsigned char   MacTypes.h  TRUE    FALSE
NSNumber    __NSCFBoolean   Foundation.h    @(YES)  @(NO)
CFBooleanRef    struct  CoreFoundation.h    kCFBooleanTrue  kCFBooleanFalse

文档结构管理

  • 建立Libraries文件夹,放所有第三方库。
  • 建立Utilities文件夹,放自己封装的类。
  • 建立Constants.h 文件,专门放常量定义和一些全局的枚举。
  • 每个功能块放一个Group(实际的文件夹),其中按MVC分成Model,View,Controller子文件夹来管理。
  • 程序资源文件放入Supporting Files 文件夹,可在其下建立 图片,音频,视频等子文件夹,分别管理不同种类的资源,若有多个功能共用的,可以建立Common文件夹。

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