Objective-C 编程规范

2017-04-14  本文已影响57人  666真666

命名规范

1.【强制】 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。

反例: _name / __name / $Object / name_ / name$ / Object$

2.【强制】 代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。

说明:正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使纯拼音命名方式 也要避免采用。

反例:DaZhePromotion [打折] / getPingfenByName() [评分] / int 某变量 = 3

正例:alibaba / taobao / youku / hangzhou 等国际通用的名称,可视同英文。

3.【强制】类名使用 UpperCamelCase 风格,必须遵从驼峰形式,但领域模型相关的命名除外,比如 UserDAO

正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion

反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion

4.【强制】方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从 驼峰形式。

正例:localValue / getHttpMessage / inputUserId

5.【强制】常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。

正例:MAX_STOCK_COUNT

反例:MAX_COUNT

6.【强制】异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾。

7.【强制】杜绝完全不规范的缩写,避免望文不知义。

8.【强制】文件名和类名同名。

9.【推荐】如果使用到了设计模式,建议在类名中体现出具体模式。

说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计思想。

10.【推荐】类名应加上三个大写字母作为前缀(两个字母的为 Apple 的类保留)。

11.【强制】特殊类的命名,如果是视图控制器的子类应添加后缀“ViewController”或者“Controller”,如果是视图的子类应添加后缀“View”,如果是按钮的子类应添加后缀“Button”。

12.【强制】分类 Category 命名,与类命名相同,此外需添加要扩展的类名和“+”

13.【强制】协议(委托)命名,与类命名相同,此外需添加“Delegate”后缀

14.【推荐】方法返回值为Boolean 类型,应加 is 前缀。如果某方法返回非属性的 Boolean 值,那么应该根据其功能,选用 has 或 is 当前缀。

- (BOOL)isEqualToString:(NSString *)aString; 
- (BOOL)hasPrefix:(NSString *)aString;

15.【推荐】方法名不要使用 new 作为前缀

16.【推荐】不要使用 and 来连接用属性作参数的关键字

17.【推荐】方法连接多个参数命名时,适当使用合适的介词。比如 in for at by of with

//清晰
insertObject:atIndex:

//不清晰,insert的对象类型和at的位置属性没有说明
insert:at:

//清晰
removeObjectAtIndex:

//不清晰,remove的对象类型没有说明,参数的作用没有说明
remove:

18.【推荐】定义成员变量,添加下划线前缀。但不推荐使用成员变量,直接使用属性。

19.【强制】资源图片命名,图片应该与类文件一样,按模块分组放置,缩略前缀可用如下例子

icon btn bg line logo pic img

常量定义

1.【强制】不允许出现任何魔法值(即未经定义的常量)直接出现在代码中。

2.【推荐】不要使用一个常量类维护所有常量,应该按常量功能进行归类,分开维护。

如:缓存相关的常量放在类:CacheConsts下;系统配置相关的常量放在类:ConfigConsts下。

说明:大而全的常量类,非得使用查找功能才能定位到修改的常量,不利于理解和维护。

3.【强制】常量使用小写k开头的驼峰法。

static const NSTimeInterval kAnimationDuration = 0.3;

4.【强制】若常量作用域超出编译单元,在类外可见时,

.h
extern NSString *const EOCStringConstant;

.m
NSString *const EOCStringConstant = @"aaa";

5.【强制】全局常量(通知或者关键字等)尽量用const来定义,而不是宏。因为如果使用宏定义, 宏可能被重定义,引用不同的文件可能会导致宏的不同。

格式规范

1.【强制】大括号的使用约定。如果是大括号内为空,则简洁地写成{}即可,不需要换行;如果 是非空代码块则:

2.【强制】 左括号和后一个字符之间不出现空格;同样,右括号和前一个字符之间也不出现空格。

3.【强制】if/for/while/switch/do 等保留字与左右括号之间都必须加空格。

4.【强制】任何运算符左右必须加一个空格。

说明:运算符包括赋值运算符=、逻辑运算符&&、加减乘除符号、三目运算符等。

5.【强制】缩进采用 4 个空格,禁止使用 tab 字符。

- (void)method {
    // 缩进 4 个空格
    NSString *say = @"hello";
    // 运算符的左右必须有一个空格
    NSInteger flag = 0;
    // 关键词 if 与括号之间必须有一个空格,括号内的 f 与左括号,0 与右括号不需要空格
    if (flag == 0) {
        NSLog(@"%@", say);
    }
    
    // 左大括号前不加空格且不换行,左大括号后换行
    if (flag == 1) {
        NSLog(@"world");
        // 右大括号前换行,右大括号后有 else,不用换行
    } else {
        NSLog(@"ok");
        // 在右大括号后直接结束,则必须换行
    }
}

6.【强制】单行字符数限制不超过 120 个,超出需要换行,按照:来对齐分行显示

-(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<IPCConnectHandlerDelegate>)delegate;

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

- (void)short:(GTMFoo *)theFoo
        longKeyword:(NSRect)theRect
  evenLongerKeyword:(float)theInterval
              error:(NSError **)theError {
    ...
}

函数的调用同理。

//写在一行
[myObject doFooWith:arg1 name:arg2 error:arg3];

//分行写,按照':'对齐
[myObject doFooWith:arg1
               name:arg2
              error:arg3];

//第一段名称过短的话后续可以进行缩进
[myObj short:arg1
          longKeyword:arg2
    evenLongerKeyword:arg3
                error:arg4];

7.【强制】多个参数逗号后边必须加空格。

正例:

NSLog(@"%@-%@-%@-%@", a, b, c, d);

8.【推荐】没有必要增加若干空格来使某一行的字符与上一行的相应字符对齐。

说明:在变量比较多的 情况下,是一种累赘的事情。

@interface HOECoupon : NSObject

@property (nonatomic, assign) NSInteger couponId;
@property (nonatomic, copy) NSString *startTime;
@property (nonatomic, copy) NSString *endTime;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subTitle;
@property (nonatomic, assign) NSInteger state;
@property (nonatomic, assign) NSInteger canUse;
@property (nonatomic, assign) CGFloat price;
@property (nonatomic, copy) NSString *stateDesc;
@property (nonatomic, copy) NSString *reason;

@end

9.【推荐】方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义之间插入一个空行。相同业务逻辑和语义之间不需要插入空行。

说明:没有必要插入多行空格进行隔开。

10.【推荐】.m实现类中推荐的代码结构如下

#pragma mark - lifecycle
- (void)dealloc
- (void)viewDidLoad
- (void)viewWillAppear:(BOOL)animated
...
#pragma mark - UITableViewDataSource & UITableViewDelegate
...
#pragma mark - Event handlers
- (void)didTapLikeButton:
...
#pragma mark - public methods
...
#pragma mark - private methods
- (void)setupTableView
- (void)setupNotification
...
#pragma mark - getters & setters
...

11.【推荐】头文件引用的顺序

//  controllers
#import "OrcaAddFriendFromContactsViewController.h"
...
// views
...
// consts
...
// utils
...
// others

12.【推荐】方法的格式,在-和(void)之间应该有一个空格

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

面向对象规范

1.【强制】避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。

2.【强制】不能使用过时的类或方法。

3.【强制】推荐使用 NS_ENUM ,而不是 enum

typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {
    UIViewAnimationTransitionNone,
    UIViewAnimationTransitionFlipFromLeft,
    UIViewAnimationTransitionFlipFromRight,
    UIViewAnimationTransitionCurlUp,
    UIViewAnimationTransitionCurlDown,
};

4.【推荐】尽量使用字面量来初始化

NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve"];
NSDictionary *productManagers = @{@"iPhone" : @"Kate", @"iPad" : @"Kamal"};
NSNumber *shouldUseLiterals = @YES;
NSNumber *buildingZIPCode = @10018;

5.【推荐】建议定义属性的时候把所有的参数写全, 尤其是如果想定义成只读的(防止外面修改)那一定要加上readonly, 这也是代码安全性的一个习惯。

@property (nonatomic, readwrite, copy) NSString *name;

6.【强制】方法参数过多,需要进行重构

正例:

- (void)registerUser(User *user) {
     // to do...
}

反例:

- (void)registerUserName:(NSString *)userName
                password:(NSString *)password 
                   email:(NSString *)email {
     // to do...
}

7.【强制】不推荐使用成员变量,直接使用属性。

8.【推荐】setter 方法中,参数名称与类成员变量名称一致,self.成员名 = 参数名。在getter/setter 方法中,尽量不要增加业务逻辑,增加排查问题的难度。

9.【推荐】对于简单对象,推荐在getter方法中延迟加载属性,如果创建复杂对象,那么对象的创建最好放到类的初始化时,而不是延迟加载。

10.【推荐】协议,在书写协议的时候注意用<>括起来的协议和类型名之间是没有空格的

@interface MyProtocoledClass : NSObject<NSWindowDelegate> {
}

- (void)setDelegate:(id<MyFancyDelegate>)aDelegate;
@end

11.【推荐】闭包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) {
        // ...
    }];

12.【推荐】不要用点分语法来调用方法,只用来访问属性。这样是为了防止代码可读性问题。

//正确,使用点分语法访问属性
NSString *oldName = myObject.name;
myObject.name = @"Alice";

//错误,不要用点分语法调用方法
NSArray *array = [NSArray arrayWithObject:@"hello"];
NSUInteger numberOfItems = array.count;

13.BOOL 的使用

BOOL在Objective-C中被定义为signed char类型,这意味着一个BOOL类型的变量不仅仅可以表示YES(1)和NO(0)两个值,所以永远不要将BOOL类型变量直接和YES比较:

//错误,无法确定|great|的值是否是YES(1),不要将BOOL值直接与YES比较
BOOL great = [foo isGreat];
if (great == YES)
  // ...be great!

//正确
BOOL great = [foo isGreat];
if (great)
  // ...be great!

同样的,也不要将其它类型的值作为BOOL来返回,这种情况下,BOOL变量只会取值的最后一个字节来赋值,这样很可能会取到0(NO)。但是,一些逻辑操作符比如&&,||,!的返回是可以直接赋给BOOL的:

//错误,不要将其它类型转化为BOOL返回
- (BOOL)isBold {
  return [self fontTraits] & NSFontBoldTrait;
}
- (BOOL)isValid {
  return [self stringValue];
}

//正确
- (BOOL)isBold {
  return ([self fontTraits] & NSFontBoldTrait) ? YES : NO;
}

//正确,逻辑操作符可以直接转化为BOOL
- (BOOL)isValid {
  return [self stringValue] != nil;
}
- (BOOL)isEnabled {
  return [self isValid] && [self isBold];
}

14.不要使用new方法

尽管很多时候能用new代替alloc init方法,但这可能会导致调试内存时出现不可预料的问题。Cocoa的规范就是使用alloc init方法,使用new会让一些读者困惑。

15.Public API要尽量简洁

共有接口要设计的简洁,满足核心的功能需求就可以了。不要设计很少会被用到,但是参数极其复杂的API。如果要定义复杂的方法,使用类别或者类扩展。

集合处理

1.【强制】对于可变集合类型作为属性时使用strong,不可变集合类型使用copy

2.【强制】尽量不要暴露mutable类型的对象在public interface, 建议在.h定义一个Inmutable类型的属性, 然后在.m的get函数里面返回一个内部定义的mutable变量。保证类的不可变性

3.使用NSNumber的语法糖

使用带有@符号的语法糖来生成NSNumber对象能使代码更简洁:

NSNumber *fortyTwo = @42;
NSNumber *piOverTwo = @(M_PI / 2);
enum {
  kMyEnum = 2;
};
NSNumber *myEnum = @(kMyEnum);

4.保证NSString在赋值时被复制

NSString非常常用,在它被传递或者赋值时应当保证是以复制(copy)的方式进行的,这样可以防止在不知情的情况下String的值被其它对象修改。

- (void)setFoo:(NSString *)aFoo {
  _foo = [aFoo copy];
}

5.按照定义的顺序释放资源

在类或者Controller的生命周期结束时,往往需要做一些扫尾工作,比如释放资源,停止线程等,这些扫尾工作的释放顺序应当与它们的初始化或者定义的顺序保持一致。这样做是为了方便调试时寻找错误,也能防止遗漏。

并发处理

1.【强制】获取单例对象需要保证线程安全,其中的方法也要保证线程安全。

2.定义一个属性时,编译器会自动生成线程安全的存取方法(Atomic),但这样会大大降低性能,特别是对于那些需要频繁存取的属性来说,是极大的浪费。所以如果定义的属性不需要线程保护,记得手动添加属性关键字nonatomic来取消编译器的优化。

控制语句

1.【强制】在一个 switch 块内,每个 case 要么通过 break/return 等来终止,要么注释说明程 序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default 语句并且 放在最后,即使它什么代码也没有。

2.【强制】在 if/else/for/while/do 语句中必须使用大括号,即使只有一行代码,避免使用 下面的形式:if (condition) statements;

3.【推荐】推荐尽量少用 else, if-else 的方式可以改写成:

if (condition) { 
    ...
    return obj; 
}
// 接着写 else 的业务逻辑代码;

4.【强制】请勿超过 3 层,超过请使用状态设计模式。

正例:逻辑上超过 3 层的 if-else 代码可以使用卫语句,或者状态模式来实现。

5.【强制】三目运算符的使用

正例:

result = object ? : [self createObject];

反例:

result = object ? object : [self createObject];

6.【强制】对于复杂的条件判断,要提取一个变量出来

正例:

([self canDeleteJob:job]) { ... }     
    
- (BOOL)canDeleteJob:(Job *)job {
    BOOL invalidJobState = job.JobState == JobState.New
                          || job.JobState == JobState.Submitted
                          || job.JobState == JobState.Expired;
    BOOL invalidJob = job.JobTitle && job.JobTitle.length;
     
    return invalidJobState || invalidJob;
}

反例:

if (job.JobState == JobState.New
    || job.JobState == JobState.Submitted
    || job.JobState == JobState.Expired
    || (job.JobTitle && job.JobTitle.length))
{
    //....
}

7.【强制】对于嵌套判断,一旦发现某个条件不符合,立即返回,条理更清晰

正例:

if (!user.UserName) return NO;
if (!user.Password) return NO;
if (!user.Email) return NO;
 
return YES;

反例:

Not preferred:
BOOL isValid = NO;
if (user.UserName)
{
 
    if (user.Password)
    {
     
        if (user.Email) isValid = YES;
    }
     
}
return isValid;

注释规范

1.【强制】类、类属性、类方法的注释必须使用文档注释,不得使用 //xxx 方式。

2.【强制】所有的类都必须添加创建者信息。

3.【强制】方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释
使用/* */注释,注意与代码对齐。

4.【强制】所有的枚举类型字段必须要有注释,说明每个数据项的用途。

5.【推荐】代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑 等的修改。

6.【参考】注释掉的代码尽量要配合说明,而不是简单的注释掉。

说明:代码被注释掉有两种可能性:1)后续会恢复此段代码逻辑。2)永久不用。前者如果没 有备注信息,难以知晓注释动机。后者建议直接删掉(代码仓库保存了历史代码)。

7.【参考】好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的一个极端:过多过滥的注释,代码的逻辑一旦修改,修改注释是相当大的负担。

8.【参考】特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描,经常清理此类标记。线上故障有时候就是来源于这些标记处的代码。

  1. 待办事宜(TODO):( 标记人,标记时间,[预计处理时间]) 表示需要实现,但目前还未实现的功能。

  2. 错误,不能工作(FIXME):(标记人,标记时间,[预计处理时间])
    在注释中用 FIXME 标记某代码是错误的,而且不能工作,需要及时纠正的情况。

其他

1.【强制】避免 block 的循环引用

__weak typeof(self) weakSelf = self;
myObj.myBlock =  ^{
    __strong typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
      [strongSelf doSomething]; // strongSelf != nil
      // preemption, strongSelf still not nil
      [strongSelf doSomethingElse]; // strongSelf != nil
    }
    else {
        // Probably nothing...
        return;
         
    }
};

2.Delegate要使用弱引用

一个类的Delegate对象通常还引用着类本身,这样很容易造成引用循环的问题,所以类的Delegate属性要设置为弱引用。

/** delegate */
@property (nonatomic, weak) id <IPCConnectHandlerDelegate> delegate;

3.nil检查
因为在Objective-C中向nil对象发送命令是不会抛出异常或者导致崩溃的,只是完全的“什么都不干”,所以,只在程序中使用nil来做逻辑上的检查。

另外,不要使用诸如nil == Object或者Object == nil的形式来判断。

//正确,直接判断
if (!objc) {
    ... 
}

//错误,不要使用nil == Object的形式
if (nil == objc) {
    ... 
}
上一篇下一篇

猜你喜欢

热点阅读