前言
核心原则:代码应该简洁易懂,逻辑清晰
规范级别
- 【必须】:必须遵守。是不得不遵守的约定,一旦违反极有可能引起严重后果。
- 【建议】:建议遵守。长期遵守这样的约定,有助于维护系统的稳定和提高合作效率。
参考资料:
- https://www.jianshu.com/p/c818c00e0690
- https://juejin.im/post/5940c8befe88c2006a468ea6
- http://www.hudongdong.com/talk/448.html
命名规范
类命名
- 【必须】类名(不包括类别和协议名)应该用大写开头的大驼峰命名法。类名中应该包含一个或多个名词来说明这个类(或者类的对象)是做什么的。
- 【必须】前缀:项目公共模块用项目前缀,其他各个模块用各自模块前缀(OC必须,swift?)
类别命名
- 【必须】类名+扩展(UIImageView +Web)
- 【必须】类别的方法应该都使用一个前缀(型如hp_myCategoryMethodOnAString ),以防止Objective-
C代码在单名空间里冲突。如果代码本来就不考虑共享或在不同的地址空间(address-space),方法命名规则就没必要恪守了。 //前缀在Swift不用,OC建议用
协议(委托/代理)命名
- 【建议】与类命名相同,此外需添加“Delegate”后缀
- 【建议】有时候protocol只是声明了一堆相关方法,并不关联class。这种不关联class的protocol使用ing形式以和class区分开来。比如NSLocking而非NSLock。
- 【建议】如果proctocol不仅声明了一堆相关方法,还关联了某个class。这种关联class的protocol的命名取决于关联的class,然后再后面再加上protocol或delegate用于显示的声明这是一份协议。
方法命名
- 【必须】首字母小写,之后每个单词首字母都大写
- 【建议】方法名使用动词短语 举例:- (void)setPostValue:(int)value
Delegate方法命名规范
- 【建议】以触发消息的对象名开头,省略类名前缀并且首字母小写。
- 【建议】使用did或will这两个情态动词通知delegate对象某件事已经发生或将要发生。
- 【建议】虽然我们可以在delegate方法中使用did和will来询问delegate是否可以代替另一个对象做某件事情,但是使用should看起来更加完美。
方法参数命名
- 【必须】首字母小写,之后每个单词首字母都大写
- 【建议】具有足够的说明性
- 【建议】不需要添加类型前缀 举例:- (void)sendUserInfo:(NSDictionary *)userInfo
变量命名
- 【必须】变量名使用小驼峰法, 使变量名尽量可以推测其用途属性具有描述性。别一心想着少打几个字母,让你的代码可以迅速被理解更加重要。
常量命名
- 【必须】常量(预定义,枚举,局部常量等)使用小写k开头的驼峰法,比如kInvalidHandle ,kWritePerm
- 【必须】枚举请使用oc风格,不要使用C语言风格
图片资源文件命名
如果设计人员提供的图片不复合规范,请开发人员让相关人员进行修改再使用。
- 【建议】模块_类别_功能_状态.png,比如:tab_btn_search_normal.png
Notification命名规范
- 【建议】notification的命名使用全局的NSString字符串进行标识。命名方式如下:
[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification
例如:NSApplicationDidBecomeActiveNotification - 【建议】object通常是指发出notification的对象,如果在发送notification的同时要传递一些额外的信息,请使用userInfo,而不是object。
- 【建议】notification名常量应该在.h头文件中暴露给外部,而字符串常量真正的赋值是在.m文件中。如下:
.h文件
extern NSString *const WSNetworkReachablityStatusDidChangedNotification;
.m文件
NSString * const WSNetworkReachablityStatusDidChangedNotification = @"WSNetworkReachablityStatusDidChangedNotification";
UI控件命名
- 【必须】前缀还是后缀?比如:btnTitle 或 titleBtn?
//titleBtn,控件类型写在最后 - 【必须】通用缩写包括?(btn/lbl...)
//长类名可以缩写,短的建议写完整
编码规范
格式规范
- 【必须】一元运算符与变量之间没有空格,比如:!bValue
- 【必须】二元运算符与变量之间必须有空格。比如:fHeight = fWidth + fLength;
- 【必须】多个不同的运算符同时存在时应该使用括号来明确优先级
- 【必须】留一个空格在 - 或 + 和返回类型之间
- 【必须】使用Tab空格进行缩进。
- 【建议】如果参数过多,推荐每个参数各占一行。使用多行的情况下,以参数前的冒号用于对齐。
- 【建议】左大括号不要另起一行。前面加空格
- 【建议】不用在冒号前添加空格。
- 【建议】逗号后面跟一个空格
- 【建议】左括号后面和右括号前面都不需要加空格
- 【建议】遵循 xcode 内置缩紧格式,多用格式化工具
- 【建议】删除多余的空行
- 【建议】所有方法与方法之间空1行
- 【建议】所有代码块之间空1行
- 【建议】block参数放到最后
Init方法规范
- 【必须】所有secondary 初始化方法都应该调用designated 初始化方法。
- 【必须】所有子类的designated初始化方法都要调用父类的designated初始化方法。使这种调用关系沿着类的继承体系形成一条链。
- 【必须】如果子类的designated初始化方法与超类的designated初始化方法不同,则子类应该覆写超类的designated初始化方法。(因为开发者很有可能直接调用超类的某个designated方法来初始化一个子类对象,这样也是合情合理的,但使用超类的方法初始化子类,可能会导致子类在初始化时缺失一些必要信息)。
- 【必须】如果超类的某个初始化方法不适用于子类,则子类应该覆写这个超类的方法,并在其中抛出异常。
- 【必须】禁止子类的designated初始化方法调用父类的secondary初始化方法。否则容易陷入方法调用死循环
- 【必须】另外禁止在init方法中使用self.xxx的方式访问属性。如果存在继承的情况下,很有可能导致崩溃。参考:为什么不能在init和dealloc函数中使用accessor方法
//可以尝试使用filnal关键字
dealloc规范
- 【必须】不要忘记在dealloc方法中移除通知和KVO。
- 【建议】dealloc 方法应该放在实现文件的最上面,并且刚好在 @synthesize 和 @dynamic 语句的后面。在任何类中,init 都应该直接放在 dealloc 方法的下面。
- 【必须】在dealloc方法中,禁止将self作为参数传递出去,如果self被retain住,到下个runloop周期再释放,则会造成多次释放crash
- 【必须】和init方法一样,禁止在dealloc方法中使用self.xxx的方式访问属性。如果存在继承的情况下,很有可能导致崩溃。 参考:为什么不能在init和dealloc函数中使用accessor方法
Notification规范
-
【建议】post通知时,object通常是指发出notification的对象,如果在发送notification的同时要传递一些额外的信息,请使用userInfo,而不是object。
//能用模型尽量用模型,提高容错率 -
【建议】通知名使用extern方式。
IO规范
- 【建议】尽量少用NSUserDefaults。说明:[[NSUserDefaults standardUserDefaults] synchronize] 会block住当前线程,知道所有的内容都写进磁盘,如果内容过多,重复调用的话会严重影响性能。 //USer可以用数据库存储代替归档
- 【建议】一些经常被使用的文件建议做好缓存。避免重复的IO操作。建议只有在合适的时候再进行持久化操作。
Collection规范
- 【必须】不要用一个可能为nil的对象初始化集合对象,否则可能会导致crash。
- 【必须】同理,对插入到集合对象里面的对象也要进行判空。
- 【必须】注意在多线程环境下访问可变集合对象的问题,必要时应该加锁保护。不可变集合(比如NSArray)类默认是线程安全的,而可变集合类(比如NSMutableArray)不是线程安全的。
- 【必须】禁止在多线程环境下直接访问可变集合对象中的元素。应该先对其进行copy,然后访问不可变集合对象内的元素。
- 【必须】注意使用enumerateObjectsUsingBlock遍历集合对象中的对象时,关键字return的作用域。block中的return代表的是使当前的block返回,而非使当前的整个函数体返回。
- 【必须】禁止返回mutable对象,禁止mutable对象作为入参传递。
- 【建议】如果使用NSMutableDictionary作为缓存,建议使用NSCache代替。
- 【建议】集合类使用泛型来指定对象的类型。
//遍历时可以使用block方式代替for循环
分支语句规范
- 【建议】if条件判断语句后面必须要加大括号{}。不然随着业务的发展和代码迭代,极有可能引起逻辑问题。
- 【必须】多于3个逻辑表达式必须用参数分割成多个有意义的bool变量。
- 【建议】遵循gold path法则,不要把真正的逻辑写道括号内。
// 不建议
- (void)someFuncWith:(NSString *)parameter {
if (parameter) {
// do something
[self doSomething];
}
}
// 建议
- (void)someFuncWith:(NSString *)parameter {
if (!parameter) {
return;
}
// do something
[self doSomething];
}
- 【建议】对于条件语句的真假,因为 nil 解析为 NO,所以没有必要在条件中与它进行比较。永远不要直接和 YES 和 NO进行比较,因为 YES 被定义为 1,而 BOOL 可以多达 8 位。
// 建议
if (isAwesome)
if (![someObject boolValue])
// 禁止这样做
if ([someObject boolValue] == NO) { }
if (isAwesome == YES) { }
- 【必须】使用switch...case...语句的时候,不要丢掉default:。除非switch枚举。
- 【必须】switch...case...语句的每个case都要添加break关键字,避免出现fall-through。(swift除外)
- 【必须】条件表达式如果很长,则需要将他们提取出来赋给一个BOOL值。比如:
let nameContainsSwift = sessionName.hasPrefix("Swift")
let isCurrentYear = sessionDateCompontents.year == 2014
let isSwiftSession = nameContainsSwift && isCurrentYear
if (isSwiftSession) {
// Do something
}
- 【建议】条件语句的判断应该是变量在左,常量在右. 比如:if ( count == 6)
懒加载规范
懒加载适合的场景:
-
一个对象的创建依赖于其他对象。
-
一个对象在整个app过程中,可能被使用,也可能不被使用。
-
一个对象的创建需要经过大量的计算或者比较消耗性能。除以上三条之外,请不要使用懒加载。
-
【建议】懒加载本质上就是延迟初始化某个对象,所以,懒加载仅仅是初始化一个对象,然后对这个对象的属性赋值。懒加载中不应该有其他的不必要的逻辑性的代码,如果有,请把那些逻辑性代码放到合适的地方。
-
【必须】不要滥用懒加载,只对那些真正需要懒加载的对象采用懒加载。
多线程规范
- 【必须】禁止使用GCD的dispatch_get_current_queue()函数获取当前线程信息。
- 【必须】对剪贴板的读取必须要放在异步线程处理,最新Mac和iOS里的剪贴板共享功能会导致有可能需要读取大量的内容,导致读取线程被长时间阻塞。
- 【必须】仅当必须保证顺序执行时才使用dispatch_sync,否则容易出现死锁,应避免使用,可使用dispatch_async。
- 【必须】禁止在非主线程中进行UI元素的操作。
- 【必须】在主线程中禁止进行同步网络资源读取,使用NSURLSession进行异步获取。当然,你可以在子线程同步获取网络资源,但还是上面的那一条建议:避免使用dispatch_sync,尽量使用dispatch_async。因为死锁不一定只发生在主线程。
- 【必须】如果需要进行大文件或者多文件的IO操作,禁止主线程使用,必须进行异步处理。
内存管理规范
- 【建议】请慎重使用单例,避免产生不必要的常驻内存。
说明:我们不仅应该知道单例的特点和优势,也必须要弄明白单例适合的场景。UIApplication、access database 、request network 、access userInfo这类全局仅存在一份的对象或者需要多线程访问的对象,可以使用单例。不要仅仅为了访问方便就使用单例。 - 【建议】单例初始化方法中尽量保证单一职责,尤其不要进行其他单例的调用。极端情况下,两个单例对象在各自的单例初始化方法中调用,会造成死锁。
- 【必须】在dealloc方法中,禁止将self作为参数传递出去,如果self被retain住,到下个runloop周期再释放,则会造成多次释放crash。
- 【建议】除非你清楚的知道自己在做什么。否则不建议将UIView类的对象加入到NSArray、NSDictionary、NSSet中。如有需要可以添加到NSMapTable 和 NSHashTable。因为NSArray、NSDictionary、NSSet会对加入的对象做strong引用(即使你把加入的对象进行了weak)。而NSMapTable、NSHashTable会对加入的对象做weak引用。
说明:简单的说,NSHashTable相当于weak的NSMutableArray;NSMapTable相当于weak的NSMutableDictionary.
延迟调用规范
- 【必须】performSelector:withObject:afterDelay:要在有Runloop的线程里调用,否则调用无法生效。
说明:异步线程默认是没有runloop的,除非手动创建;而主线程是系统会自动创建Runloop的。所以在异步线程调用是请先确保该线程是有Runloop的。
使用performSelector:withObject:afterDelay:和cancelPreviousPerformRequestsWithTarget组合的时候要小心:
afterDelay会增加引用计数,而cancel会对引用计数减一
如果receiver在引用计数器为1的时候,调用cancel会立即回收receiver。后续再次调用receiver的方法就会crash。所以我们需要使用weakSelf并判空。如下:
__weak typeof(self) weakSelf = self;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
if (!weakSelf) {
// NSLog(@"self dealloc");
return;
}
[self doOther];
长度规范
- 【建议】一个类的代码行数尽量不要超过 500 行
- 【必须】一个函数的长度必须限制在50行以内,建议20行以内
- 【建议】代码中的每行文本不要超过 80 个字的长度
其他
- 【必须】测试的数据或代码要添加警告标记,比如:#warning; 同时加上“test”标签
- 【建议】用到classname,建议用NSStringFromClass,不要直接写字符串。
- 【建议】多用字面量语法。比如:NSArray *arr = @[@"1",@"2",@"3"];
- 【必须】如果方法和类没有使用到,请删除它
- 【建议】删除未被使用的资源文件
- 【建议】图片资源文件,强烈建议采用Images.xcassets管理。
注释规范
- 【必须】如果方法、函数、类、属性等需要提供给外界或者他人使用,必须要加注释说明。
- 【必须】如果你的代码以SDK的形式提供给其他人使用,那么接口的注释是必须的。必须对暴露给外界的所有方法、属性、参数加以注释说明。
- 【建议】注释应该说明其作用以及注意事项(如果有)。
- 【建议】因为方法或属性本身就具有自我描述性,注释应该简明扼要,说明是什么和为什么即可。
- 【建议】方法注释的时候尽量采用 Xcode 快捷注释。如 cmd + opt + /
- 【必须】每个类都要有类的注释说明
- 【建议】容易产生歧义的代码需要些注释
- 【建议】涉及到比较深层专业知识的代码(注释要体现出实现原理和思想)。
代码设计规范
【建议】面向协议编程
- 因为协议是不依赖于某个对象的,所以通过协议,我们可以解开两个对象之间的耦合。
类的设计规范
- 【建议】尽量减少继承,类的继承关系不要超过3层。可以考虑使用category、protocol来代替继承。
- 【建议】.h文件中尽量不要声明成员变量。
- 【建议】.h文件中的属性尽量声明为只读。
- 【建议】.h文件中只暴露出一些必要的类、公开的方法、只读属性;私有类、私有方法和私有属性以及成员变量,尽量写在.m文件中。
- 【建议】把类的实现代码分散到便于管理的多个分类中
- 【建议】一个类的代码行数尽量不要超过 500 行
函数
- 【必须】一个函数的长度必须限制在50行以内,建议20行以内
- 【必须】一个函数只做一件事(单一原则)
代码组织规范
#pragma mark - Lifecycle
- (instancetype)init {}
- (void)dealloc {}
- (void)viewDidLoad {}
- (void)viewWillAppear:(BOOL)animated {}
- (void)didReceiveMemoryWarning {}
#pragma mark - Custom Accessors
- (void)setCustomProperty:(id)value {}
- (id)customProperty {}
#pragma mark - IBActions
- (IBAction)submitData:(id)sender {}
#pragma mark - Public
- (void)publicMethod {}
#pragma mark - Private
- (void)privateMethod {}
#pragma mark - UITextFieldDelegate
#pragma mark - UITableViewDataSource
#pragma mark - UITableViewDelegate
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {}
#pragma mark - NSObject
- (NSString *)description {}
工程结构规范
-
【必须】为了避免文件杂乱,物理文件应该保持和 Xcode 项目文件同步。Xcode 创建的任何组(group)都必须在文件系统有相应的映射。为了更清晰,代码不仅应该按照类型进行分组,也可以根据业务功能进行分组。
//必须强制使用实体文件夹 -
【必须】项目根目录添加README.md文件,内容包含:接口文档地址和简单说明、需求文档地址。
需要重新确定工程结构
参考方案:
1.主目录结构
-ProjectDemo
--README.md //项目说明
--Features //模块。包含各个模块的Model,View,Controller,Manager
--categories //类目。包含各种类的分类
--Frameworks //系统框架。包含导入的系统的框架
--Helpers //帮助类。包含网络,数据库,归档,定位等操作类的封装和实现
--Utilites //工具类,一些非对象的,而是类方法调用的类
--Vendors //第三方库。部分需要修改或者不支持cocoapod的第三方的框架引入
--Config //配置。包含宏定义文件,全局配置文件,全局常量文件,颜色配置文件
--Resources //资源。包含plist,image,html,bundle,Localizable.strings等
--AppEntry //程序入口。包含AppDelegate,main.c,info.plist
-PAHealthTests
-PAHealthUITests
-Products // 系统自动生成的.app所在文件夹
-Pods // 采用 CocoaPods 管理的第三方库。
2.模块目录结构
-- Features
---Base //MVC的基类或者通用类
----Models //数据模型
----Views //视图
----Controllers //控制器
----Manager //store层的数据管理类
---Home
----Models
----Views
----Controllers
----Manager
---UserCenter
----Models
----Views
----Controllers
----Manager
---UserEntry
----Models
----Views
----Controllers
----Manager
---Payment
----Models
----Views
----Controllers
----Manager
…
之前的方案:
AppDelegate AppDelegate.h(.m) 文件,整个应用的入口文件
Common 公用的组件
Models 与数据相关的 Model 文件
index index 模块
… 其他模块
Sections 这个目录下面的文件对应的是 app 的具体单元 / 模块
index index 模块
… 其他模块
ThirdParty 第三方的类库 /SDK
Resources app 的资源文件
images 图片资源,为方便管理按 Sections 生成子目录,公共的放 common 目录
common 公共的图片资源
index index 模块
… 其他模块,与 Sections 子目录一致
sounds 声音资源
other 其他资源
协作规范
- 【必须】有文件的改动时(比如:增删文件、修改文件名),先commit之前的改动,再从远程pull,再进行文件改动,改动完成后立马commit,然后pull,最后push。接下来再进行其他修改。这样能尽量避免xcodeproj文件冲突。
//使用xib,要各自建立独立的SB、或者xib文件,避免在一个文件中修改
Swift相关规范
安全相关规范
- 【建议】避免对 可选类型 强解包
- 【建议】避免隐式解析的可选类型
- 【建议】尽量不要使用 as! 强try。 try!
- 【建议】能用 let 尽量用 let 而不是 var
编码相关规范
- 【建议】在创建字典或者 数组的时候。推荐类型写在 冒号后面 不推荐写在 = 号后面进行创建对象,推荐:Let a: [string] = []. 不推荐:let a = string.
- 【建议】尽早地用guard return 或者 break
- 【建议】需要时才写上 self
- 【建议】如果一个变量判断 nil 时,在后续需要使用的时候,采用 if let 或者guard let 形式。
- 【建议】当遍历一个集合变形为另一个集合时,推荐使用 map,filter ,reduce。
- 【建议】能不用下标访问数组尽量不用 如 .first .last 。在遍历数组时候采用 for item in items。 尽量不用 for i in 0…10
- 【建议】使用类型推断。 省略显而易见的类型有助于提高可读性 (swift 进阶书里推荐的)
//能用进阶写法完成时,尽量使用进阶写法,比如map,reduce
代码设计相关规范
- 【建议】代使用 extention 区分理协议方法 ,并在顶部 标 // MARK:注释内容
- 【建议】除非你的设计就是希望某个类被继承使用,否则都应该将它们标记为 final (swift 进阶书里推荐的)
- 【建议】尽可能地对现有的类型和协议进行扩展,而不是写一些全局函数。这有助于提高可读性,让别人更容易发现你的代码 (swift 进阶书里推荐的)
- 【建议】在使用switch 语句时候 推荐按照概率由高到低排列
热修复相关(针对需要支持热修复的代码)
- Struct 结构体不能使用,因为无法桥接成 OC 对象。无法拥有动态属性
- 声明 Class 需要继承
NSObject
,并且对属性和方法进行动态说明,也就是需要添加相应的@objc
,dynamic
,@objcMembers
关键字。
class TestProject: NSObject {
@objc dynamic fileprivate var name = ""
override init() {
name = ""
super.init()
}
@objc dynamic func testLog() {
print("原始打印log")
}
}
@objcMembers
class TestProject: NSObject {
fileprivate var name = ""
override init() {
name = ""
super.init()
}
func testLog() {
print("原始打印log")
}
}
- Enum 枚举尽量少用,需要一些特殊处理,并且枚举中不能有其他方法。即使桥接成OC枚举,JavaScript没办法获取。
@objcpublicenumNVActivityIndicatorType: Int {
case Blank
case BallPulse
case BallGridPulse
case BallClipRotate
case SquareSpin
}
- Protocol 协议需要在相应的地方添加
@objc
关键字, 并且继承NSObjectProtocol
协议。
@objc protocol TestDelegate: NSObjectProtocol {
@objc func TestClick(Str: String)
}
-
元组类型不能使用。
-
需要在 JavaScript 调用或者修改的方法都必须具有动态属性,而且方法所用到的参数以及返回的对象都必须具有动态属性。
-
调用 C 函数 函数很麻烦需要做绑定操作,所以尽量少用,而且不能保证所有的 C 函数 都能绑定调用。尤其是内联函数。
-
常量、枚举、宏、全局变量不要使用,因为 JavaScript 没办法获取。
-
指针尽量不要使用,对于 Swift 和 JavaScript 语言来说,指针使用麻烦,容易出错。指针使用方法请看JPMemory使用文档
-
方法里的代码尽量不能太多,尽量不要超过 30 行。对臃肿代码,尤其是逻辑比较重要的代码进行方法拆分。
-
项目中对于公用工具类最好具备动态属性,而且如果是纯 Swift 写的就尽量中间封装动态中间类。