《抛砖引玉》iOS开发规范文档

【前言】

一份合格的代码不应只满足于实现功能, 更应该遵循良好的规范. 遵循良好的代码规范有利于:

  • 统一标准, 提升多人协作效率;
  • 方便新人快速上手, 在项目组人员发生变动时保证项目进度;
  • 提升程序稳定性, 减少代码隐患, 降低故障率;
  • 增强可扩展性, 大幅提高维护效率;

这也是我整理这份文档的初衷,技术有限,抛砖引玉希望大家多多指导。

【目录】

  • 命名规范
  • 注释规范
  • UI/布局规范
  • 编码规范
  • 资源文件规范
  • 版本规范
  • 其他规范

  • 系统设计
  • 开发流程
  • 持续集成
  • 调试技巧

【命名规范】

不允许出现中文命名方式+采用驼峰命名法。

项目命名
  • 项目名都遵循大驼峰命名。例如:AliTaoBaoProject
Bundle Identifier 命名
  • 采用反域名命名规范,全部采用小写字母,以域名后缀+公司顶级域名+应用名形式命名。例如:com.ali.taobao
Class类名
  • 类的命名都遵循大驼峰命名。一般是:前缀 + 功能 + 类型。例如:WN + Login + ViewController,在实际开发中,一般都会给工程中所有的类加上属于本工程的前缀。
  • 常用控件类命名类型对照表(下表中前缀为:WN,如果用到下表中没有列举出来,请去掉UI首字母,遵循实际规则即可。)
控件名 类型 示例
UIViewController ViewController WNBaseViewController
UView ViewController WNBaseView
UITableView TableView WNOrderTableView
UITableViewCell TableViewCell WNOrderListCell
UIButton Button WNSuccessButton
UILabel Label WNSuccessLabel
UIImageView ImageView WNGoodsImgView
UITextField TextField WNNameTextField
UITextView TextView WNSuggestTextView
对象命名
  • 用小驼峰方式。例如:UIViewController * userVc = [[UIViewController alloc] init ];
常量
  • 宏:小写k+大驼峰 即为:#define kUserGaoDeKey @“gaodeKey”

  • 全局常量:工程前+缀全大写,下划线隔开 即为:extern const NSString WN_USER_AGE_KEY

Resource文件
  • 全部小写,采用下划线命名法,加前缀区分。所有的资源文件都需要加上工程前缀(小写形式)。
  • 命名模式:可加后缀_small表示小图,_big表示大图,逻辑名称可由多个单词加下划线组成,采用以下规则:

用途_模块名_逻辑名称
用途_模块名_颜色
用途_逻辑名称
用途_颜色

例如:img_home_banner

版本号命名规范
  • 采用A.B.C 三位数字命名,比如:1.0.2,当有版本更新的时候,依据下面的情况来确定版本号规范
版本号 说明 示例
A.b.c 属于重大更新内容 1.0.0 -> 2.0.0
a.B.c 属于小部分更新内容 1.0.2 -> 1.2.2
a.b.C 属于补丁更新内容 1.0.2 -> 1.0.4
函数命名
  • OC的命名方法通常比较长,是为了让程序有更好的可读性,目的是为了可以当成一个句子的形式朗读出来,达到见名知意的效果

  • 方法一般以小写字母打头,每一个后续的单词首字母大写,方法名中不应该有标点符号(包括下划线),有两个例外:

可以用一些通用的大写字母缩写打头方法,比如PDF,TIFF等。

可以用带下划线的前缀来命名私有方法或者类别中的方法。
  • 如果方法表示让对象执行一个动作,使用动词打头来命名,注意不要使用do,does这种多余的关键字,动词本身的暗示就足够了:
//动词打头的方法表示让对象执行一个动作
- (void)invokeWithTarget:(id)target;
- (void)selectTabViewItem:(NSTabViewItem *)tabViewItem;
  • 如果方法是为了获取对象的一个属性值,直接用属性名称来命名这个方法,注意不要添加get或者其他的动词前缀:
//正确,使用属性名来命名方法
- (NSSize)cellSize;
 
//错误,添加了多余的动词前缀
- (NSSize)getCellSize;

  • 对于有多个参数的方法,务必在每一个参数前都添加关键词,关键词应当清晰说明参数的作用:
//正确,保证每个参数都有关键词修饰
- (void)cycleScrollView:(SDCycleScrollView *)cycleScrollView didSelectItemAtIndex:(NSInteger)index
//错误,遗漏关键词
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;
//正确
- (id)viewWithTag:(NSInteger)Tag;
//错误,关键词的作用不清晰
- (id)taggedView:(int)Tag;
  • 不要用and来连接两个参数,通常and用来表示方法执行了两个相对独立的操作(从设计上来说,这时候应该拆分成两个独立的方法):
//错误,不要使用and来连接参数
- (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;
//正确,使用and来表示两个相对独立的操作
- (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag;
通知命名
  • 通知常用于在模块间传递消息,所以通知要尽可能地表示出发生的事件,通知的命名方式是:
code[触发通知的类名] + [ Did | Will ] + [动作] + Notification

举个栗子

NSApplicationDidBecomeActiveNotification
NSWindowDidMiniaturizeNotification

命名规范总结

清晰
  • 命名应该尽可能的清晰和简洁,但在Objective-C中,清晰比简洁更重要
//清晰
insertObject:atIndex:
//不清晰,insert的对象类型和at的位置属性没有说明
insert:at:
  • 不要使用单词的简写,拼写出完整的单词
//清晰
destinationSelection:setBackgroundColor:
 
//不清晰,不要使用简写
destSel:setBkgdColor:
  • 命名方法或者函数时要避免歧义
//有歧义,是返回sendPort还是send一个Port?
sendPort
 
//有歧义,是返回一个名字属性的值还是display一个name的动作?
displayName

  • 命名统一使用驼峰命名法;只采纳有广为人知含义的缩写,比如infomsgUIHTTP这类。自造的缩写不被认可。总体的命名原则是清晰和一致,避免歧义。

  • 类名需要结合项目名称来命名,确保整个项目中的自定义类的名称开头是统一的,同样要确保类名需要大写字母开头。

  • 类名命名需结合功能或者模块,并且尾部要带上该类的类型,比如
    UIViewController的子类命名为JasonIndexViewController。
    UIViewController 后缀添加“Controller”
    UIView 后缀添加“View”
    UIButton 后缀添加“Button"或者"Btn"
    UILabel 后缀添加“Label"

一致性
  • 整个工程的命名风格要保持一致性,最好和苹果SDK的代码保持统一。不同类中完成相似功能的方法应该叫一样的名字,比如我们总是用count来返回集合的个数,不能在A类中使用count而在B类中使用getNumber。

【注释】

文件注释
/*******************************************************************************
    Copyright (C), 2011-2013, Andrew Min Chang
 
    File name:  AMCCommonLib.h
    Author:     Andrew Chang (Zhang Min) 
    E-mail:     [email protected]
 
    Description:    
            This file provide some covenient tool in calling library tools. One can easily include 
        library headers he wants by declaring the corresponding macros. 
            I hope this file is not only a header, but also a useful Linux library note.
 
    History:
        2012-??-??: On about come date around middle of Year 2012, file created as "commonLib.h"
        2012-10-10: Change file name as "AMCCommonLib.h"
        2012-12-04: Add UDP support in AMC socket library
        2013-01-07: Add basic data type such as "sint8_t"
        2013-01-18: Add CFG_LIB_STR_NUM.
        2013-01-22: Add CFG_LIB_TIMER.
        2013-01-22: Remove CFG_LIB_DATA_TYPE because there is already AMCDataTypes.h
 
    Copyright information: 
            This file was intended to be under GPL protocol. However, I may use this library
        in my work as I am an employee. And my company may require me to keep it secret. 
        Therefore, this file is neither open source nor under GPL control. 
 
********************************************************************************/
/*************************************************************
 * Copyright (c)  xxx科技有限公司
 * All rights reserved.
 *
 * 文件名称:        xxx
 * 文件标识:        xxx
 * 摘要说明:        xxx
 * 
 * 当前版本:        1.0.0
 * 作    者:       CPX
 * 更新日期:            
 * 整理修改:    
 *
 ***************************************************************/

文件注释的格式通常不作要求,能清晰易读就可以了,但在整个工程中风格要统一。

代码注释
  • 方法、函数、类、协议、类别的定义都需要注释,推荐采用Apple的标准注释风格,好处是可以在引用的地方alt+command+/点击自动弹出注释,非常方便
/**
 *  Get the COPY of cloud device with a given mac address.
 *
 *  @param macAddress Mac address of the device.
 *
 *  @return Instance of IPCCloudDevice.
 */
-(IPCCloudDevice *)getCloudDeviceWithMac:(NSString *)macAddress;
  • 协议、委托的注释要明确说明其被触发的条件
// Individual rows can opt out of having the -editing property set for them. If not implemented, all rows are assumed to be editable.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath;

【UI布局规范】

  • 建议项目统一使用Masonry和xib结合的方式布局。不允许出现直接设置frame的情况。如果是纯代码的项目,不允许出现xib和拉约束的情况。不允许纯storyboard开发。
  • 提取方法,去除重复代码。对于必要的工具类抽取也很重要,这在以后的项目中是可以重用的。
  • 尽可能的使用局部变量
  • 尽量减少对变量的重复计算
  • 尽量在合适的场合使用单例。使用单例可以减轻加载的负担,缩短加载的时间,提高加载效率。注意:并不是所有的地方都适用于单例

【编码规范】

函数的书写

方法长度建议不超过80行,如果方法太长可以考虑抽取其中一部分
方法(-、+)和返回值前面的左括号间隔一个空格,方法参数直接间隔一个空格。每个方法结束后需间隔一行来书写新方法

- (void)applicationDidEnterBackground:(UIApplication *)application

- (void)applicationWillResignActive:(UIApplication *)application 

如果一个函数有特别多的参数或者名称特别长,将其按照:来对齐分行显示

-(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;
使用函数的调用

函数调用的格式和书写的差不多,可以按照函数的长短选择写在一行或者分成多行

//写在一行
[myObject doFooWith:arg1 name:arg2 error:arg3];
 
//分行写,按照 : 对齐
[myObject doFooWith:arg1
               name:arg2
              error:arg3];
 
//第一段名称过短的话后续可以进行缩进
[myObj short:arg1
          longKeyword:arg2
    evenLongerKeyword:arg3
                error:arg4];
使用#pragma mark 来分类方法,参考以下结构,通常将不常用的函数方法写在底部,这里强制要求懒加载必须写在底部
#pragma mark – Life Cycle

#pragma mark - Events

#pragma mark – Private Methods

#pragma mark - UITextFieldDelegate

#pragma mark - UITableViewDataSource

#pragma mark - UITableViewDelegate

#pragma mark - Custom Delegates

#pragma mark – Getters and Setters

枚举的定义参考系统定义枚举方式
typedef NS_ENUM(NSUInteger, UISearchBarStyle) {
    UISearchBarStyleDefault,    // currently UISearchBarStyleProminent
    UISearchBarStyleProminent,  // used my Mail, Messages and Contacts
    UISearchBarStyleMinimal     // used by Calendar, Notes and Music
}
if和case语句,不论if或者else下有一个还是多个语句,都必须带上大括号。同样case语句也是如此。
//正确
if (!error) {
  return success;
}
//错误
if (!error)
  return success;
或  
if (!error) return success;

布尔值推荐写法
if (someObject) {
    //...
}
if (![anotherObject boolValue]) {
    //...
}
当需要提高代码的清晰性和简洁性时,三元操作符才会使用。
//推荐写法
NSInteger value = 5;
result = (value != 0) ? x : y;

BOOL isHorizontal = YES;
result = isHorizontal ? x : y;

//不推荐写法
result = a > b ? x = c > d ? c : d : y;

CGRect函数

//推荐写法
CGRect frame = self.view.frame;
CGFloat x = CGRectGetMinX(frame);
CGFloat y = CGRectGetMinY(frame);
CGFloat width = CGRectGetWidth(frame);
CGFloat height = CGRectGetHeight(frame);

//不推荐的写法
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;

当使用条件语句编码时,不要嵌套if语句,多个返回语句也是OK

//推荐写法
- (void)someMethod {
  if (![someOther boolValue]) {
    return;
  }

  //Do something important
}

//不推荐写法
- (void)someMethod {
  if ([someOther boolValue]) {
    //Do something important
  }
}

单例模式
//单例对象应该使用线程安全模式来创建共享实例
+ (instancetype)sharedInstance {
  static id sharedInstance = nil;

  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedInstance = [[self alloc] init];
  });

  return sharedInstance;
}

闭包

block 的右括号"}"应该和调用block 那行代码的第一个非空字符对齐
block 内的代码采用一个tab(四个空格的距离)的缩进
如果block 过于庞大,应该单独声明一个变量来使用

//分行书写的block,内部使用一个tab的缩进
[operation setCompletionBlock:^{
    [self.delegate newDataAvailable];
}];

//使用C语言API调用的block遵循同样的书写规则
dispatch_async(_fileIOQueue, ^{
    NSString* path = [self sessionFilePath];
    if (path) {
      // ...
    }
});

//庞大的block应该单独定义成变量使用
void (^largeBlock)(void) = ^{
    // ...
};
[_operationQueue addOperationWithBlock:largeBlock];

//在一个调用中使用多个block,通过进行一个tab缩进
[myObject doSomethingWith:arg1
    firstBlock:^(Foo *a) {
        // ...
    }
    secondBlock:^(Bar *b) {
        // ...
    }
];

【系统设计】

  • 推荐使用Swift语言;
  • 推荐使用MVVM架构;
  • 推荐采用模块分类方式替代文件类别方式, 方便快速查找模块相关内容;
隐私协议
  • 根据法律法规,目前的APP都需要加入《隐私政策》的选项以供用户体验,所以在APP第一步就是添加该功能。
// 强制退出App
- (void)exitApplication {
    
    UIWindow * window = [[[UIApplication sharedApplication] delegate] window];

    [UIView animateWithDuration:0.2f animations:^{
        window.alpha = 0;
    } completion:^(BOOL finished) {
        exit(0);
    }];
}
版本控制更新
  • 程序在发布初始1.0版本的时候建议加上版本控制。
  • 分为强制更新和选择更新,由管理后台进行控制。
  • 具体功能开发根据自己情况来制定。
Crash搜集

1:注册

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

NSSetUncaughtExceptionHandler(handleExceptionAndTalk);

return YES;

}

2:实现handleExceptionAndTalk方法

- (void)handleExceptionAndTalk(NSException *exception){

NSString *content = [NSString stringWithFormat:@"========异常错误报告========\nname:%@\nreason:\n%@\ncallStackSymbols:\n%@",name,reason,[callStack componentsJoinedByString:@"\n"]];

//保存异常信息

NSMutableDictionary *info = [NSMutableDictionary dictionary];

info[@"name"] = [exception name];                          // 异常名字

info[@"reason"] = [exception reason];                      // 异常描述(报错理由)

info[@"callStackSymbols"] = [exception callStackSymbols];  // 调用栈信息(错误来源于哪个方法)

//写入沙盒

NSString *path =[NSHomeDirectory() stringByAppendingString:@"/crash.plist"];

[info writeToFile:path atomically:YES];

//  把异常崩溃信息发送至开发者邮件

NSMutableString *mailUrl = [NSMutableString string];

[mailUrl appendString:@"mailto:[email protected]"];

[mailUrl appendString:@"?subject=程序异常崩溃,请配合发送异常报告,谢谢合作!"];

[mailUrl appendFormat:@"&body=%@", content];

// 打开地址

NSString *mailPath = [mailUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

[[UIApplication sharedApplication] openURL:[NSURL URLWithString:mailPath]];

}

当用户使用软件过程中程序崩溃,我们可以及时捕获,并得到日志进行错误分析。
我们这里也可以根据自己业务逻辑来进行相关处理,也可以使用第三方工具来实现,例如:Bugly

CocoaPods管理第三方库
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, ‘9.0’
use_frameworks!
target 'AliTaoBao' do
  
    pod 'MJRefresh' , '3.1.15.7'
    pod 'MJExtension', '3.0.15.1'
    pod 'SDWebImage'  , '4.4.2'
    pod 'AFNetworking' , '3.2.1'
    pod 'MBProgressHUD', '0.9.2'
    pod 'SDCycleScrollView' , '1.75'
    pod 'IQKeyboardManager' , '6.2.0'
    pod 'Masonry' , '1.1.0'
    pod 'Bugly' , '2.5.0'

end
pod update

pod update --verbose --no-repo-update
网络请求
  class func tokenRequest(isShow: Bool = true,
                            method: NetMethod = .POST,
                            url: String,
                            parameter: [String: AnyObject]? = nil ,
                            finished: @escaping (_ result: [String: AnyObject]?, _ issuccess: Bool, _ msg: String?) -> ()) {
        
        guard let token = UserModel.shared.token else {  return }
             
        var para = parameter
        
        if para == nil {
            
            para = [String : AnyObject]()
        }
        
        para!["uid"] =  UserModel.shared.id as AnyObject
        para!["token"] = token as AnyObject
        
        isShow ? SVProgressHUD.show() : ()
        
        let httpMethod: HTTPMethod = method == .GET ? .get : .post
        
        Alamofire.request(url, method: httpMethod, parameters: para).responseJSON { (response) in
            
            SVProgressHUD.dismiss()
            
            switch response.result {
            case .success(let data):
                
                if let data = data as? [String: AnyObject],
                    let status = data["status"] as? Int{
                    
                    if status == 200 {
                        
                        finished(data, true, nil)
                        
                    } else if status == 16 {
                          
                        keyWindow?.rootViewController = BaseNavigationController(rootViewController: LoginController())
                        
                        finished(nil, false, data["error_desc"] as? String)
                        
                    } else {
                        
                        finished(nil, false, data["error_desc"] as? String)
                    }
                }
                
            case .failure(let error):
                
                print(response)
                finished(nil, false, "网络请求超时")
            }
        }
    }

这里只是对Alamofire做个简单的处理。大家根据公司的流程来对网络请求框架进行自我封装,可以加上请求头数据加密数据压缩,请求失败处理域名更换,循环请求等处理。

安全设计
  • 网络请求的参数和返回结果进行加密。比如:Base64加密Zlib压缩等。
  • 引入Token验证机制。
  • 禁止把敏感信息打印到Log中。
  • 在应用发布时必须确保Release模式。
  • Http请求都带上MAC,进行双向安全校验。
  • 针对用户密码等涉及的重要信息进行加密传输
  • 其他的持续补充ing。
图片/动画

TODO

文件/数据库

TODO

埋点设计

TODO

  • 其他的持续补充ing。

【开发流程】

【持续集成】

  • Xcodebuild 编译打包。
  • Fastlane自动化打包上传发布平台。
  • Jenkins持续集成配置。
  • 还有很多工具,网上也有相关教程,目前我自己使用的是Fastlane

【调试技巧】

NSLog

日常的开发过程中最常见的Debug方式就是打Log。

#if DEBUG
#define NSLog(...) NSLog(__VA_ARGS__)
#define debugMethod() NSLog(@"%s",__func__)、
#else
#define NSLog(...)
#define debugMethod()
#endi

另外在使用NSLog的时候应当注意,release版本中应该要去掉NSLog。

EXC_BAD_ACCESS
  • 1、开启僵尸对象

开启Zombie模式之后会导致内存上升,因为所以已经被释放(引用计数为0)的对象被僵尸对象取代,并未真的释放掉。这个时候再给僵尸对象发送消息,就会抛出异常,并打印出异常信息,你可以轻松的找到错误代码位置,结束Zombies时会释放。它的主要功能是检测野指针调用。

使用方法:

“Edit Scheme…” —> “Run” —> “Diagnostics” —> “Zombie Objects”
打开”Edit Scheme…”窗口:
  • 2、Address Sanitizer

在Xcode7之后新增了AddressSanitizer工具,为我们调试EXC_BAD_ACCESS错误提供了便利。当程序创建变量分配一段内存时,将此内存后面的一段内存也冻结住,标识为中毒内存。程序访问到中毒内存时(访问越界),立即中断程序,抛出异常并打印异常信息。你可以根据中断位置及输出的Log信息来解决错误。当然,如果变量已经释放了,它所占用的内存也会被标识为中毒内存,这个时候访问这片内存空间同样会抛出异常。

使用方法:

“Edit Scheme…” —> “Run” —> “Diagnostics” —> “Zombie Objects”

开启AddressSanitizer之后,在调试程序的过程中,如果有遇到EXC_BAD_ACCESS错误,程序则会自动终端,抛出异常。

  • 其他的持续补充ing。

你可能感兴趣的:(《抛砖引玉》iOS开发规范文档)