iOS代码规范

2020-09-08  本文已影响0人  里克尔梅西

前言

由于iOS端项目刚刚创立,又恰逢项目快速迭代,为了快速完成业务功能需求,造成项目人员各自开发,造成了项目代码的不统一。为了增加代码的可读性和可维护性,需要对代码进行统一的规范,以减少项目人员熟悉代码的时间和障碍。此文可作为iOS团队Objective-C语言编程规范,如有更好建议,欢迎提出。

一、核心原则

1、代码应该简洁易懂,逻辑清晰

2、优秀的组织性

3、良好的命名规范

4、良好的注释文档

具体的目录结构、命名规则、注释规则后续详细说明。

二、项目结构

1、整体结构

image

2、主要模块结构

按每个TabBar模块区分,另外拆分出来个人中心、登录注册两大模块,在其他中是Base、nav、tabBar等其他的模块


image

3、子模块结构

在每个子模块中,都采用MVC的文件夹分类,且加入Manager文件夹,进行相应的逻辑Manager的存放


image

三、代码风格

#import "ClassroomTableVC.h"

#import "ClassPracticePhotoVC.h"
#import "ClassPracticeReportVC.h"
#import "PromotePaperVC.h"
#import "PaperPreviewVC.h"
#import "PromoteReportVC.h"
#import "ActivateVC.h"
#import "SpokenEnglishVC.h"
#import "SpokenChineseVC.h"
#import "FeedbackVC.h"
#import "EBookVC.h"
#import "TYAlertController.h"
#import "WrongTrainVC.h"

#import "TripleHeaderView.h"
#import "AVPlayerView.h"
#import "TripleLevelTableViewCell.h"

#import "ClassroomHomeModel.h"
#import "ClassroomShowModel.h"

#import "StudyTaskManager.h"
#import "AMapLocation.h"
#import <CCVodSDK/DWPlayInfo.h>
#import <CCVodSDK/DWPlayerView.h>
#import <CCVodSDK/DWVodVideoModel.h>

#import "UIDevice+TFDevice.h"
#import "UIButton+TouchAreaInsets.h"
#import "UIView+TYAlertView.h"
static CGFloat cellHeight = 48;
static NSString *kTripleLeveleCellId = @"TripleLevelTableViewCell";

@interface ClassroomTableVC () <UITableViewDelegate, UITableViewDataSource, DWVideoPlayerDelegate>
{
    NSInteger selectedIndex;//题目数量选择
}
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) UIView *headerView;
@property (nonatomic, strong) AVPlayerView *playerView;//播放器
@property (nonatomic, strong) TsaaImgView *sectionOneImageView;

@property (nonatomic, strong) ClassroomHomeModel *model;

@property (nonatomic, strong) NSMutableArray *categoryTitleArray;
@property (nonatomic, strong) NSMutableArray *dataArray;
@property (nonatomic, strong) NSString *clickId;
@property (nonatomic, assign) NSInteger openSection;
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self setNav];
    
    [self initSth];
    
    [self configureUI];
}
#pragma mark - Life Cycle

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
}

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)dealloc {

}


#pragma mark - Private

- (void)loadSet {

}

- (void)setNav {

}

- (void)configureUI {

}


#pragma mark - Action
- (void)navTitleAction:(UIButton *)sender {

}


#pragma mark - Request

- (void)loadData {
    [self getSubjectListRequest:NO];
}

- (void)getSubjectListRequest:(BOOL)refresh {

}


四、命名规范

一、统一要求

二、类

三、变量

四、常量

常量应该以驼峰法命名,并以相关类名作为前缀。

推荐:

    static const NSTimeInterval ZXKSignInViewControllerFadeOutAnimationDuration = 0.4;

不推荐:

    static const NSTimeInterval fadeOutTime = 0.4;

推荐使用常量来代替字符串字面值和数字,这样能够方便复用,而且可以快速修改而不需要查找和替换。常量应该用 static 声明为静态常量,而不要用 #define,除非它明确的作为一个宏来使用。

推荐:

    static NSString * const ZXKCacheControllerDidClearCacheNotification = @"ZXKCacheControllerDidClearCacheNotification";
    static const CGFloat ZXKImageThumbnailHeight = 50.0f;

不推荐:
#define CompanyName @"Apple Inc."
#define magicNumber 42

常量应该在头文件中以这样的形式暴露给外部:

    extern NSString *const ZOCCacheControllerDidClearCacheNotification;

并在实现文件中为它赋值。

只有公有的常量才需要添加命名空间作为前缀。尽管实现文件中私有常量的命名可以遵循另外一种模式,你仍旧可以遵循这个规则。

五、属性

1、属性的命名使用小驼峰

推荐这样写:

    @property (nonatomic, readwrite, strong) UIButton *confirmButton;

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

推荐这样写:

@property (nonatomic, readwrite, copy) NSString *name;
@property (nonatomic, readonly, copy) NSString *gender;
@property (nonatomic, readwrite, strong) UIView *headerView;

3、 Block属性应该使用copy关键字

推荐这样写:

typedef void (^ErrorCodeBlock) (id errorCode,NSString *message);
@property (nonatomic, readwrite, copy) ErrorCodeBlock errorBlock;//将block拷贝到堆中

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

推荐这样写:

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

5、使用getter方法做懒加载

实例化一个对象是需要耗费资源的,如果这个对象里的某个属性的实例化要调用很多配置和计算,就需要懒加载它,在使用它的前一刻对它进行实例化:

- (NSDateFormatter *)dateFormatter {
    if (!_dateFormatter) {
           _dateFormatter = [[NSDateFormatter alloc] init];
           NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
           [_dateFormatter setLocale:enUSPOSIXLocale];
           [_dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS"];
    }
    return _dateFormatter;
}

但是也有对这种做法的争议:getter方法可能会产生某些副作用,例如如果它修改了全局变量,可能会产生难以排查的错误。

五、宏

六、枚举

 typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
     AFNetworkReachabilityStatusUnknown = -1,
     AFNetworkReachabilityStatusNotReachable = 0,
     AFNetworkReachabilityStatusReachableViaWWAN = 1,
     AFNetworkReachabilityStatusReachableViaWiFi = 2
};

五、格式规范

一、函数

1、函数尽可能简单明了

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

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

例如:

    [self initData];
    [self configureUI];
3、对于有返回值得函数,每一个分支都必须有返回值
+ (NSString *)getSubjectNameWithSubjectId:(NSString *)subjectId {
    if ([subjectId isEqualToString:@"01"]) {
        return @"语文";
    }
    else if ([subjectId isEqualToString:@"02"]) {
        return @"数学";
    }
    else if ([subjectId isEqualToString:@"03"]) {
        return @"英语";
    }
    else if ([subjectId isEqualToString:@"06"]) {
        return @"物理";
    }
    else if ([subjectId isEqualToString:@"07"]) {
        return @"生物";
    }
    else if ([subjectId isEqualToString:@"08"]) {
        return @"化学";
    }
    else if ([subjectId isEqualToString:@"04"]) {
        return @"道德与法治";
    }
    else if ([subjectId isEqualToString:@"05"]) {
        return @"历史";
    }
    else if ([subjectId isEqualToString:@"09"]) {
        return @"地理";
    }
    else if ([subjectId isEqualToString:@"10"]) {
        return @"科学";
    }
    else if ([subjectId isEqualToString:@"11"]) {
        return @"历史与社会";
    }
    else {
        return @"";
    }
}

最后的else也要加上

4、如果在不同的函数内部有相同的功能,应该把相同的功能抽取出来单独作为另一个函数
5、将函数内部比较复杂的逻辑提取出来作为单独的函数

一个函数内的不清晰(逻辑判断比较多,行数较多)的那片代码,往往可以被提取出去,构成一个新的函数,然后在原来的地方调用它这样你就可以使用有意义的函数名来代替注释,增加程序的可读性。

6、函数名中不应使用and,而且签名要与对应的参数名保持高度一致

推荐这样写:
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;

不推荐这样写:
- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;
- (instancetype)initWith:(int)width and:(int)height;

7、函数中{}的写法

紧跟在函数名后面,不要另起一行写
推荐这样写:

- (instancetype)init {
    self = [super init];
    if (self) {
        
    }
    return self;
}

不推荐这样写:

- (instancetype)init 
{
    self = [super init];
    if (self) {
        
    }
    return self;
}
8、 方法实现时,如果参数过长,则令每个参数占用一行,以冒号对齐

例如:

- (void)doSomethingWith:(NSString *)theFoo
                            rect:(CGRect)theRect
                        interval:(CGFloat)theInterval {
   //Implementation
}

二、if语句

1、每个分支else另起一行,而不是跟在 } 后面

推荐这样写:

if (aaa) {
    return;
}
else {
    return;
}

不推荐这样写:

if (aaa) {
    return;
} else {
    return;
}
2、必须列出所有分支(穷举所有的情况),而且每个分支都必须给出明确的结果。

推荐这样写:

+ (NSString *)isSaveClassroomSubjectId:(NSString *)subjectId {
    if ([subjectId isEqualToString:GetClassRoomSubjectId]) {
        return @"1";
    }
    else {
        return @"";
    }
}

不推荐这样写:

+ (NSString *)isSaveClassroomSubjectId:(NSString *)subjectId {
    if ([subjectId isEqualToString:GetClassRoomSubjectId]) {
        return @"1";
    }
}
3、不要使用过多的分支,要善于使用return来提前返回错误的情况

推荐这样写:

- (void)someMethod  { 
  if (!goodCondition) {
    return;
  }
  //Do something
}

不推荐这样写:

- (void)someMethod  { 
  if (goodCondition) {
    //Do something
  }
}
4、条件表达式如果很长,则需要将他们提取出来赋给一个BOOL值

推荐这样写:

    BOOL isCurrentIndex = (self.currentIndex == 0);
    BOOL shouldScroll = [self.name isEqualToString:@"hello"];
    BOOL isSuccess = isCurrentIndex && shouldScroll;
    if (isSuccess) {
        //Do something
    }

不推荐这样写:

    if (self.currentIndex == 0 &&  [self.name isEqualToString:@"hello"]) {
        //Do something
    }

三、switch语句

推荐这样写:

switch (integer) {  
  case 1: {
    // ...  
   }
    break;  
  case 2: {  
    // ...  
    break;  
  }  
  case 3: {
    // ...  
    break; 
  }
  default: {
    // ...  
    break; 
  }

四、注释

优秀的代码大部分是可以自描述的,我们完全可以用程代码本身来表达它到底在干什么,而不需要注释的辅助。

但并不是说一定不能写注释,有以下三种情况比较适合写注释:

公共接口(注释要告诉阅读代码的人,当前类能实现什么功能)。
涉及到比较深层专业知识的代码(注释要体现出实现原理和思想)。
容易产生歧义的代码(但是严格来说,容易让人产生歧义的代码是不允许存在的)。
除了上述这三种情况,如果别人只能依靠注释才能读懂你的代码的时候,就要反思代码出现了什么问题。

最后,对于注释的内容,相对于“做了什么”,更应该说明“为什么这么做”。

参考文档:
https://www.jianshu.com/p/2093cdaa2f96
https://www.jianshu.com/p/9b10331970a5
https://www.jianshu.com/p/08be5b30ff82
https://www.cnblogs.com/gfxxbk/p/5469017.html
https://www.jianshu.com/p/8165a220e125
https://github.com/oa414/objc-zen-book-cn

上一篇下一篇

猜你喜欢

热点阅读