iOS开发中的代码规范

Objective-C代码编写规范

1. 命名规范

我们尽可能遵守Apple的命名约定,其推荐使用长的、描述性强的方法名和变量名,使其阅读起来更加清晰易懂。不能随意使用缩写,导致其他人员阅读代码困难。 附:The Coding Guide for Cocoa

1.1 前缀
项目名称、类名、文件名都应该保持一致的前缀名。根据Apple Guide的建议,类名前缀应该使用2个英文字母以上最好,因为Apple写的框架都是使用2个英文字母开头,使用3个字母能有效防止类名重复,避免影响工程。

1.2 方法命名规范
方法一般不能用init、set开头进行命名,如果不是写初始化方法不要用init进行开头;如果不是属性的set方法,不要用set作为方法的前缀。

根据Cocoa命名方法规则,我们应该遵守这几个点:
a. 使用小写字母开头,后面嵌套连接的字母使用大写开头。不过在写 Category Method 的时候,我们比较习惯使用 JSD_method 的方式,而非遵守所有的方法命名规则 jsd_method,这个我觉得只要统一起来就好了。
b. 对于采取动作行为的方法,使用动词开头,但是不要直接使用do或者does
c. 每个方法参数前必须带有相同或者能清晰表达其原意的关键字;
d. 子类创建相对父类更加详细功能的方法时应该把新增参数添加在原有方法的后面;
e. 假如方法名过长的时候可以采用每个参数单独占一行的规则,并保持每个参数分号:对齐的方式排列;
f. 实例方法和类方法 (-/+) 符号后面应该保持一个空格,如:- (void)

1.3 控件命名规范
对于命名一定不要简写,UILabel结尾加上LabelUIImageView结尾加上ImageView等等让其他人看名字就知道变量的用法和属于什么控件。

1.4 Block 的命名规范
之前研究过很多的第三方命名,对于Apple官方的没找到,有CallBackCompleteBlock结尾的,还有CompletionHandler结尾的。我看到很多的结尾都是用CompletionHandler,大部分命名是Block,我们按照Block来命名。

例子:

eg 1. 对于#define宏命名,单词应全部大写,单词之间用_分割。
建议的写法:
#define NS_AVAILABLE_IOS 3.0
不建议的写法:
#define NSAvailableIos 3.0

eg 2. 类型常量
多用类型常量,少用#define
建议的写法:
const NSTimeInterval kAnimationDuration = 0.3;
不建议的写法:
#define ANIMATION_DURATION 0.3

当使用类型常量定义时,若常量局限于某编译单元,也就是实现文件里面,则使用k作为前缀,如:kCountdownTime;

若常量在类之外公开出来,则需要使用规定的类名作为前缀。如:SettingCountdownTime

约定:在我们自己定义NSNotification的时候应该把通知的名字定义为一个字符串常量,就像把我们暴露给其他类的字符串常量一样。使用extern关键字将其在.h文件声明,并且在.m文件对其定义。

.h声明:

UIKIT_EXTERN NSString *const BWWillUpdateListNotification;
UIKIT_EXTERN const NSInteger MaxLeadCharCount;

.m实现:

NSString *const BWWillUpdateListNotification = @"kWillUpdateListNotification";
const NSInteger MaxLeadCharCount = 44;
  1. 关于通知名称,推荐使用Did/Will这样的动词连接表示最好;
  2. 如果导入的是UIKit类,就使用UIKIT_EXTERNFoundation类,就使用FOUNDATION_EXTERN;如果只在本类使用,只用写实现,不用写声明。

对于只在内部声明的const,需要添加static,这个我觉得可以不加,但是无法看到苹果的实现,不知道苹果的规范怎么写的。

2. 代码格式

2.1 间距
a. 方法的大括号和其他的大括号(if/else/switch/while等等)应始终和声明在同一行开始,在新的一行结束;
b. 方法之间应该正好空一行,这样有助于视觉清晰度和代码组织性。在方法中的功能块之间也应该使用空白行分开。
c. switch-case中,case后的代码如果多于一行,则需要用{}包裹,建议所有casedefault后的代码块均用{}包裹。

建议的写法:

if (user.isHappy) {
    // Do something cool
} else {
    // Do something else
}

不建议的写法:

if (user.isHappy) 
{
    // Do something cool
} 
else 
{
    // Do something else
}

2.2 属性关键字首个应该是原子性,再到内存管理关键词。如果需要读写关键字的话,其排在第二位。
建议的写法:

@property (nonatomic, readonly, copy) NSString *name;

2.3 推荐使用三元运算符进行运算,它能使代码更加简洁、清晰。
当三元运算符的第二个参数(也就是if分支)返回的对象和条件语句中已经检查的对象一样的时候,下面的表达方式更灵巧:
建议的写法:

result = object ? : [self createObject];

不建议的写法:

result = object ? object : [self createObject];

2.4 黄金大道规则(Golden Path
在使用条件语句编程时,尽管会遇到逻辑复杂的代码,我们也应该尽量避免其嵌套导致阅读困难。
尽量使用return将不符合逻辑的直接忽略掉,然后将要执行的代码放到判断语句外面,减少嵌套。
建议的写法:

- (void)someMethod {
    if (![someOther boolValue]) {
        return;
    }
    // Do something important
}

不建议的写法:

- (void)someMethod {
    if ([someOther boolValue]) {
        // Do something important
    }
}

2.5 避免尤达表达式
不要使用尤达表达式,尤达表达式是指拿一个常量去和变量比较。
建议的写法:

if ([myValue isEqual:@42]) { 
 
}

不建议的写法:

if ([@42 isEqual:myValue]) {
 
} 

3. 文件引入方式

.h文件中尽量使用@class声明文件,直到.m文件中真正需要的时候再使用#improt进行引用,这样能有效的防止相互引用、编译失败、不容易查找的bug等;这样做的缺点是:.m文件还要#import其他类。
建议的写法:

@class UIView, UIImage;

不建议的写法:

@class UIView;
@class UIImage;

#improt头文件顺序:可以先引入系统文件,依次到Public.h,最后才到我们自己编写的文件(可以大家一起商量一下顺序)。记得检查引用文件名称,避免引入了没有使用到的文件,发现后应及时清除。
尽量按照系统类、第三方类、自己写的类顺序导入,中间不能有空行。
写法模板:
#import <系统库>
#import <第三方库>
#import "其他类"

建议的写法:

#import 
#import 
#import "GBOrderEmptyView.h"

不建议的写法:

#import "GBOrderEmptyView.h"

#import 

#import 

4. 不允许外界修改的属性要设置 readonly

大家平时设置属性默认是可读可写的,但是这样很容易对别人造成误解,以为可以赋值。因此,对于只能获取的属性,一定写readonly

@property (nonatomic, readonly, copy) NSString *name;

5. BOOL 类型属性的声明

属性set不要带isget要带is
建议的写法:

@property (nonatomic, assign, getter=isUserLogin) BOOL userLogin;

不建议的写法:

@property (nonatomic, assign) BOOL userLogin;

6. 对于初始化,一定要使用类对应的初始化方法

a. UIView的对应初始化方法为:- (instancetype)initWithFrame:(CGRect)initWithFrame

b. UIViewController的对应初始化方法为:- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil

防止初始化用initnew等,没经过系统进行设置一些默认属性而造成bug。

c. 对于局部变量,尽量进行初始化。局部变量要初始化,属性有默认的值,所以我们不必须对属性进行初始化。
建议的写法:

int index = 0;

不建议的写法:

int index;

7. 对于一些状态、选项,使用枚举,尽量少用数字、字符串判断状态。

建议的写法:

typedef NS_ENUM(NSUInteger, HomeViewState) {
    HomeViewStateNoData,
    HomeViewStateFailure,
    HomeViewStateItemList,
    HomeViewStateBannerList,
};

if (state == HomeViewStateNoData) { // 显示无数据
    
} else if (state == HomeViewStateFailure) { // 显示请求错误
    
} else if (state == HomeViewStateItemList) { // 显示商品的列表
    
} else if (state == HomeViewStateBannerList) { // 显示banner列表
    
}

不建议的写法:

if (state == 0) { // 显示无数据
    
} else if (state == 1) { // 显示请求错误
    
} else if (state == 2) { // 显示商品的列表
    
} else if (state == 3) { // 显示banner列表
    
}

8. 可变对象的使用

OC存在很多可变的对象,比如:NSMutableStringNSMutableArrayNSMutableDictionary等等,对于一些不允许改变的,直接使用不可变对象,可以节省对象开支,还可以防止别人修改数据造成bug。
建议的写法:

NSArray *sexList = @[@"Man", @"Woman"];

不建议的写法:

NSMutableArray *sexList = [NSMutableArray arrayWithArray:@[@"Man", @"Woman"]];

9. 遍历的写法

a. 如果只需要遍历数组或字典,用forin
建议的写法:

for (NSString *name in names) {
    // Do something very cool
}

不建议的写法:

for (int i = 0; i < names.length, i++) {
    NSString *name = names[i];
}

b. 如果需要遍历数组或字典的内容,并且需要索引时使用enumerator
建议的写法:

[names enumerateObjectsUsingBlock:^(NSString * _Nonnull name, NSUInteger idx, BOOL * _Nonnull stop) {
    // Do something very cool
}];

不建议的写法:

for (int i = 0; i < names.length, i++) {
    NSString *name = names[i];
}

10. 复杂的表达式

建议的写法:

BOOL nameContainsSwift = [sessionName containsString:@"Swift"];
BOOL isCurrentYear     = [sessionDateCompontents year] == 2014;
BOOL isSwiftSession    = nameContainsSwift && isCurrentYear;

if (isSwiftSession) {
    // Do something very cool
}

不建议的写法:

if ([sessionName containsString:@"Swift"] && [sessionDateCompontents year] == 2014) {
    // Do something very cool
}

11. NSUserDefaults的代码使用规范

因为用到NSUserDefaults无非是保存和读取数据,所以先创建一个对象,可以精简代码,当执行方法很多,用变量替换。
对于我们取值和存值的key要定义一下,定义一下key方便我们使用,并且方便之后改名字。

建议的写法:

NSString *user_id = @"user_id";
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:@"1" forKey:user_id];

不建议的写法:

[[NSUserDefaults standardUserDefaults] setObject:@"1" forKey:@"user_id"];

12. 通知的移除

通知在dealloc要移除(记得在dealloc时释放注册的通知和 KVO 的监听)。
建议的写法:

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

不建议的写法:

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:name1 object:nil];
}

13. 创建单例的方法

正确的写法:

+ (instancetype)sharedInstance {
    static BTSCustomView *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[BTSCustomView alloc] init];
    });
    return instance;
}

不建议的写法:

+ (instancetype)sharedInstance {
    static BTSCustomView *instance = nil;
    if (!instance) {
        instance = [[BTSCustomView alloc] init];
    }
    return instance;
}

14. OC方法的代码量

一个方法内部尽量控制在最多50行,如果超过就精简代码,分开方法写,方便之后进行热修复、代码重构;注释一定要写,自己管理的类一定要注释属性用途、方法的用途、参数说明。
a> 属性如果设置默认值,一定要注明默认值是什么;
b> 如果方法内部存在逻辑判断、方法跳转,一定注释逻辑判断的用途、方法跳转的用途;
c> 除了初始化操作,其他声明变量、赋值、判断,应该注释用途。

15. #pragma mark的使用

对于属性的不同作用,可以进行分组,比如设置颜色的、设置字体的、设置其他样式的;
对于方法的不同作用,可以进行分类,比如添加功能、删除功能的;
对于其他的代理方法、GetSet方法、Init初始化方法等,可以进行分类。

建议的写法:

#pragma mark - Init

#pragma mark - Request

#pragma mark - Delegate

#pragma mark - DataSource

#pragma mark - Setter

#pragma mark - Getter

16. 注释的写法

<1> 类注释

/**
 订单的cell
 */
@interface OrderCell : UITableViewCell

<2> 属性注释

/**
 当前订单cell
 */
@property (nonatomic, strong) OrderCell *cell;

<3> 方法注释

/**
 显示倒计时文本
 
 @param timerLabel 倒计时文本
 @param countTime 剩余时间
 @return 是否完成
 */
- (BOOL)timerLabel:(MZTimerLabel *)timerLabel finishedCountDownTimerWithTime:(NSTimeInterval)countTime;

<4> 局部变量和全局变量注释

BOOL _isOfflinePay; // 是否是离线支付

<5> block注释

/**
 验证输入的是否正确
 
 @param inputText 输入的文本
 @return 如果返回值存在就代表验证失败;否则,就代表成功
 */
typedef NSString *(^ATFVValidateInputCorrectComplete)(NSString *inputText);

<6> 枚举(NS_ENUM) 注释

/**
 当前的状态
 
 - HomeViewStateNoData: 显示无数据
 - HomeViewStateFailure: 显示请求错误
 - HomeViewStateItemList: 显示商品的列表
 - HomeViewStateBannerList: 显示banner列表
 */
typedef NS_ENUM(NSUInteger, HomeViewState) {
    HomeViewStateNoData,
    HomeViewStateFailure,
    HomeViewStateItemList,
    HomeViewStateBannerList,
};

17. CGRect使用

当需要访问CGRect中某个成员变量时,应该使用CGGeometry函数来直接访问,而不是使用.语法来获取。
建议的写法:

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;

18. 错误处理

很多系统方法通过error返回指针的形式来表示错误,我们应该针对其返回值判断,而非错误变量。

建议的写法:

NSError *error;
if (![self trySomethingWithError:&error]) {
    // 处理错误
}

不建议的写法:

NSError *error;
[self trySomethingWithError:&error];
if (error) {
    // 处理错误
}

一些苹果API在成功的情况下会写一些垃圾值给错误参数(如果非空),所以针对错误变量可能会造成虚假结果以及接下来的崩溃。

其他

  1. 下面是整理的一些常用对仗词,大家可以参考使用。
add/delete          添加/删除        add/remove    添加/移除

begin/end           开始/结束

create/destroy      创建/销毁

first/last          第一个/最后一个

get/release         获取/释放        get/set       取出/设置

increment/decrement 增加/减少        insert/delete 插入/删除

lock/unlock         锁/解锁

next/previous       下一个/前一个

old/new             旧的/新的        open/close    打开/关闭

pop/push            出栈/入栈        put/get       放入/取出

send/receive        发送/接收        show/hide     显示/隐藏         source/target  源/目标
source/sink         来源/接收器      source/destination 源/目的地     start/stop      开始/停止
store/query         存储/查询

up/down             向上/向下

visible/invisible   可见/不可见



settings  配置
traversal  遍历
Proactor  设计模式
adapter  适配器
listener  监听器
trigger  触发器
acceptor 接收器
connector  连接器
dispatch  调度/分派/分发
dispatcher  分派器
reactor  反应器
executor  执行器
parser  解析器
builder  生成器/构造器
handle  句柄/处理
handler  处理器
invoke  调用
invoker  调用方
masterplate  模板
  1. iPhone屏幕适配比例
//    屏幕宽高                  宽度                         高度
3.5   320x480      100 / 414 * 320 ~= 77.3     100 / 736 * 480 ~= 65.2
4.0   320x568      100 / 414 * 320 ~= 77.3     100 / 736 * 568 ~= 77.2
4.7   375x667      100 / 414 * 375 ~= 90.6     100 / 736 * 667 ~= 90.6
5.5   414x736      100 / 414 * 414 ~= 100      100 / 736 * 736 ~= 100
5.8   375x812      100 / 414 * 375 ~= 90.6     100 / 736 * 812 ~= 110.3


iPhone 6 和 iPhone X 屏幕尺寸对比:

          iPhone 6             iPhone X

          375x667               375x812
        ************         ************
        *          *         *          *
        *          *         *          *
        *          *         *          *
        *          *         *          *
        *          *         *          *
        *          *         *          *
        *          *         *          *
        *          *         *          *
        *          *         *          *
        *          *         *          *
        ************         *          *
                             *          *
                             *          *
                             ************
宽度相同,高度不同:

667 = 20 + 44 + 554 + 49

812 = 44 + 44 + 641 + 49 + 34


Device Screen Sizes

设备 屏幕尺寸 (英寸) 点分辨率 (pt) 像素分辨率 (px) PPI (DPI) 渲染后
iPhone 3GS 3.5 320 x 480 320 x 480 163
iPhone 4 / 4s 3.5 320 x 480 640 x 960 326
iPhone 5 / 5s / SE 4.0 320 x 568 640 x 1136 326
iPhone 6 / 6s / 7 / 8 4.7 375 x 667 750 x 1334 326
iPhone 6 Plus / 6s Plus / 7 Plus / 8 Plus 5.5 414 x 736 1242 x 2208 401 1080 x 1920
iPhone X / Xs / 11 Pro 5.8 375 x 812 1125 x 2436 458
iPhone XR / 11 6.1 414 x 896 828 x 1792 326
iPhone Xs Max / 11 Pro Max 6.5 414 x 896 1242 x 2688 458

你可能感兴趣的:(iOS开发中的代码规范)