美文网首页
iOS代码开发详细规范

iOS代码开发详细规范

作者: 麟young | 来源:发表于2017-06-07 17:40 被阅读52次

    一、命名

    1.属性对象和局部变量(采用小驼峰命名法)

    UI视图对象

    变量名称 + (Label / Button / Cell / TableView / WebView / ScrollView / CollectionView / ImageView / TabBar / TextField ...)
    如:UILabel *titleLabel;
    特殊类型:UIBarButtonItem -> ButtonItem.

    非UI视图对象

    VC: name + (VC / TableVC / CollectionVC / PageVC)
    如: UIPageViewController *firstPageVC;
    特殊类型:

    • UINavigationController -> NavCtroller
    • UITapGestureRecognizer -> TapGesture
    • UILongPressGestureRecognizer -> LongPressGesture

    数据类型: name + 类型(Integer / Int / Long / Float / Double / Number / String / Array / MutableArray / IndexPath / Date / Error...)
    如:NSArray *dataArray;
    特殊类型:

    • NSDictionnary -> Dict
    • NSMutableDictionnary等可变类型变量,使用不可变类型的命名方式如:name + Dict
    • NSTimeInterval -> Double
    • CGRect -> Rect, CGSize -> Size

    注意:1.局部变量可不遵守变量名字后面加数据类型这一规则。 2.Bool类型以is、has、can等作为前缀,或以ing、ed等作后缀。

    其他:

    • name + Model
    • name + ViewModel
    • name + Request(网络请求)
    • name + Block

    注意:尽量为每个变量取有意义的名字,即“name”,但若确实没有name的情况下,直接采用如下形式,如:UITableView *tableView;

    2.实例变量

    实例变量采用小驼峰命名法的基础上,以下划线“_”作为前缀,如:
    UIButton *_loginButton;

    3.常量 和 宏常量

    普通常量:以小写字母“k”开头的驼峰命名法,如:static NSString *const kMovieCellHeight;
    通知常量:一般形式为[ 触发通知的类名] + [Did 或 Will] + [ 动作 ] + Notification ;
    宏常量:全部大写,中间用下划线“_”作间隔,如:#define TARGET_OS_IOS
    注意:

    • 如果宏常量和APP业务相关则添加相应业务类名作前缀,如果适用于整个APP则直接使用APP前缀,如:#define MOHomeCellHeaderHeight (18.0)
    • 尽量不用宏来定义常量,而是采用枚举和const定义的常量

    4.枚举类型名称 和 枚举变量名称

    定义枚举类型:采用OC风格定义枚举类型,举个栗子:

    typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {  
        UIViewAnimationTransitionNone,         //默认从0开始  
        UIViewAnimationTransitionFlipFromLeft,  
        UIViewAnimationTransitionFlipFromRight,  
        UIViewAnimationTransitionCurlUp,  
        UIViewAnimationTransitionCurlDown,  
    };
    
    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  
    };
    
    其中:    
    1.枚举类型名称采用首字母大写的驼峰命名法,并且添加相关类作为前缀    
    (本例中相关的类为UIView)
    2.枚举值命名要添加枚举类型名作前缀    
    (本例中枚举值前缀分别为UIViewAnimationTransition和UIViewAutoresizing)
    
    

    枚举变量名称和类型名称基本保持一致的情况下,尽量增强其可阅读性,采用小驼峰命名法,如:

    UIViewAnimationTransition animationTransitionType; 
    

    5.方法名

    采用小驼峰命名法,做到见其名知其含义,本身具有充分的解释性,拒绝无意义命名。
    例外情况:

    • 可以用一些通用的大写字母缩写打头的方法,比如 PDF,TIFF
    • 可以用带下划线的前缀来命名私有方法或者类别中的方法

    6.类名 和 协议名

    类名:“统一的类名前缀” + “功能模块简称”(可省略) + “描述该类的词或词组” + 控件类型名尾缀(如Cell,ViewController,View等,可省略)
    协议名:类名 + “Delegate”
    都采用大驼峰命名法。

    7.分类的名称 和 分类中方法名

    分类的名称和类名的命名方式基本一致,只是前缀由开发者指定。
    但分类中的方法名,需使用分类的前缀小写形式 + “_”的形式,如:

    @interface NSDate (MOLocalDate)
    
    /// 获取根据时间间隔按照系统时间计算的日期,seconds:单位(秒)
    + (NSDate *)mo_dateWithTimeIntervalSince1970:(NSTimeInterval)seconds;    
    
    
    

    8.Assets.xcassets文件命名

    1.功能模块使用的资源,使用功能模块代码资源同样的文件夹结构(一级或多级)和名称。
    2.公共或通用资源文件夹取名为“Common”或“Normal”
    3.图片资源命名方式采用全部小写和分割线分隔的形式,前缀为功能模块名称或缩写,如:
    common_navigation_bar_back

    9.功能模块内文件夹命名

    一般情况下,各功能模块下的文件夹命名包括但不限于以下命名:

    • View 或 CustomView
    • Model
    • ViewController
    • Cell
    • ViewModel (MVVM模式下使用)
    • Helper
    • ……

    10.Xib或Storyboard中命名

    • Xib文件名和对应类名保持一致
    • Xib或者Storyboard中的控件,命名采用全部大写且用一个空格分隔的形式。添加控件时立即添加有意义的命名,方便查看不同控件的约束关系以及后期维护。

    总结:命名尽量不要用单词简写,注重保持语义明确;名称保持一致性,避免同样语义出现多种单词如“number”和“count”;命名风格统一,遵守统一的的命名规则。

    二、风格

    1.关于空格与空行

    • 数组或字典字面量用逗号隔开的形式书写时,逗号后留有一个空格
    • ifswitch语句中,这两个关键字和紧跟的括号之间留有一个空格
    • 二元符号如"="、"=="、">"、"<"、"||"、"&&"等两侧留有一个空格
    • 三元符号中"?"和":"两侧留有一个空格
    • 方法名“+”和“-”号和方法名中间留有一个空格;方法实现中“{”和方法名之间留有一个空格。
    • 定义属性时,逗号后留有一个空格,如:
    @property (nonatomic, copy, readonly) NSString *titleString;    
    小括号左右两边各用一个空格与外部隔开;
    星号“*”左边留有一个空格。
    
    • 声明类时
    @interface MOCalendarService : NSObject
    冒号左右各用一个空格隔开
    
    • 声明分类或延展(类目)时
    /// 分类
    @interface NSDate (MOLocalDate)
    /// 延展(类目)
    @interface ClassName () <UICollectionViewDelegate,    
     UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
    
    - 小括号左右两边各用一个空格与外部隔开
    - 协议列表中,逗号后留有一个空格
    
    • 方法与方法之间空一行,方法内的代码块与代码块之间都空一行
    • #prgma mark - Demo分隔语句上方留有一空行,下方不留空行或留有一空行。

    2.注释

    • 在需要注释的方法或属性上方使用Command + Alt + /”快捷键,或者“/// + 一个空格 + 说明”的形式进行注释,方便别处使用“Alt + 鼠标点按”直接查询该方法或属性的定义。
      (注意:在属性右侧使用“/// + 一个空格 + 说明”的形式进行注释,在别处无法用“Alt + 鼠标点按”快捷键获取注释详情)
    • 针对方法内某一行代码语句进行注释,可以在此行代码上方或右侧进行“// + 一个空格 + 说明”的形式进行注释,注意将该代码段和其它代码段用注释行或空白行进行分隔。
    • 针对方法内某几行代码段进行注释,也注意将该代码段和其它代码段用注释行或空白行进行分隔。

    总结:

    • 注释方法和属性尽量方便代码使用时用快捷键获取注释内容
    • 注释内容尽量让不懂代码的人能看懂在做的事情
    • 注释代码行或代码段要明确区分注释区域
    • 典型的需要注释的地方包括:头文件中类名、属性和方法注释,ifswitch语句注释,魔术数字或字符串注释
    • 注释不要和Mark混淆使用。

    3.Mark分区

    建议开发时使用Xcode右下角的代码快捷(Code Snippet Library)方式保存和提取常用文件Mark分区结构。
    针对VC:

    #pragma mark - View Lifecycle
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self buildingUI];
        [self makeViewConstraints];
        [self bindViewModel];
    }
    
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
    }
    
    - (void)viewWillDisappear:(BOOL)animated {
        [super viewWillDisappear:animated];
    }
    
    #pragma mark - buildingUI
    - (void)buildingUI {
    }
    
    #pragma mark - Make Constraints
    - (void)makeViewConstraints {
    }
    
    #pragma mark - bindViewModel
    - (void)bindViewModel {
    }
    
    #pragma mark - Delegate Methods
    
    #pragma mark - Publich Methods
    
    #pragma mark - Private Methods
    
    #pragma mark - Notification Event
    
    #pragma mark - Property Set
    
    #pragma mark - Property Get
    
    

    针对ViewModel:

    #pragma mark - Life Cycle
    - (instancetype)init {
        self = [super init];
        if (self) {
            [self binding];
        }
        
        return self;
    }
    
    #pragma mark - Bind
    - (void)binding {
        
    }
    
    #pragma mark - Publich Methods
    
    #pragma mark - Private Methods
    
    #pragma mark - Property Set
    
    #pragma mark - Property Get
    
    
    

    针对TableViewController:

    #import "<#ControllerName#>.h"
    
    // static NSString *const <#cellNameId#> = @"<#cellId#>";
    
    @interface <#ControllerName#> () <UITableViewDelegate, UITableViewDataSource>
    
    // @property (nonatomic, strong) <#ViewModolClass#> *viewModel;
    @property (nonatomic, strong) UITableView *tableView;
    
    @end
    
    @implementation <#ControllerName#>
    
    #pragma mark - View Lifecycle
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self buildingUI];
        [self makeViewConstraints];
    }
    
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
    }
    
    - (void)viewWillDisappear:(BOOL)animated {
        [super viewWillDisappear:animated];
    }
    
    #pragma mark - buildingUI
    - (void)buildingUI {
        self.title = @"<#TitleName#>";
    }
    
    #pragma mark - Make Constraints
    - (void)makeViewConstraints {
        [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.edges.equalTo(self.view);
        }];
    }
    
    #pragma mark - UITableViewDataSource && UITableViewDelegate
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
        return 1;
    }
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        return <#countInteger#>;
    }
    
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
        return 44.f;
    }
    
    - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
        return 0.1f;
    }
    
    - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
        return 0.1f;
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:<#cellNameId#>];
        if (!cell) {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:<#cellNameId#>];
        }
        return cell;
    }
    
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
        [tableView deselectRowAtIndexPath:indexPath animated:NO];
        // do something
        
    }
    
    #pragma mark - Publich Methods
    
    #pragma mark - Private Methods
    
    #pragma mark - Notification Event
    
    #pragma mark - Property Set
    
    #pragma mark - Property Get
    // - (<#ViewModolClass#> *)viewModel {
    //  if (!_viewModel) {
    //      _viewModel = [[<#ViewModolClass#> alloc] init];
    //  }
    //  return _viewModel;
    //}
    
    - (UITableView *)tableView {
        if (!_tableView) {
            _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
            _tableView.delegate = self;
            _tableView.dataSource = self;
            // _tableView.tableFooterView = [[UIView alloc] init];
            // _tableView.showsVerticalScrollIndicator = NO;
            // _tableView.contentInset = UIEdgeInsetsMake(0, 0, 50, 0);
            [self.view addSubview:_tableView];
        }
        return _tableView;
    }
    
    @end
    
    
    

    针对Cell:

    #pragma mark - Init Method
    - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
        self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
        if (self) {
            [self makeViewConstraints];
        }
        return self;
    }
    
    #pragma mark - 约束布局
    - (void) makeViewConstraints {
    }
    
    #pragma mark - Public Methods
    
    #pragma mark - Private Methods
    
    #pragma mark - Property Set
    
    #pragma mark - Property Get
    
    

    针对自定义View:

    #pragma mark - Init Method
    - (instancetype)initWithFrame:(CGRect)frame {
        self = [super initWithFrame:frame];
        if (self) {
            [self makeViewConstraints];
        }
        return self;
    }
    
    - (instancetype)initWithCoder:(NSCoder *)aDecoder {
        self = [super initWithCoder:aDecoder];
        if (self) {
            [self makeViewConstraints];
        }
        return self;
    }
    
    #pragma mark - 约束布局
    - (void) makeViewConstraints {
    }
    
    #pragma mark - Publich Methods
    
    #pragma mark - Private Methods
    
    #pragma mark - Property Set
    
    #pragma mark - Property Get
    
    

    4.对齐

    • 所有代码段使用四个空格进行缩进
    • 名字太长的方法定义或调用,可采用冒号对齐的方式多行展示
    • 数组和字典字面量跨行书写时“@”符号对齐:
    /// 我是数组
    NSArray *demoArray = @[@"Object-C",
                           @"Swift",
                           @"Python",
                           @(YES),
                           @(1)
                          ];
    /// 我是字典
    NSDictionary *demoDict = @{@"Object-C":@"Object-C",
                               @"Swift":@"Swift",
                               @"Python":@"Python",
                               @"BoolValue":@(YES),
                               @"Number":@(1)
                              };
    其实,上面两个例子中,中括号和大括号的始末位置也进行了对齐。
    
    • if语句中,左大括号“{”不跨行,并和右括号“)”之间留有一个空格; 右大括号“}”和对应的“if”保持左端对齐; else语句和左侧“}”以及右侧“{”在同一行,举个例子:
    /// 我是 if 语句
    if (18 == self.age) {
        if (self.isGirl) {
            NSLog(@"Marry me!");
        } else {
            /// do something
        }
    }
    
    建议使用if语句时,直接选择苹果提供的快捷代码段。
    
    
    • 所有枚举值与最左侧保持四个空格的缩进
    • 非线程安全的属性以"@property (nonatomic, ...)"打头的形式书写,代码更工整
    • block中嵌套block时,注意每个"}"和对应的代码起点对齐

    三、开发习惯

    1.Switch语句每个case后添加“{}”的习惯

    - (void)sampleForSwitch {
        SampleEnum testEnum = SampleEnumTwo;
        switch(testEnum) {
            caseSampleEnumUndefined: {
                // do something
                break;
            }
            caseSampleEnumOne: {
                // do something
                break;
            }
            caseSampleEnumTwo: {
                // do something
                break;
            }
            default: {
                NSLog(@"WARNING: there is an enum type not handled properly!");
                break;
            }
        }
    }
    

    2.先建立实体文件夹,后在工程中引入

    3.条件语句

    简单条件判断推荐使用三目运算符“? :”,如:

    result = object ? : [self createObject];
    
    注意这里“?”后写成空格,会直接返回object的情况,不建议用下面这种形式:
    result = object ? object : [self createObject];    
    
    
    

    4.Bool赋值

    简单的条件判断后赋布尔值的逻辑,可以省略if语句,如:

    BOOL isAdult = age > 18;
    
    而不是:
    BOOL isAdult;
    if (age > 18) {
        isAdult = YES;
    }
    else {
        isAdult = NO;
    }
    

    5.拒绝魔术数字和字符串

    举个例子:

    /// 反例1 “Nissan”字符串突然横空出现
    if (carName == "Nissan")
    /// 反例2 “18”是个什么意思~
    if (age > 18) { ... }
    
    推荐下面的方式:
    /// 用枚举类型代替字符串类型(由于是数字类型比较,编译速度比字符串类型更为高效)
    if (car == Car.Nissan)
    /// 提前为魔术数字定义常量,同时方便添加注释(即明确数字含义后再继续写代码)
    const int adultAge = 18; 
    if (age > adultAge) { ... }
    
    

    6.readonly属性

    不需要修改属性的地方,心怀添加readonly的念想。

    7.copy属性

    对于有可变类型子类的数据类型,使用copy属性防止数据被更改,如NSString、NSArray、NSDictionary。

    8.使用字面量方式初始化数据,增强可读性

    9.复杂判断简单化

    if (job.JobState == JobState.New
        || job.JobState == JobState.Submitted
        || job.JobState == JobState.Expired
        || (job.JobTitle && job.JobTitle.length) {
        ....
    }
    
    可以被优化为:
    /// 首先将上面整体提炼为一个方法
    if ([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;
    }
    
    

    10.嵌套判断平行化

    BOOL isValid = NO;
    if (user.UserName) {
        if (user.Password) {
            if (user.Email) {
                isValid = YES;
            }
        }
    }
    return isValid;
    
    可以被优化为:
    
    if (!user.UserName) return NO;
    if (!user.Password) return NO;
    if (!user.Email) return NO;
    
    return YES;
    
    

    11.回调方法加调用者的习惯

    如经典的:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
    
    添加调用者tableView作为参数,方便信息传递和区分调用者。
    

    12.NSTimer、观察者、通知、网络请求等注意在Dealloc或viewWillDisappear:方法中移除。同一个文件中,移除的顺序应和创建的顺序保持一致,方便后期维护(注意:工程中使用RAC或已经封装好的方法不需要作移除操作)。

    13.访问或操作NSArray、NSDictionary等对象时,注意判断对象和对象内要访问的元素是否为nil。

    14.虽然很简单,但尽量不用new方法而是统一采用Cocoa规范中的[[ClassName alloc] init]方法

    15.Delegate使用weak属性修饰

    @property (nonatomic, weak) delegate;
    

    16.提交代码前保证无warning和error

    17.随时注释别人或未来的自己有可能看不懂的代码

    18.逻辑捋清楚前不要写代码,因为一定会重写。

    四、设计思想

    1.精简

    • 方法不超过约一百行,否则就要考虑拆分
    • 清除:无用类、方法、资源、注释、多余空行、Log语句、警告

    2.分工明确

    • .h文件:核心属性和方法声明地带 (.h文件由于不参与实现过程,用@class引用类,将#import "Demo.h"形式的引入统一写在.m文件中)
    • .m文件:私有方法、私有变量声明以及所有方法的实现基地
    • 分类:不常用、或与主业务无关的方法聚居地(不要滥用分类)
    • View层:UI搭建(MVC设计模式下可以进行简单的数据展示)
    • Model层:展示最直白的数据结构
    • ViewController层:
      • 1.容器:容纳子VC,构建View布局和展示
      • 2.控制Viewmodel
      • 3.响应UI事件(或信号)和代理方法
      • 4.不同生命周期逻辑处理
    • ViewModel层:用来且只用来处理和数据相关的一切
      • 1.数据初始化
      • 2.请求网络数据
      • 3.数据业务处理:数据持久化、筛选、排序、验证、增删改查
      • 4.将处理后的数据在View上展示
      • 5.头文件中返回最终有效readonly数据
    • 网络层:分担ViewModel层网络请求职责

    3.团队一致性

    上述所有,共同自律,提高整体效率。

    相关文章

      网友评论

          本文标题:iOS代码开发详细规范

          本文链接:https://www.haomeiwen.com/subject/fndafxtx.html