声明:
- 本文档面向以Objective-C作为开发语言的iOS团队。
- 本文档以苹果开发文档为基础,结合网络内容和开发实践进行整理,针对iOS开发进行规范和约定。
- 最后更新时间:2019年3月13日
- 为保证时效性,持续更新地址为:iOS开发手册
1.项目基础
1.1 项目新建信息
- Product Name:工程名。
- Team:开发者账号信息,没有选择None,个人开发者账号(含Personal Team),公司/企业账号(如:XXX Co.,LTD),也可以暂时选择None,后续再配置调试。
- Organization Name:个人开发者账号(自定义名字),公司/企业账号(如:XXX Co.,LTD),此处内容明显体现在代码文件头部注释中。
- Organization Identifier:个人开发者账号(自定义标识),公司/企业账号(域名反写如:com.xxx)。
- Bundle Identifier:自动生成格式为 [Organization Identifier] + [Product Name],也可后续配置时进行修改。
- Language:根据需要选择Objective-C 或者 Swift。
- Use Core Data:根据项目情况勾选,如果明确需要请直接勾选,也可后续添加。
- Include Unit Tests:单元测试,根据需要勾选,也可后续添加。
- Include UI Tests:UI测试,根据需要勾选,也可后续添加。
- Source Control:默认git进行版本管理,根据需要勾选,也可后续添加。
1.2 项目初始配置
- Display Name:应用名称。
- Deployment Info:系统版本、设备、屏幕方向、状态栏等,
- Build Active Architecture Only:一般Debug模式YES,Release模式NO。如果Release模式为YES,那么上传AppStore之后会显示大量而具体的兼容设备,Release模式为NO则仅显示模糊的兼容信息。
- Architecture:默认$(ARCHS_STANDARD)不作修改。
- Valid Architecture:默认arm64/armv7/armv7s不作修改。模拟器32位处理器是i386架构,模拟器64位处理器是x86_64架构,真机32位处理器是armv7或armv7s架构,真机64位处理器是arm64架构。
1.3 项目文件结构
- 所有的文件应放在工程中的项目目录下。
- 项目文件和物理文件需保持一致。
- Xcode创建的任何组(group)都必须有文件夹映射。
- 项目文件不仅可以按照业务类型分组,也可以根据功能分组。
2.代码格式规范
2.1 代码注释格式
- 文件注释:采用Xcode自动生成的注释格式。
//
// AppDelegate.h
// 项目名称
//
// Created by 开发者姓名 on 2018/6/8.
// Copyright © 2018年 公司名称. All rights reserved.
//
- import注释:如果有一个以上的import语句,对这些语句进行分组,每个分组的注释是可选的。
// Framework
#import <UIKit/UIKit.h>
// Model
#import "WTUser.h"
// View
#import "WTView.h"
- 方法注释:Xcode8之后快捷键自动生成(option + command + /)。
/**
<#Description#>
@param application <#application description#>
@param launchOptions <#launchOptions description#>
@return <#return value description#>
*/
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
- 代码块注释:单行的用 “// + 空格” 开头, 多行用“/* */”。
2.2 代码结构与排版
- 声明文件:方法顺序和实现文件的顺序保持一致,根据需要用”#pragma mark -“将方法分组。
- 实现文件:必须用”#pragma mark -“将方法分组。分组前后优先级:Lifecycle方法 > Public方法 > UI方法 > Data方法 > Event方法 > Private方法(逻辑处理等) > Delegate方法 > 部分Override方法 > Setter方法 > Getter方法。
#pragma mark - Lifecycle
- (instancetype)init {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)viewDidAppear:(BOOL)animated {}
- (void)viewWillDisappear:(BOOL)animated {}
- (void)viewDidDisappear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}
- (void)dealloc {}
#pragma mark - Public
- (void)refreshData {}
#pragma mark - UI
- (void)initSubViews {}
#pragma mark - Data
- (void)initData {}
- (void)constructData {}
#pragma mark - Event
- (void)clickButton:(UIButton *)button {}
#pragma mark - Private
- (CGFloat)calculateHeight {}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {}
#pragma mark - Override
- (BOOL)needNavigationBar {}
#pragma mark - Setter
- (void)setWindow:(UIWindow *)window {}
#pragma mark - Getter
- (UIWindow *)window {}
- 变量:优先使用属性声明而非变量声明,注意属性修饰符、变量类型、变量之间的间隔。
@property (strong, nonatomic) UIWindow *window;
- 点语法:应始终使用点语法来访问和修改属性。
- 间距要求如下:
- 一个缩进使用四个空格。
- 在”-“或者”+“号之后应该有一个空格,方法的大括号和其它大括号始终和声明在同一行开始,在新的一行结束,另外方法之间应该空一行。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if (door.isClosed) {
// Do something
} else {
// Do something
}
return YES;
}
- 长度要求如下:
- 每行代码的长度不应该超过100个字符。
- 单个函数或方法的实现代码控制在50行以内。
- 单个文件里的代码行数控制在500~600行之内。
3.代码命名规范
3.1 代码命名基础
- 最好是既清晰又简短,但不要为简短丧失清晰性,并使用驼峰命名法。
- 名称通常不缩写,即使名称很长也要拼写完全(禁止拼音),然而可使用少数非常常见的缩写,部分举例如下:
常用缩写词 | 含义 | 常用缩写词 | 含义 |
app | application | max | maximum |
alt | alternate | min | minimum |
calc | calculate | msg | message |
alloc | allocte | rect | rectangle |
dealloc | deallocte | msg | message |
init | initialize | temp | temporary |
int | integer | func | function |
- 由于Cocoa(Objective-C)没有C++一样的命名空间机制,需添加前缀(公司名首字母)防止命名冲突,前缀使用2个字符(以下统称项目前缀)。
- 常见的单词略写:ASCII,PDF,HTTP,XML,URL,JPG,GIF,PNG,RGB等
3.2 类和协议命名
- 类名应明确该类的功能,并且要有项目前缀防止命名冲突。
- 协议组合一组相关的方法,不关联具体的一个类,使得某些相似类有统一的接口,这种协议的命名应采用动名词形式(ing),例如:NSLocking。
- 协议组合一些不相关的方法(主要是避免创建多个独立的协议),仅仅关联某一个具体的类(该类是协议的具体体现者),这种协议用该类名命名,例如:NSObject。
- 委托形式的协议命名为类名加上Delegate,例如:UIScrollViewDelegate。
3.3 变量和属性命名
- 变量名应前置下划线“_”,属性名没有下划线。
- 属性本质上是存取方法setter/getter,可进行重写(注意内存管理)。
@property (strong, nonatomic) UIWindow *window;
- (void)setWindow:(UIWindow *)window;
- (UIWindow *)window;
- 可以适当的对setter/getter进行别名设置。
@property(nonatomic,getter=isUserInteractionEnabled) BOOL userInteractionEnabled;
3.4 方法和函数命名
- 方法名和函数名一般不需要前缀,但函数(C语言形式)作为全局作用域的时候最好加上项目前缀。
- 表示行为的方法名称以动词开头,但不要使用do/does等无实际意义的助动词。
- 参数前面的单词要能够描述该参数,并且参数名最好能用描述该参数的单词命名。
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
- 方法中多个参数可以使用适当的介词进行连接。
// 后续多个参数使用with
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
// 添加适当介词能够使方法的含义更明确
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
// 第一个参数用了with,后面的参数不使用with
- (instancetype)initWithImage:(nullable UIImage *)image highlightedImage:(nullable UIImage *)highlightedImage;
- 只有在方法返回多个值的时候使用get单词进行明确。
- (void)getLineDash:(nullable CGFloat *)pattern count:(nullable NSInteger *)count phase:(nullable CGFloat *)phase;
- 方法返回某个对象实例。
+ (instancetype)buttonWithType:(UIButtonType)buttonType;// 类方法创建对象
+ (UIApplication *)sharedApplication;// 单例命名
- 委托或代理方法命名第一个参数最好能相关某个对象。
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
- 私有方法不要以下划线“_“开头,因为系统私有方法保留此方式。
- 自定义方法和系统方法重名,建议在方法开头加前缀”xx_methodName“。
3.5 常量和宏的命名
- const常量外部声明:在Objective-C文件中优先采用FOUNDATION_EXTERN和UIKIT_EXTERN,而非C语言中的extern。
- const常量采用驼峰命名原则。
- const常量根据作用域适当加上前缀(含项目前缀):可供外部使用需加上相应的类名或者模块前缀,仅文件内部使用需要加上小写字母“k”.
- 宏定义每个字母采用大写,单词之间用下划线“_”间隔。
- 宏定义也可根据作用范围加上适当前缀,避免命名冲突。
3.6 枚举的命名
- 使用枚举来定义一组相关的整数常量,增强代码的可读性。
- 枚举可根据作用域添加前缀(含项目前缀),格式:[相关类名或功能模块名] + [描述] + [状态]。
- 建议优先采用Objective-C的声明NS_ENUM和NS_OPTIONS,少采用C语言形式的enum等枚举声明.
- 枚举定义时需指定None状态,并且其rawValue一般为起始值0。
// NS_ENUM
typedef NS_ENUM(NSInteger, UIStatusBarAnimation) {
UIStatusBarAnimationNone = 0,
UIStatusBarAnimationFade = 1,
UIStatusBarAnimationSlide = 2,
}
typedef NS_OPTIONS(NSUInteger, UIRemoteNotificationType) {
UIRemoteNotificationTypeNone = 0,
UIRemoteNotificationTypeBadge = 1 << 0,
UIRemoteNotificationTypeSound = 1 << 1,
UIRemoteNotificationTypeAlert = 1 << 2,
UIRemoteNotificationTypeNewsstandContentAvailability = 1 << 3,
}
3.7 通知命名
- 外部声明:在Objective-C文件中优先采用FOUNDATION_EXTERN和UIKIT_EXTERN,而非C语言中的extern。
- 通知的命名一般都是跨文件使用的,需添加项目前缀。
// [相关联类名或者功能模块名] + [will/Did](可选) + [描述] + Notification
UIApplicationDidEnterBackgroundNotification
UIApplicationWillEnterForegroundNotification
3.8 类型别名命名
- 根据作用域添加前缀(含项目前缀),格式:[类名或功能模块名] + [描述]。
4.文件资源命名规范
- 资源文件命名也需加上项目前缀。
- 资源文件名全小写,单词之间用下划线“_”间隔。
- 资源文件命名格式:[项目前缀] + [业务] + [文件名]
- 图片文件命名格式:[项目前缀] + [业务] + [类型] + [状态]。
// 常见类型:logol,icon,img
// 常见状态:normal,selected,highlight
UIImage *image = [UIImage imageNamed:@"wt_mine_setting_normal"];
5.代码警告处理
- 注意警告问题的隐蔽性,因此最好修复警告。
- 警告类型的查看步骤:选中警告 -> 右键Reveal in Log(不编译Reveal in Log是灰色的,因此先编译) ->查看方括号的内容
- 如果需要忽略警告,建议优先代码push或者pop处理。
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
// 造成警告的代码
#pragma clang diagnostic pop
- 如果警告数量过大,检查警告类型以及必要性,可xcode配置忽略此类型警告。步骤:选中工程 -> TARGETS -> Build Settings -> Other Warning Flags。
忽略单个和全局配置稍有差别,如下举例:
push/pop Other Warning Flags
-Wformat —-> -Wno-format
-Wunused-variable —-> -Wno-unused-variable
-Wundeclared-selector —-> -Wno-undeclared-selector
-Wint-conversion —-> -Wno-int-conversion
- 也可以在pch等大范围作用域的头文件中添加代码来忽略后续警告:#pragma clang diagnostic ignored “警告名称” 。
6.外部库文件引入
- 库文件引入最好把警告处理掉。
- 库文件引入优先采用CocoaPods引入,并且指定版本号。
- 源文件方式需引入文件到工程目录下。
- 源文件方式需注意有无版本说明信息(可能在README文件中,也可能在某个.h头文件中,又或者有Version文件)没有时需在库文件目录下新增版本说明文件,
7.代码版本管理
- 版本管理工具:svn 或 git。
- svn文件管理配置:目录~/.subversion打开config文件全局配置global-ignore,所有的仓库都会受到影响,而svn:ignore只影响仓库目录。
- git文件管理配置:.gitignore_global为全局配置,而仓库目录下的.gitignore文件仅注明本仓库被git忽略的文件,常见语法如下:
- 井号(#)用来添加注释用的,比如 "#注释"。
- build/ : 星号()是通配符,build/*则是要说明要忽略 build 文件夹下的所有内容。
- *.pbxuser : 表示要忽略后缀名为.pbxuser的文件。
- !default.pbxuser : 感叹号(!)是取反的意思,*.pbxuser 表示忽略所有后缀名为.pbxuser的文件,如果加上!default.pbxuser则表示,除了default.pbxuse忽略其它后缀名为pbxuse的文件。
- 提交信息规范:
- BUG类型为“Fix + [BUG编号] + [BUG描述]”。
- 任务类型为“Done + [任务编号] + [任务描述]”。
- 任务中间态为“Doing + [任务编号] + [任务描述]”。
- 引入类库为“import + [类库名]”。