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

2020-03-26  本文已影响0人  晴天ccc

【前言】

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

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

【目录】


【命名规范】

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

项目命名
Bundle Identifier 命名
Class类名
控件名 类型 示例
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
对象命名
常量
Resource文件

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

例如:img_home_banner

版本号命名规范
版本号 说明 示例
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
函数命名
可以用一些通用的大写字母缩写打头方法,比如PDF,TIFF等。

可以用带下划线的前缀来命名私有方法或者类别中的方法。
//动词打头的方法表示让对象执行一个动作
- (void)invokeWithTarget:(id)target;
- (void)selectTabViewItem:(NSTabViewItem *)tabViewItem;
//正确,使用属性名来命名方法
- (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来连接参数
- (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

命名规范总结

清晰
//清晰
insertObject:atIndex:
//不清晰,insert的对象类型和at的位置属性没有说明
insert:at:
//清晰
destinationSelection:setBackgroundColor:
 
//不清晰,不要使用简写
destSel:setBkgdColor:
//有歧义,是返回sendPort还是send一个Port?
sendPort
 
//有歧义,是返回一个名字属性的值还是display一个name的动作?
displayName

一致性

【注释】

文件注释
/*******************************************************************************
    Copyright (C), 2011-2013, Andrew Min Chang
 
    File name:  AMCCommonLib.h
    Author:     Andrew Chang (Zhang Min) 
    E-mail:     LaplaceZhang@126.com
 
    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
 * 更新日期:            
 * 整理修改:    
 *
 ***************************************************************/

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

代码注释
/**
 *  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布局规范】

【编码规范】

函数的书写

方法长度建议不超过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) {
        // ...
    }
];

【系统设计】

隐私协议
// 强制退出App
- (void)exitApplication {
    
    UIWindow * window = [[[UIApplication sharedApplication] delegate] window];

    [UIView animateWithDuration:0.2f animations:^{
        window.alpha = 0;
    } completion:^(BOOL finished) {
        exit(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:test@qq.com"];

[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做个简单的处理。大家根据公司的流程来对网络请求框架进行自我封装,可以加上请求头数据加密数据压缩,请求失败处理域名更换,循环请求等处理。

安全设计
图片/动画

TODO

文件/数据库

TODO

埋点设计

TODO

【开发流程】

【持续集成】

【调试技巧】

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

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

使用方法:

“Edit Scheme…” —> “Run” —> “Diagnostics” —> “Zombie Objects”
打开”Edit Scheme…”窗口:

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

使用方法:

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

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

上一篇 下一篇

猜你喜欢

热点阅读