基础

一篇走心的 iOS 代码规范

2022-03-03  本文已影响0人  大菠萝_DABLO

前言

关于代码规范的重要性这里不做过多解释,能看到这篇文章说明你已经开始重视代码规范了(代码规范看起来是在限制你的自由和发挥,其实它是在间接的帮助你变得更优秀。)。
适当的代码规范和标准绝不是消灭代码内容的创造性、优雅性,而是限制过度个性化,以一种普遍认可的统一方式一起做事,进而提高工作效率,降低沟通成本。
代码的字里行间流淌着的是软件和程序员的血液,质量的提升是尽可能少踩坑、杜绝踩重复的坑,切实提升系统稳定性,码出质量(摘抄自《阿里巴巴Java代码规范》)。
根据约束力度,暂时把规范约定为2个等级,分别是 [必须] 和 [建议]。

(一)命名规范

1. 通用命名规范

Tips:
所有的命名都应该遵循3个基本原则,即“清晰性”、“一致性”、“不要自我指涉”。
正例:
removeObject:、[string stringByReplacingOccurrencesOfString:@"1" withString:@"2"]
反例:
remove:(不清楚,要删除什么?)、string.replace("1", "2")(是将"1"替换成"2"还是将"2"替换成"1"?是将第1个"1"替换成"2"还是将所有的"1"都替换成"2")
正例:
NSDictionary、NSArray、NSSet这几个集合类都是用count来表示数量而不是一个用count其它的用amount或其他单词,这体现了命名的一致性。

@property (readonly) NSUInteger count;
正例:
NSString
反例:
NSStringObject
Tips:
你明白这个缩写的意思不代表其他人也一定会明白,你的代码可能会被任何人阅读,而阅读的人来自不同的地方接受不同的教育不同的文化,所有建议一般不要乱使用缩写,只使用那些国际通用缩写。如果为了缩写创建一个缩写对照表只会增加代码阅读复杂度。
正例:
destinationSelection、setBackgroundColor
反例:
destSel、setBgColor
反例:
DaZhePromotion(打折)
正例:
insertObject:atIndex:
反例:
insert:at:(不清晰,插入什么?at代表什么?)
正例:
secondary
反例:
slave
Tips: 系统保留任意两个字符作为前缀的使用权,
包括但不限于NS、UI、CG、CF、CA、WK、MK、CI、NC;前缀若等于2个字符可以考虑添加_。
正例:
ZT_LoginViewController
反例:
ZTLoginViewController
正例:
NSString *_nameString;
正例:
nameLabel、nameString
反例:
name(name是字符串还是什么?)
正例:
OrderFactory、LoginProxy
正例:
t_label、t_string(t在这里表示temp)

2. 类命名规范

正例:
WXYZ_LoginViewControler WXYZ_表示前缀,Login表示该类跟登录相关,ViewController表示该类是一个视图控制器而不是View。

3. 方法命名规范

Tips: 系统会使用_开头命名一些系统私有方法
Tips: 给私有方法加前缀有2个好处: 
1. 增加辨识度,提高代码可读性。
2. 避免自己的方法无意间覆盖了系统/框架同名的私有方法。
正例:
- (CGSize)cellSize;
反例:
- (CGSize)getCellSize;
正例:
- (void)getCachedResponseForDataTask:(NSURLSessionDataTask *)dataTask 
                 completionHandler:(void (^) (NSCachedURLResponse * __nullable cachedResponse))completionHandler;
正例:
- (void)sendAction:(SEL)aSelector toObject:(id)anObject forAllCells:(BOOL)flag;
反例:
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;
正例:
- (id)viewWithTag:(NSInteger)aTag;
反例:
- (id)taggedView:(int)aTag;
正例:
- (int)runModalForDirectory:(NSString *)path file:(NSString *) name types:(NSArray *)fileTypes;
反例:
- (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;
正例:
- (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag;

4. Protocol命名规范

正例:
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
正例:
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender;

5. Category命名规范

正例:
UIView (YYAdd)
反例:
UIView (Add)
Tips: 尽管从技术上来讲可以在分类中声明属性,但是这么做需要格外小心,
因为它很容易出现内存上或其他一些问题,而且一旦出现问题很难排查。
[建议] 如果一个类比较复杂,那么建议使用分类组织代码(可以参考系统的UIView)。

6. Notification命名规范

正例:
UIApplicationDidBecomeActiveNotification
UIApplication表示该通知属于谁,DidBecomeActive表示该通知的作用,Notification表示它是一个通知。
Tips: 
例如applicationDidBecomeActive:代理方法和NSApplicationDidBecomeActiveNotification通知(这其实也符合命名规范的基本原则"一致性")。

7. 常量命名规范

正例:
// 局部可见
const CGFloat kAnimationDuration = 2.0;
// 全局可见
const CGFloat UIActivityIndicatorViewAnimationDuration = 2.0;

8. Exception命名规范

9. 文件命名规范

正例:
public_back@2x.png

(二)编码规范

1. 通用编码规范

反例:
- (void)dealloc {
  [self unsafeMethod:self];
}
Tips:
对于过时的方法或类,大都是因为其自身有一些缺陷或BUG才会不建议使用,使用新方法时建议了解一下为什么废弃掉旧方法/类。
反例:
CFArrayRef arrayRef = (__bridge CFArrayRef)array;

if (x == YES) return;

CFRelease(arrayRef);

以上代码如果x等于YES的话那么arrayRef对象就会内存泄漏。
反例:
@try {
CFArrayRef arrayRef = (__bridge CFArrayRef)array;
  
do some thing……

CFRelease(arrayRef);
      
} @catch (NSException *exception) {
  
}

以上代码如果do some thing……出现异常的话那么arrayRef就会出现内存泄漏。
Tips: 
宏定义声明常量的缺点:
1. 宏定义只是简单的替换,缺少编译检查,运行期可能会出现溢出或数据错误等问题。
2. 宏定义缺少类型,不方便编写文档用例。
3. 宏定义可能会被不小心替换。
4. 宏定义无法编写注释。

反例:
#define kTime @"10"
  
if (1 == 2) {
#define kTime @"20"
}
  
NSLog(@"time = %@", kTime);

以上代码中的if永远不会执行,但是编译器也会将kTime替换为@"20"
Tips:
函数不通过对象调用,所以不会走OC的消息转发流程,效率远高于方法调用;而且函数会有返回值和参数类型以及参数检查,而这些都是宏定义没有的。

正例:
UIKIT_STATIC_INLINE NSString * kNSStringFromInteger(NSInteger a) {
  return [NSString stringWithFormat:@"%zd", a];
}
反例:
#define kNSStringFromInteger(a) [NSString stringWithFormat:@"%zd", a]
Tips:
只在以下三种情况下才能使用懒加载:
1. 对象的创建需要依赖其他对象
2. 对象可能被使用,也可能不被使用
3. 对象创建比较消耗性能
正例:
i++、++i、
反例:
i ++、++ I
正例:
1 + 2
反例:
1+2
反例:
NSMutableSet *sets = [NSMutableSet set];
NSMutableString *string1 = [NSMutableString stringWithString:@"1"];
[sets addObject:string1];
[sets addObject:@"12"];
  
[string1 appendString:@"2"];

当 [string1 appendString:@"2"] 执行完以后sets对象内会包含2个@"12"。
Tips: 
CGRect t_frame = CGRectMake(-10, -10, -10, -10);
当一个view的frame设置成t_frame后,其坐标会隐式的转换为CGRectMake(-20, -20, 10, 10),因为宽高不可能出现负值;这时通过t_frame.的方式获取的值都是错误的,而CGRectGet会自动帮您处理这些隐式转换。

正例:
CGRectGetWidth(frame)、CGRectGetMinX(frame)、CGRectGetMaxX(frame)
反例:
frame.size.width、frame.origin.x、frame.size.width + frame.origin.x
Tips:
1. 第二行相对第一行缩进4个空格,从第三行起不再继续缩进。
2. 运算符与下文一起换行。
3. 方法调用的点符号与下文一起换行。

正例:
- (void)setImageWithURL:(nullable NSURL *)imageURL
          placeholder:(nullable UIImage *)placeholder
              options:(YYWebImageOptions)options
             progress:(nullable YYWebImageProgressBlock)progress
             ransform:(nullable YYWebImageTransformBlock)transform
           completion:(nullable YYWebImageCompletionBlock)completion;
正例:
@interface WXYZModel : NSObject

@property (nonatomic, readonly) NSString *name;

@end

@interface WXYZModel ()

@property (nonatomic, strong) NSString *name;

@end
Tips: 大而全的类,杂乱无章,使用查找功能才能定位到具体位置,不利于理解也不利于维护。
正例:
缓存相关常量类放在CacheCosts下,系统配置相关常量类放在SystemConfigConsts下。
正例:
int a1 = 1;
long a2 = 3;
NSString *a3 = @"";

反例:
int a1       = 1;
long a2      = 3;
NSString *a3 = @"";
正例:
if (x == 1) {
……
return;
}

if (x == 2) {
……
return;
}

反例:
if (x == 1) {
……
} else if (x == 2) {
……
}
正例:
if (array == nil) {
……
}
反例:
if (!array) {
……
}
Tips:
OC中的NSArray、NSString、NSDictionay、NSNumber都有与之对应的字面量语法: @[]、@""、@{}、@();使用它们有以下优点:
1. 简单易读,提高代码的可读性和可维护性。
2. 使用字面量创建数组、字典时如果元素里在nil则会抛出异常,而使用arrayWithObjects:这些等价方法创建则会丢失nil后的数据,抛出异常能让你知道这里有问题及时修改防止问题在线上发生。

缺点:
1. 使用字面量创建的对象默认是不可变的,如果要创建可变对象需要进行mutableCopy操作。
2. 不支持子类,如果你创建了一个NSString的子类,@""并不会返回你想要的子类对象。

2. 类编码规范

3. 方法编码规范

Tips: 当存在继承关系时使用.语法访问会因为多态关系调用子类的实现方法,而如果这个时候子类还没有初始化好或者已经释放了那么可能会出现一些奇怪的问题。
正例:
method(a1, a2, a3);

4. Block编码规范

Tips: 
对于简单的Block可以使用三目运算进行判空处理,
例如 !self.block ?: self.block();
Tips: 
1. 不一定在Block内使用self才会循环引用,如下情况也会造成循环引用:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  WXYZ_TitleTableViewCell *cell = ………
  
  cell.refreshTableViewBlock = ^{
      [tableView reloadData];
  };
  
  return cell;
}
2. Block内部是否要使用weak需要看Block本身和weak的这个对象是否存在直接或间接的相互引用,若无相互引用则不需要使用weak。

3. 如果Block内部使用了strong修饰了外部的weak变量,那么当使用strong指向成员变量时需要进行判空,否则会崩溃,参考以下代码:
__weak typeof(self) weakSelf = self;
  cell.refreshTableViewBlock = ^{
      __strong typeof(weakSelf) strongSelf = weakSelf;
      if (strongSelf != nil) {
          strongSelf->_name = @"name";
      }
  };
  
如果把(strongSelf != nil)的判断去掉那么可能会崩溃。

5. 通知编码规范

6. 注释编码规范

正例:
// 这是示例注释,请注意在双斜线后有一个空格
- (void)testFunction;
正例:
/**
* @brief 简要描述
* @author 标明开发该类模块的作者
*/
// FIXME: 有bug,需要修改
- (void)testFunction;
Tips: 注释不能美化糟糕的代码。当企图使用注释前,先考虑是否可以通过调整结构,命名等操作,消除写注释的必要。

(三)工程结构规范

正例:
@interface MineViewController ()

@property (nonatomic, weak) UIView *headView;

@property (nonatomic, weak) UITableView *tableView;

我是换行符,请忽略
@property (nonatomic, copy) NSArray *dataSourceArray;
正例:
[self createSubviews];
[self createTableview];

[self netRequest];
#pragma mark - LifeCycle(生命周期相关的代码放在最上面)

- (void)dealloc {}

- (void)viewDidLoad {}

- (void)viewWillAppear:(BOOL)animated {}


#pragma mark - Public(公开方法)

// code...
// 上空一行
// 下空两行


#pragma mark - Private(私有方法)


#pragma mark - Override(需要覆盖父类的方法)


#pragma mark - Notification(通知方法)


#pragma mark - Delegate(Delegate需要实现的方法)


#pragma mark - getter/setter

结语

这只是一篇关于iOS的代码规范,所以某些需要和服务端需要统一的规范(例如错误码)并没有提到,还有些关于如何编写安全代码方面的规范也只是略微提到,因为关于如何写出更安全的代码应该不属于代码规范层面;欢迎大家提出更好的建议或改进,我也会不断更新完善;最后祝大家码出开心,码出质量。

上一篇下一篇

猜你喜欢

热点阅读