iOS代码规范

编程核心原则:

原则一:代码应该简洁易懂,逻辑清晰

因为软件是需要人来维护的。这个人在未来很可能不是你。所以首先是为人编写程序,其次才是计算机:

  • 不要过分追求技巧,降低程序的可读性。
  • 简洁的代码可以让bug无处藏身

原则二:面向变化编程,而不是面向需求编程。

需求是暂时的,只有变化才是永恒的。 本次迭代不能仅仅为了当前的需求,写出扩展性强,易修改的程序才是负责任的做法,对自己负责,对公司负责。

原则三:先保证程序的正确性,防止过度工程

过度工程(over-engineering):在正确可用的代码写出之前就过度地考虑扩展,重用的问题,使得工程过度复杂。

  1. 先把眼前的问题解决掉,解决好,再考虑将来的扩展问题。
  2. 先写出可用的代码,反复推敲,再考虑是否需要重用的问题。
  3. 先写出可用,简单,明显没有bug的代码,再考虑测试的问题。

Objective-C 编程规范

if 语句不要使用过多分支,分支比较多的情况下要善于使用return来提前返回

推荐:

1
2
3
4
5
6
- (void)someMethod { 
if (!goodCondition) {
return;
}
//Do something
}

不推荐这样写:

1
2
3
4
5
- (void)someMethod { 
if (goodCondition) {
//Do something
}
}

举个典型的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

-(id)initWithDictionary:(NSDictionary*)dict error:(NSError)err{
//方法1. 参数为nil
if (!dict) {
if (err) *err = [JSONModelError errorInputIsNil];
return nil;
    }

    //方法2. 参数不是nil,但也不是字典
    if (![dict isKindOfClass:[NSDictionary class]]) {
        if (err) *err = [JSONModelError errorInvalidDataWithMessage:@"Attempt to initialize JSONModel object using initWithDictionary:error: but the dictionary parameter was not an 'NSDictionary'."];
        return nil;
    }

    //方法3. 初始化
    self = [self init];
    if (!self) {
        //初始化失败
        if (err) *err = [JSONModelError errorModelIsInvalid];
        return nil;
    }
    //方法4. 检查用户定义的模型里的属性集合是否大于传入的字典里的key集合(如果大于,则返回NO)
    if (![self __doesDictionary:dict matchModelWithKeyMapper:self.__keyMapper error:err]) {
        return nil;
    }

    //方法5. 核心方法:字典的key与模型的属性的映射
    if (![self __importDictionary:dict withKeyMapper:self.__keyMapper validation:YES error:err]) {
        return nil;
    }
    //方法6. 可以重写[self validate:err]方法并返回NO,让用户自定义错误并阻拦model的返回
    if (![self validate:err]) {
        return nil;
    }

    //方法7. 终于通过了!成功返回model
    return self;

条件表达式如果很长(超过2个表达式),则需要将他们提取出来赋给一个BOOL值

推荐

1
2
3
4
5
6
7
8

Bool isContains = sessionName.hasPrefix("Swift")
Bool isCurrentYear = sessionDateCompontents.year == 2014

let isSwiftSession = nameContainsSwift && isCurrentYear && user.isLogin
if (isSwiftSession) {
//Do something
}

不推荐

1
2
3
if (sessionName.hasPrefix("Swift") && sessionDateCompontents.year == 2014 && user.isLogin ){
//Do something
}

一个函数的长度必须限制在50行以内

通常来说,在阅读一个函数的时候,如果视需要跨过很长的垂直距离会非常影响代码的阅读体验。如果需要来回滚动眼球或代码才能看全一个方法,就会很影响思维的连贯性,对阅读代码的速度造成比较大的影响。最好的情况是在不滚动眼球或代码的情况下一眼就能将该方法的全部代码映入眼帘。

一个函数只做一件事(单一原则)

每个函数的职责都应该划分的很明确(就像类一样)。

推荐:

1
2
dataConfiguration()
viewConfiguration()

不推荐:

1
2
3
4
5
void dataConfiguration()
{
...
viewConfiguration()
}

函数命名要清晰

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

推荐

1
2
3
- (void) insertObject:(UInteger)atIndex;

- (void) removeObjectAtIndex:(UInteger)atIndex;

不推荐

1
2
- (void) insert:(int)at;
- (void) remove:(int)at;

在 init 和 dealloc 中不要用存取方法访问实例变量
initdealloc 方法被执行时,类的运行时环境不是处于正常状态的,使用存取方法访问变量可能会导致不可预料的结果,因此应当在这两个方法内直接访问实例变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//正确,直接访问实例变量
- (instancetype)init {
self = [super init];
if (self) {
_bar = [[NSMutableString alloc] init];
}
return self;
}

- (void)dealloc {
[_bar release];
[super dealloc];
}

// 错误,不要通过存取方法访问
- (instancetype)init {
self = [super init];
if (self) {
self.bar = [NSMutableString string];
}
return self;
}
- (void)dealloc {
self.bar = nil;
[super dealloc];
}

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

推荐

1
view.backgroundColor = [UIColor orangeColor];

不推荐

1
[view setBackgroundColor:[UIColor orangeColor]];

注释

  1. 公共接口(注释要告诉阅读代码的人,当前类能实现什么功能)。
  2. 涉及到比较深层专业知识的代码(注释要体现出实现原理和思想)。
  3. 容易产生歧义的代码

设计以上三种情况下。都必须写好注释。在xcode中自带快捷注释方法。选好要注释的方法或类。option + command + /

系统常用类作实例变量声明时加入后缀

类型 后缀
UIView View
UILabel Lbl
UIButton Btn
UIImage Img
UIImageView ImgView
NSArray Ary
NSMutableArray Mary
NSDictionary Dict
NSMutableDictionary Mdict
NSString Str
NSMutableString Mstr
NSSet Set
NSMutableSet Mset

CGRect函数

其实iOS内部已经提供了相应的获取CGRect各个部分的函数了,它们的可读性比较高,而且简短,推荐使用

推荐

1
2
3
4
5
6
CGRect frame = self.view.frame; 
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);
CGRect frame = CGRectMake(0.0, 0.0, width, height);

不推荐

1
2
3
4
5
6
CGRect frame = self.view.frame;  
CGFloat x = frame.origin.x;  
CGFloat y = frame.origin.y;  
CGFloat width = frame.size.width;  
CGFloat height = frame.size.height;  
CGRect frame = (CGRect){ .origin = CGPointZero, .size = frame.size };

范型

如果容器元素唯一时,建议在定义NSArray、NSDictionary、NSSet,指定容器的元素

推荐

1
2
@property (nonatimic, readwrite, copy) NSArray <DSRUser*> *userAry;
@property (nonatimic, readwrite, copy) NSDictionary <NSString *,DSRUser*> *userDict

不推荐

1
2
@property (nonatimic, readwrite, copy) NSArray *userAry;
@property (nonatimic, readwrite, copy) NSDictionary * userDict

尽量使用字面量值来创建 NSString , NSDictionary , NSArray , NSNumber 这些不可变对象:

推荐

1
2
3
4
NSArray *nameAry = @[@"zhangsan", @"lishi", @"wangwu"];
NSDictionary *productDict = @{@"iPhone" : @"Kate",
@"iPad" : @"Kamal",
@"Mobile Web" : @"Bill"};

不推荐

1
2
NSArray * nameAry = [NSArray arrayWithObjects:@"zhangsan", @"lishi", @"wangwu", nil];
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys:@"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill"];

属性的命名使用小驼峰

推荐:

1
@property (nonatomic, readwrite, copy) NSString * tipStr;

属性的关键字推荐按照 原子性,读写,内存管理的顺序排列
推荐:

1
2
@property (nonatomic, readwrite, copy) NSString *nameStr;
@property (nonatomic, readonly, assign) NSUInterger old;

Block属性应该使用copy关键字

推荐:

1
2
typedef void (^ErrorCodeBlock) (id errorCode,NSString *message);
@property (nonatomic, readwrite, copy) ErrorCodeBlock errorBlock;

形容词性的BOOL属性的getter应该加上is前缀

推荐

1
@property (nonatomic, getter=isEditable, assign) BOOL editable;

建议尽量把对外公布出来的属性设置为只读,在实现文件内部设为读写,避免多处地方修改属性值,方便管理代码

1
2
在头文件中,设置对象属性为 `readonly`
在实现文件中设置为 `readwrite`

方法名中不应使用and

推荐

1
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;

不推荐

1
- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;

方法实现时,如果参数过长(参数超过3个),则令每个参数占用一行,以冒号对齐

1
2
3
4
5
6
- (void)doSomethingWith:(NSString *)theFoo
                   rect:(CGRect)theRect
               interval:(CGFloat)theInterval
{
//Implementation
}

方法名前缀

  • 刷新视图的方法名要以refresh为首。
  • 更新数据的方法名要以update为首
  • 点击事件的方法实现要以click为首

推荐

1
2
- (void)refreshHeaderViewWithCount:(NSUInteger)count;
- (void)updateDataSourceWithViewModel:(ViewModel*)viewModel;

类遵循代理过多的时候(超过3个协议),换行对齐显示

1
2
3
4
5
6
7
@interface ShopViewController () <UIGestureRecognizerDelegate,
                                  HXSClickEventDelegate,
                                  UITableViewDelegate,
                                  UITableViewDataSource>{
   ...
                 
}

代理的方法需要明确必须执行和可不执行

代理方法在默认情况下都是必须执行的,然而在设计一组代理方法的时候,有些方法可以不是必须执行(是因为存在默认配置),这些方法就需要使用@optional关键字来修饰:

1
2
3
4
5
6
@protocol HomeCellViewDeleagte <NSObject>

@optional
- (void)switchValueChanged:(id)sender;
- (void)clickButtonToDetail:(id)sender;
@end

使用定时器时。要实现startTimer 和 stopTimer 两个函数;实现函数尽量不要加入其他的业务逻辑

1
2
3
4
5
6
7
8
9
10
11
12
- (void) startTimer
{
if (_timer){
[self stopTimer];
}
_timer = ...
}

- (void) stopTimer{
[_timer invalidate];
_timer = nil;
}

方法与参数名,一般都以小写字母开头;对于缩写字符串,可以大写字符开头

1
+ (NSURL *)URLWithString:(NSString *)URLString;

参考文献

https://juejin.im/post/5940c8befe88c2006a468ea6#heading-29

https://github.com/QianKaiLu/Objective-C-Coding-Guidelines-In-Chinese

0%