objective-c 编写规范_Objective-C 代码规范

版本

内容

修订人

时间

0.1.0

草稿

黄鑫

2018/06/11

0.2.0

修改文档组织

黄鑫

2018/06/16

0. 前言

"代码是写给人看的"

例子🌰

//

// M2User.h

// M2API

//

// Created by Kim on 2018/06/11.

//

// 头文件引入

#import

#import "M2Defines.h"

// 常量定义

FOUNDATION_EXPORT NSString * const M2UserErrorDomain;

/**

性别枚举

*/

typedef NS_ENUM(NSUInteger, M2Gender) {

M2GenderUnknow = 0, //!< 未知

M2GenderMale, //!< 男性

M2GenderFemale //!< 女性

};

/** 用户 */

@interface M2User : NSObject

@property (nonatomic, readonly, copy) NSString *name; //!< 名字

@property (nonatomic, readonly, assign) NSUInteger age; //!< 年龄

@property (nonatomic, readonly, assign) M2Gender gender; //!< 性别

/**

初始化

@param name 用户名

@param age 年龄

@param gender 性别

*/

+ (instancetype)userWithName:(NSString * __Nonnull)name

age:(NSUInteger)age

gender:(M2Gender)gender;

- (instancetype)initWithName:(NSString * __Nonnull)name

age:(NSUInteger)age

gender:(M2Gender)gender;

@end

// 实现

@implementation M2User

+ (instancetype)userWithName:(NSString * __Nonnull)name

age:(NSUInteger)age

gender:(M2Gender)gender {

return [[self alloc] initWithName:name age:age gender:gender];

}

- (instancetype)initWithName:(NSString * __Nonnull)name

age:(NSUInteger)age

gender:(M2Gender)gender {

if (self = [super init]) {

_name = name;

_age = age;

_gender = gender;

}

return self;

}

@end

1. 布局与风格

良好布局的目的

准确表现代码的逻辑结构

始终如一地表现代码的逻辑结构

改善可读性

经得起修改

布局技术

分组 从另一个角度看,空白也是分组,也是确保相关到语句组成放在一起。

空行 是指示一个程序如何组织的手段。可以用空行将相关语句各自划分成段落,分开各个子程序,突出注释部分。

缩进 使用缩进形式显示程序的逻辑结构。

“当程序有两到四个空格的缩进时,受试者对程序的理解分数会比毫无缩进的程序高出20%到30%。”

— 《程序缩进和可理解性》

2. 代码组织

Objective-C的类通常分成头文件和实现文件。

头文件

头文件通常包含:

文件说明与版权

头文件引入

宏定义

常量定义

类型前置声明

块类型定义

枚举定义

函数定义

协议定义

类定义 - 类定义通常包含

类方法。

属性。

公开方法。

分类定义

分类方法。

🚧 注意:内容排列顺序与上面一致。

如下面的头文件模板所示。按照下面的顺序定义。

// 文件说明与版权

//

// M2API2Client.h

// M2API

//

// Created by Kim on 2017/11/11.

// Copyright (c) 2017 Kim Studio. All rights reserved.

//

// 头文件引入 (见下文说明)

#import

#import "M2APIClient.h"

// 宏定义 (见下文说明。必须是才使用宏)

#define M2_DEBUG 0

#define M2_TEST 1

// 常量定义

FOUNDATION_EXPORT NSString * const M2UserErrorDomain;

// 类型前置声明

@class User;

// 类型定义

typedef NSString * const M2APIHTTPMethod;

// Block类型定义

typedef void (^M2APISuccessBlock)(id response);

typedef void (^M2APIFailureBlock)(NSError *error);

// 枚举定义

typedef NS_ENUM(NSUInteger, M2DirectionType) {

M2DirectionTypeUnknown = 0,

M2DirectionTypeTop,

M2DirectionTypeLeft,

M2DirectionTypeButtom,

M2DirectionTypeRight

};

// 协议定义

@protocol M2LoginViewDelegate : NSObject

@end

// 类定义

@interface M2User : NSObject

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

@end

// 分类定义

@interface M2User

- (NSString *)debugInfo;

@end

📝 通常使用文件模板

版权

文件头中增加版权信息。

//

// M2API2Client.h

// M2API

//

// Created by Kim on 2017/11/11.

// Copyright (c) 2017 Kim Studio. All rights reserved.

//

头文件引入

头文件引入规则顺序:

系统库

第三方库

工程内类引入

注意:

系统库/第三方库与工程内类引入直接的分组空行。

工程内类如果有多个头文件引入,也可以增加空行按功能进行分组。

//

// M2API2Client.h

// M2API

//

// Created by Kim on 2017/11/11.

// Copyright (c) 2017 Kim Studio. All rights reserved.

//

#import

#import

#import

#import

#import "M2APIConfiguration.h"

#import "M2APIHTTPSessionManager.h"

实现文件

实现文件通常包含:

// 文件描述

// 头文件引入

// 常量定义

// 文件内私有类定义

// 类私有方法定义

// 类实现

类定义

一般类定义组成如下:

协议

成员变量

属性

类方法

构造函数

公开方法

控件响应函数

通知响应函数

委托方法

私有方法

空行与注释:

协议与类之间留2行空行。

@protocol/@interface与第一个属性或方法后,空1行。

最后一个方法与@end之间空1行。

类最后空1行。

属性与第一个方法之间空1行。

属性如果有长注释,则空1行。

属性如果使用短注释,则在属性后使用//!

如果方法定义有注释,则空1行。

如果方法定义没有注释,则可以不留空行进行分组。

方法分组之间,空1行。

函数定义的注意点,见后面函数一节

// 头文件说明

//

// M2APIUserClient.h

// M2UserAPI

// Created by Kim on 2018/06/10

// Copyright (c) 2017 Kim Studio. All rights reserved.

// 头文件引入

#import

#import "M2APIUser.h"

// 协议定义

@protocol M2APIUserEndPoint

/**

登陆

@param user 用户名

@param password 密码

@return 信号 M2APIUser

*/

- (RACSignal *)loginWithUser:(NSString *)user password:(NSString *)password;

/**

获取用户信息

@return 信号 用户信息

*/

- (RACSignal *)userInfo;

@end

/// 类定义

/**

用户模块客户端

*/

@interface M2APIUserClient : M2HTTPClient

@property (nonatomic, strong) M2APIConfiguration *configuration; //!< 配置消息

@property (nonatomic, strong) M2APISigner *signer; //!< 签名类

+ (instancetype)sharedClient;

+ (instancetype)clientWithConfiguration:(M2APIConfiguration *)configuration;

- (instancetype)initWithConfiguration:(M2APIConfiguration *)configuration;

@end

类实现

类实现内部组织 使用#pargma mark -来分割功能组。一个典型的ViewController的实现功能分组有:

类方法。

单件函数。

其他类方法。

Lifecycle。 对象生命周期函数

对象生命周期函数。init, dealloc, descrition。

自定义属性。

UI对象的事件响应函数。

公开方法。

私有方法和辅助函数。

通知处理函数。

委托方法。

#pargma mark - Class methods

+ (instancetype)sharedInstance {}

+ (CGFloat)viewHeightForObject:(id)object {}

#pargma mark - Lifecycle

- (instancetype)init {}

- (void)dealloc {}

- (void)viewDidLoad {}

- (void)viewWillAppear:(BOOL)animated {}

- (void)didReceiveMemoryWarning {}

#pargma mark - Custom Accessors

- (void)setCustomProperty:(id)value {}

- (id)customProperty {}

#pargma mark - IBActions

- (IBAction)onSubmitDataAction:(id)sender {}

#pargma mark - Public

- (void)publicMethod {}

#pargma mark - Private helpers or utils

- (void)m2_privateMethod {}

#pargma mark - Notification Handlers

- (void)onEnterBackgroundHandler:(NSNotification *)notification {}

#pargma mark - Delegate methods

// 多个delegate 进行分组

📝 使用文件模板

3. 命名

Apple命名规则尽可能坚持,特别是与这些相关的memory management rules(NARC)。

长的,描述性的方法和变量命名是好的。

命名涉及到比较多:

库名

文件名

类名

函数名

变量名

库名

设计一个库通常使用前缀+名称的方式。eg.

UIKit

AVFoundation

SDWebImage

AFNetworking

文件名

文件名命名规则与类命名规则一致:

命名空间。本项目/或项目模块缩略前缀。eg. M2API, M2BL, M2PL。

功能名词。User,Device,File,VideoPlayer。

功能分类名字。例如,

Client 代表DAL的网络访问客户端。

Data代表DAL的DTO (Data Transfer Object)。

使用名词作为领域模型名称。

Service代表BL的业务逻辑类。

Item代表PL中View的VO(View Object)。

View代表PL的视图类。

Controller代表PL的中MVC模式的C控制器

ViewModel/Store代表PL的MVVM的VM或者MVCS的S。

eg.

UIViewController.h

M2PLLoginView.h

命名空间

由于Objective-C 没有命名空间,所以通常使用项目名的头字母用于:

常量

枚举

C函数名

全局变量名

类名

块类型名

前缀应由不少于3个字母组成(苹果保留所有2个字母的前缀)。可以是APP名、公司名缩写等。

例子🌰

// 宏

#debug M2_DEBUG

// 常量

FOUNDATION_EXPORT NSString * const M2UserErrorDomain;

// 别名

typedef NSString * const M2HTTPMethod;

// 块类型名

typedef void (^M2APISuccessBlock)(id response);

typedef void (^M2APIFailureBlock)(NSError *error);

// 类名

@class M2User;

尽量少使用宏来定义常量。宏通常用于编译条件。

增加命名空间。

单词使用大写。

使用下划线连接单词。

使用

#define M2_DEBUG

不使用

#define Production

#define M2_Production

常量

常量通常使用与字符串类型常量与值类型常量。注意点:

命名空间。命名空间前缀全部大写

使用驼峰命名

// 头文件 .h

FOUNDATION_EXPORT NSString * const M2UserErrorDomain;

FOUNDATION const CGFloat M2UserMaxAge;

// 实现文件 .m

static NSString * const M2UserError = @"net.kim.M2UserErrorDomain"; // 跨文件使用

static const CGFloat M2UserMaxAge = 200; // 跨文件使用

static const CGFloat M2UserMinAge = 0; // 文件内部使用

块类型

块类型定义。注意点:

命名。命名空间 + 功能名词 + Block。

命名空间。见《命名空间一节》

功能名词。

后缀Block。以区别其他类型。

注意返回类型后需要增加一个空格。

typedef void (^M2APISuccessBlock)(id response);

typedef void (^M2APIFailureBlock)(NSError *error);

4. 类

类方法

单例模式

属性

实例方法

类方法

单例模式

单例对象应该使用线程安全模式来创建共享实例。

+ (instancetype)sharedInstance {

static id sharedInstance = nil;

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

sharedInstance = [[self alloc] init];

});

return sharedInstance;

}

属性

公开属性

属性特性排列顺序如下:

是否原子atomic/nonatomic。虽然默认为atomic, 原子访问时还是需要显式说明。

读写readonly/readwrite。默认为readwrite。只读是需要显式说明。

访问器getter/setter。如果是布尔类型getter,需要加is前缀。

存储特性weak/strong/copy/assign。放在最后。

💡Tip 不可变性 Immutable

为了避免数据遭到不必要的修改:

不应该被外部直接修改的属性,应该声明(readonly)(可以在Extension中重新声明为(readwrite),使它对外只读,对内可读写)。

不要把NSMutable(Array/Dictionary/Set...)暴露出来,应该只留一个setter给外部使用,以免它们被其他类修改时,类自身难以察觉。

如果数据不是特别多,copy的代价不是特别大,留给其他类的getter应尽量用copy方法,返回一个不可变的对象。

例子

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

@property (nonatomic, readonly, assign) NSUInteger age;

@property (nonatomic, readonly, assign) M2Gender gender;

@property (nonatomic, readonly, getter=isLogin, assign) BOOL login;

💡Tip 控件属性命名

控件功能名词 + 后缀不带命名空间的控件类型名

例子

@interface M2PLoginView : UIView

@property (nonatomic, weak) IBOutlet UILabel *userLabel; //!< 用户名标签

@property (nonatomic, weak) IBOutlet UITextField *userTextField; //!< 用户名输入

@end

私有属性

如果私有属性在模块内部可以访问,则使用私有头文件。

eg. M2User_Private.h 在有需要使用到的实现文件引入即可。

🚧 注意:私有头文件,在模块外部不可访问。生成库时需要注意忽略改头文件。

例子

/// 实现文件

@interface M2User ()

@property (nonatomic, copy)NSString *name;

@property (nonatomic, assign)NSUInteger age;

@property (nonatomic, assign)M2Gender gender;

@end

@implementation M2User

// ...

@end

自定义属性

下面是一个懒加载的自定义属性:

- (NSMultableDictionary *)extraInfo {

if (!_extraInfo) {

_extraInfo = [NSMutableDictionary dictionary];

}

return _extraInfo

}

自定义属性设置:

- (void)setExtraInfo:(NSDictionary *)extraInfo {

_extraInfo = extraInfo;

// Do something else.

// 其他副作用。

}

🚧 注意:在自定义属性内增加副作用需要特别注意。需要在属性增加注释说明。

成员变量

私有属性,一般定义在类的实现文件。

/// 实现文件

@interface M2User () {

BOOL _status;

}

@end

@implementation M2User

// ...

@end

标识位

@interface Fool () {

struct {

BOOL step1Done;

BOOL step2Done;

} _flags;

}

// 使用

_flag.step1Done = YES;

_flag.step1Done = NO;

类初始化方法

@interface Airplan

+ (instancetype)airplanWithType:(AirplanType)type;

@end

@implementation Airplan

关于更多instancetype信息,请查看NSHipster.com

Init方法

Init方法应该遵循Apple生成代码模板的命名规则。返回类型应该使用instancetype而不是id

- (instancetype)init {

if (self = [super init]) {

// ...

}

return self;

}

// 或者

- (instancetype)initWithName:(NSString *)name {

self = [super init];

if (self) {

// ...

}

return self;

}

方法定义

响应函数

规则:

控件响应函数:前缀on + 功能动作 + 后缀Action。

通知响应函数:前缀on + 通知 + 后缀Handler。

使用

// 控件响应函数

- (IBAction)onLoginAction:(id)sender {

// ...

}

// 通知响应函数

- (void)onEnterBackgroundHandler:(NSNotification *)notification {

// ...

}

不使用

- (IBAction)login:(id)sender {

// ...

}

- (void)enterBackground:(NSNotification *)noti {

// ...

}

分类

@interface M2APIUserClient (User)

// 登陆

- (RACSignal *)loginWithUser:(NSString *)user password:(NSString *)password;

// 登出

- (RACSignal *)logout;

@end

5. 子程序

变量[TODO]

布尔值

Objective-C使用YES和NO。因为true和false应该只在CoreFoundation,C或C++代码使用。既然nil解析成NO,所以没有必要在条件语句比较。不要拿某样东西直接与YES比较,因为YES被定义为1和一个BOOL能被设置为8位。

这是为了在不同文件保持一致性和在视觉上更加简洁而考虑。

使用

if (someObject) {}

if (![anotherObject boolValue]) {}

不使用

if (someObject == nil) {}

if ([anotherObject boolValue] == NO) {}

if (isAwesome == YES) {} // Never do this.

if (isAwesome == true) {} // Never do this.

如果BOOL属性的名字是一个形容词,属性就能忽略"is"前缀,但要指定get访问器的惯用名称。例如:

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

条件语句if/else

条件语句主体为了防止出错应该使用大括号包围,即使条件语句主体能够不用大括号编写(如,只用一行代码)。这些错误包括添加第二行代码和期望它成为if语句;还有,even more dangerous defect可能发生在if语句里面一行代码被注释了,然后下一行代码不知不觉地成为if语句的一部分。除此之外,这种风格与其他条件语句的风格保持一致,所以更加容易阅读。

使用

// Good!

if (!error) {

return success;

}

不使用

// Bad

// 没有花括号,容易多些空行,造成逻辑提前返回。

if (!error)

return success;

if (!error) return success;

if (error != nil)

return success

多条件情而且单行过长的情况下,使用换行。条件符放行最后。

使用

// Good!

if (direction == M2Left ||

direction == M2Right) {

// ...

}

不使用

// Bad!

if (direction == M2Left

|| direction == M2Right) {

// ...

}

使用

if (user.isHappy) {

// Do something

} else {

// Do something else

}

不使用

if (user.isHappy)

{

// ...

}

else {

// ...

}

Switch-Case

- (void)handleMessage:(M2Message *)message {

// 注意花括号与break。

M2MessageType type = message.type;

switch(type) {

case M2MessageChat: {

// ...

} break;

case M2MessageNotify: {

// ...

} break;

case M2MessageSystem: {

// ...

} break;

default:

break;

}

}

三元操作符 ?:

当需要提高代码的清晰性和简洁性时,三元操作符?:才会使用。单个条件求值常常需要它。多个条件求值时,如果使用if语句或重构成实例变量时,代码会更加易读。一般来说,最好使用三元操作符是在根据条件来赋值的情况下。

Non-boolean的变量与某东西比较,加上括号()会提高可读性。如果被比较的变量是boolean类型,那么就不需要括号。

使用

NSInteger value = 5;

result = (value != 0) ? x : y;

Bool isHorizontal = YES;

result = isHorizontal ? x : y;

NSString *name = nil;

result = name ?: @"";

不使用

BOOL result = value!=0 ?x:y;

块block

使用

typedef void (^M2SuccessBlock)(id response);

M2SuccessBlock successBlock = ^(id response) {

// Do something.

};

不使用

// 可读性不够强,另外可能导致过长行。

void (^successBlock)(id response) = ^(id response) {

// Do something.

};

使用

// 风格1

[userClient loginWithUser:user password:password success:^(id user) {

// do something.

} failure:^(NSError *error) {

// ...

}];

// *******************************************

// 如果success/failure块过长,则可以前缀定义块。

// 风格2

M2APISuccessBlock successBlock = ^(id response) {

// do something.

};

M2APIFailureBlock failureBlok = ^(NSError *error) {

// ...

};

// 单行过长,则换行。

[userClient loginWithUser:user

password:password

success:successBlock

failure:failureBlock];

字面值

使用

// 数组

NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];

// 字典

NSDictionary *productManagers = @{@"iPhone": @"Kate", @"iPad": @"Kamal", @"Mobile Web": @"Bill"};

// Number

NSNumber *shouldUseLiterals = @YES;

NSNumber *buildingStreetNumber = @10018;

不使用

// 数组

NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];

// 字典

NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil];

// Number

NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];

NSNumber *buildingStreetNumber = [NSNumber numberWithInteger:10018];

数组

// 数组

NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];

names[]

字典

// 字典创建

NSDictionary *productManagers = @{@"iPhone": @"Kate", @"iPad": @"Kamal", @"Mobile Web": @"Bill"};

// 访问

NSString *product = productManagers[@"iPhone"];

//

NSMutableDictionary *user = [NSMutableDictionary dictionary];

// 设置

user[@"name"] = @"Bob";

user[@"title"] = @"IT Manager";

user[@"age"] = @25;

// 迭代

[user enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {

NSLog(@"key : %@, value : %@", key, obj);

}];

CGRect 函数

使用

// Good!

CGRect frame = self.view.frame;

CGFloat x = CGRectGetMinX(frame);

CGFloat y = CGRectGetMinY(frame);

CGFloat width = CGRectGetWidth(frame);

CGFloat height = CGRectGetHeight(frame);

CGRect frame = CGRectMake(0.0, 0.0, width, height);

不使用

// Bad

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;

CGRect frame = (CGRect){ .origin = CGPointZero, .size = frame.size };

注释

当需要注释时,注释应该用来解释这段特殊代码为什么要这样做。任何被使用的注释都必须保持最新或被删除。

一般都避免使用块注释,因为代码尽可能做到自解释,只有当断断续续或几行代码时才需要注释。例外:这不应用在生成文档的注释

空格

空行

6. 资源

iOS应用包含多种资源文件

storyboard/xib

图片

字符串

字体

多媒体

storyboard/xib

图片

字符串

字体

多媒体

7. 模块

8. Xcode工程

物理文件应该与Xcode工程文件保持同步来避免文件扩张。任何Xcode分组的创建应该在文件系统的文件体现。代码不仅是根据类型来分组,而且还可以根据功能来分组,这样代码更加清晰。

尽可能在target的Build Settings打开"Treat Warnings as Errors,和启用以下additional warnings。如果你需要忽略特殊的警告,使用 Clang's pragma feature。

9. 辅助工具

Spacecommander

参考:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值