一. 宗旨
- 尊重苹果原生的命名和书写规范
- 能清晰表达含义
- 简洁而不省略
- 减少不必要的注释
二. 编码规范
1. 运算符
使用运算符(+,-,*, /,=, ==,->)的时候,前后加空格
let a = 5 * 4 - 10
2. 逗号后面空格,冒号紧跟前面参数名,左大括号不用换行
func add(a: T, b: T) -> Int {
if let intA = a as? Int,let intB = b as? Int {
return intA + intB
}
fatalError()
}
3. 尽量不用self,除非有必要
var aName = "小明"
var bName = "小花"
func modifyName(aName: String, cName: String) {
self.aName = aName
bName = cName
}
4. 访问省略
类方法的点语法禁止省略,枚举的掉语法可以尽量省略。
enum Gender {
case male
case female
}
let xiaomingSex : Gender = .male
let xiaomingSkin : UIColor = UIColor.yellow
枚举可以通过上下文快速知道。类不可以。
5. 使用// MARK: -
和extension
对代码按 功能/协议 分割
class <#Class#>: <#Class#> {
// life cycle
override func viewDidLoad() {
super.viewDidLoad()
baseSetting()
initUI()
sendNetworking()
}
// MARK: - Setter & Getter
}
//MARK: 通知回调,闭包回调,点击事件
extension <#Class#> {
}
//MARK: 网络请求
extension <#Class#> {
func sendNetworking() { }
func sendNetworking_one() { }
func sendNetworking_two() { }
}
//MARK: UI的处理,通知的接收
extension <#Class#> {
func baseSetting() {
self.title = "<#...#>"
}
func initUI() {
}
}
//MARK: 代理方法
6. 行缩进
缩进 4个空格代表一个缩进。 建议使用Xcode的缩进Control + i
三. 命名规范
1. 命名用语
命名用美式英语,不要使用中文或者拼音。
2. 尊重先例
尊重先例(苹果命名/行业默认命名规范。 命名数组用Array不用list。
3. 驼峰命名法
大驼峰 | 小驼峰 | k前缀 | |
---|---|---|---|
说明 | 所有单词首字母都大写 | 第一个单词要小写,后面的单词首字母大写 | k作为前缀的驼峰命名 |
使用 | 类、结构体、枚举、协议、文件名 | 变量、属性、函数、方法 | 非单例的静态常量命名 |
4. 文件命名
建议一个文件实现一个 Class。命名规则为 工程前缀 + 模块名 + 自定义 + 父类后缀
.
BBPhoneSeasonListViewController
BBPhoneSeasonDetialViewModel
BBLiveHomeCell
BBLiveHomeItemView
5. 命名中出现缩写
当命名里出现缩写词时,根据首字母全大写或小写。
TableViewCell 这种基本上不会和 CollectionViewCell 撞车,直接都叫 Cell。其它的大部分父类后缀维持就好.
6. 分类存放
文件按照功能分类存放
7. 命名Bool
以is
作为前缀。
8. 懒加载
声明对象的时候尽量使用懒加载。
lazy var dataArrayM = NSMutableArray()
lazy var bgView: UIView = {
let view = UIView()
// 属性赋值
return view
}()
9. 必要的时候使用介词
,_
和默认值
使阅读更符合习惯,减少冗余词汇,提供更多的可能性。
extension UIBarButtonItem {
public static func setImage(_ image: UIImage?, on target: UIViewController, selector: Selector, isLeft: Bool = false) -> UIBarButtonItem {
return UIBarButtonItem.init()
}
}
10. 命名不歧义
extension SomeClass {
✅
public mutating func remove(at position: Index) -> Element?
❌
public mutating func remove(_ position: Index) -> Element?
❌
public mutating func removeElement(_ member: Element) -> Element?
}
some.remove(at: x)
some.remove(x)
some.removeElement(x)
11. 根据角色承担的任务命名变量、参数、关联,而不是类型限制
let string = "Hello"
let greeting = "Hello"
12. 消除声明角色的弱类型信息
❌
func add(_ observer: NSObject, for keyPath: String)
✅
func addObserver(_ observer: NSObject, forKeyPath path: String)
13. 阅读更符合正常语法
// 正常语法
“x, insert y at z”
✅
x.insert(y, at: z)
❌
x.insert(y, position: z)
14. 创建型方法和创建型工厂方法的命名
- 创建型方法: 使用
init
或make
。
那么第一个参数的标签不要考虑组合成一个句子。因为这样的函数名称已经知道是要创建一个实例,那么参数再用介词修饰让句子流畅显得多余。正确的示范:
✅
let aColor = Color(red: 32, green: 64, blue: 128)
❌
let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128)
- 创建型工厂方法
使用make
开头.
SomeClass.makeButton()
四. 资源文件命名
1. 模块前缀
首先资源必须以 主要模块名称定义
中的名称开头。然后资源在模块名称下划线后,以驼峰命名法命名。例如:
live_silverSeed.png
live_goldSeed.png
资源较多分二级目录的情况下再加一层前缀,例如充电相关资源:
misc/battery/misc_battery_6.png
misc/battery/misc_battery_12.png
2. 功能后缀(可选)
第一层后缀以图片用途结尾,常见的几个定义如下:
类型 | 后缀
---- | ----
图标 | _ico
按钮 | _btn
背景 | _bg
加上后缀后的命名举例:
recommend_refresh_btn.png
如果资源较少不加也能一眼看出来完全可以不加,资源多的话建议加上方便区分。
3. 状态后缀
第二层后缀描述图片所表达的状态:
状态 | 后缀 |
---|---|
普通(Normal) | _n(可选,建议不加) |
高亮(Highlighted) | _h(必选) |
选中(Selected) | _s(必选) |
禁用(Disabled) | _d(必选) |
用上面的按钮资源举例:
recommend_refresh_btn_d.png
4. 特殊后缀
如果资源分成多块之类的特殊情况,加一层特殊后缀,也用下划线隔开。
common_tip.png 因拉伸需求需要拆分成左右两块:
common_tip_left.png
common_tip_right.png
common_loading 是一系列关键帧组成的动画:
common_loading_1.png
...
common_loading_5.png
5. 分辨率后缀
最后就是大家都知道的分辨率后缀了。1x 资源是不需要加 @1x 的!
2x,3x 资源请记得加上对应的后缀 @2x,@3x。
如果设计给了一张大图说所有设备统一用这个资源,记得把它当做 1x 资源。
五. 语法规范
1. 可选类型的拆包取值 if let
let string : String? = "name"
if let str == string {
print(str)
}
2. as! 尽量不要使用
多用 as?
和??
可选给默认值的形式
3. 常量的定义
如无必要尽量放到类里面。避免污染命名空间。
class Animal: NSObject {
static let height: CGFloat = 100
}
4. 最短路径原则
根据判断条件的长短,选择使用if
还是guard
5. 遍历数组
多用 forin
或map
flatMap
等高级函数。
let array = [1, 2, 3, 4, 5, 6]
for item in array {
print(item)
}
for (index, item) in array.enumerated() {
print(index,item)
}
array.map {
print($0)
}
6. 反向传值
反向传值统一使用Delegate。
- 使用delegate的可以定义一次,声明多个方法(
required
oroptional
)。 - 可以寻根溯源(能点进去)
六. 注释规范
1. 代码尽量不要注释,除非有必要注释。
需要注释的情况
- 多含义的字段(status,type等)
- 复杂的业务逻辑
- 公共方法,公共类
2. 注释快捷键
- 单行注释
“command + /”,即“⌘ + /”
- 多行注释
/** ... */
- 注释文档
“command + Option + /”,即“⌘ + ⌥ + /”
注释添加以后,在调用该函数时,按下“Option + 左键”,即“⌥ + 左键”,就能看到该函数的注释信息。 - 注意区分
注释
和带标记属性的注释
//
和/**/
是注释///
和/** */
带有标记性质 - 分组注释
//MARK: -你要写的注释
- todo注释
//TODO:需要接着 写得东西
- 警告注释
// FIXME:要解决的BUG
七. 设计规范
遵循行业规范- 六大设计模式(solid规范)
- 单一职责原则(Single Responsibility Principle, SRP)
- 开闭原则(Open-Closed Principle, OCP)
- 里氏代换原则(Liskov Substitution Principle, LSP)
- 依赖倒转原则(Dependency Inversion Principle, DIP)
- 接口隔离原则(Interface Segregation Principle, ISP)
- 迪米特法则(Law of Demeter, LoD)
六大原则首字母去除重复的即 solid(固体的;可靠的;立体的;结实的;一致的)
1. 单一职责原则
一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。附详解
2. 开闭原则原则
一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。附详解
3. 里氏代换原则
所有引用基类(父类)的地方必须能透明地使用其子类的对象。附详解
4. 依赖倒置原则
抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程附详解
5. 接口隔离原则
使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。附详解
6. 迪米特法则
一个软件实体应当尽可能少地与其他实体发生相互作用附详解
八. 组件化
从第一代码农写下第一行代码开始到上个世纪的80年代的软件危机,码农一直在考虑一个问题,怎么让写代码容易。
抛开找大牛,大神程序员这条路(你以为大牛,大神那么容易找啊),最后自然而然形成的一套思路就是大团队的协同合作(如同cpu发展史一样,从飙主频到飙核数)。
协同合作?—– 这个可就麻烦了。。。。团队。。。。还合作。。。。
几乎所有的码农开始代码的时候,写代码的都是以自我为中心的。怎么解释这种情况呢,就是cow code—牛仔代码,代码风格随意看心情。这就导致了写代码协作起来极为麻烦,为什么呢?我写代码的时候 ,我和上帝知道什么我写的什么,过了一个月就只有上帝知道写的什么了。
你这让人怎么干活。。。。活那么多。。。。人那么多。。。。相互坑不出活,老板会fire掉大家的。
很早就有人来想办法解决这个问题,在软代时代就已经有解决这个问题的法宝--组件化。当然那时候不是那么叫的,是通过两个原则来规范这个问题的,这两个原则就是:内聚性和耦合性。
意思就是:哥,我想按时回家哄妹子!!!你怎么写代码我不管,你的功能全在这你这儿实现(内聚性),不要让我还帮写你那块功能。另外,哥,求你了,你代码不要block(影响)我的代码(低耦合性)。
既然解决问题的思路在这儿,大牛们一代代前赴后继的在这条路上狂奔下去。
-----引用《知乎》解答
在一个项目越来越大,开发人员越来越多的情况下,项目会遇到很多问题。
- 业务模块间划分不清晰,模块之间耦合度很大,非常难维护。
- 所有模块代码都编写在一个项目中,测试某个模块或功能,需要编译运行整个项目。
- 老大写的公共方法或组件老是被一些刁民妄想改动以便自己的功能。
- 多个项目同时用一套架构,经常遇到版本不同步的困扰,新增了方法没法保证全部同步。
- 同一个项目中协作开发,经常出现,"卧槽,谁TMD又动我的代码了"
在公司项目开发中,如果项目比较小,普通的单工程+MVC架构就可以满足大多数需求了。但是像淘宝、蘑菇街、微信这样的大型项目,原有的单工程架构就不足以满足架构需求了。
就拿淘宝来说,淘宝在13年开启的“All in 无线”战略中,就将阿里系大多数业务都加入到手机淘宝中,使客户端出现了业务的爆发。在这种情况下,单工程架构则已经远远不能满足现有业务需求了。所以在这种情况下,淘宝在13年开启了插件化架构的重构,后来在14年迎来了手机淘宝有史以来最大规模的重构,将其彻底重构为组件化架构。
如何实现组件化?
通过iOS开发伴侣cocoapods来管理组件化代码。大致意思就是将代码发布到cocoapods上面去。每个一个组件就相当于一个工程,通过cocopods来管理代码,在主工程中使用。一些方便公开的组件可以发布为公有库共所有人使用。不方便公开的组件,放到公司私有仓库里面仅供自己组员使用。
如何发布到cocoapods?
- 什么是cocoaPods?
- 如何编写podspec文件?
- 如何发布公有库?
- 如何发布私有库?
这样就达到了我们的目的将 图1
变成了图2
的样子.
也就达到了低耦合高内聚的目的了。
but 中间层是什么鬼?
中间层相当于电脑的cpu,来调度被组件化的模块。所有的模块都向中间层注册自己。各个模块之间的关系都是通过中间层联系的。
中间层即 路由!!!
九. 路由
思考如下的问题,平时我们开发中是如何优雅的解决的?
- 3D-Touch功能或者点击推送消息,要求外部跳转到App内部一个很深层次的一个界面。
比如微信的3D-Touch可以直接跳转到“我的二维码”。“我的二维码”界面在我的里面的第三级界面。或者再极端一点,产品需求给了更加变态的需求,要求跳转到App内部第十层的界面,怎么处理? - 自家的一系列App之间如何相互跳转?
如果自己App有几个,相互之间还想相互跳转,怎么处理? - 如何解除App组件之间和App页面之间的耦合性?
随着项目越来越复杂,各个组件,各个页面之间的跳转逻辑关联性越来越多,如何能优雅的解除各个组件和页面之间的耦合性? - 如何能统一iOS和Android两端的页面跳转逻辑?甚至如何能统一三端的请求资源的方式?
项目里面某些模块会混合ReactNative,Weex,H5界面,这些界面还会调用Native的界面,以及Native的组件。那么,如何能统一Web端和Native端请求资源的方式? - 如果使用了动态下发配置文件来配置App的跳转逻辑,那么如果做到iOS和Android两边只要共用一套配置文件?
- 如果App出现bug了,如何不用JSPatch,就能做到简单的热修复功能?
比如App上线突然遇到了紧急bug,能否把页面动态降级成H5,ReactNative,Weex?或者是直接换成一个本地的错误界面? - 如何在每个组件间调用和页面跳转时都进行埋点统计?每个跳转的地方都手写代码埋点?利用Runtime AOP ?
- 如何在每个组件间调用的过程中,加入调用的逻辑检查,令牌机制,配合灰度进行风控逻辑?
- 如何在App任何界面都可以调用同一个界面或者同一个组件?只能在AppDelegate里面注册单例来实现?
比如App出现问题了,用户可能在任何界面,如何随时随地的让用户强制登出?或者强制都跳转到同一个本地的error界面?或者跳转到相应的H5,ReactNative,Weex界面?如何让用户在任何界面,随时随地的弹出一个View ?
iOS界组价化方案
- Protocol注册方案 (暂无了解)
- URL注册方案
- Target-Action runtime调用方案
MGJRoute方案
URL注册方案 蘑菇街 App 的组件化之路 已经说的很清楚了 可以去看下
原理:
- 通过url注册服务, 其他地方通过url, 获取服务
- 框架在维护一个url-block的表格
特点:
- 每个业务组件, 都需要依赖这个框架
- url维护成本高 硬解码
- 可以在组件内部任何地方调用/注册服务, 没有必要统一组件接口服务
target-action方案
原理:
- 每个组件, 提供一个统一披露的接口文件
- 额外的维护一个中间件的分类扩展(在此处进行硬解码 通过运行时进行物理解耦)
- 其他地方通过target-action;的方案进行交互
特点:
- 集约
- 统一了组件api服务
- 组件与框架之间无依赖关系
- 需要额外维护中间件类扩展
主讲Target-Action
1. 向路由注册控制器
/**
* 首页推荐
*/
class Target_Recommend: NSObject,RouteTargetProtocol {
@objc func Action_ViewController(_ params: [String : Any]) -> UIViewController? {
let vc = CTRecommendViewController(params)
return vc
}
}
fileprivate let target_Recommend = "Recommend"
extension MCRoute {
/**
* 这个是首页推荐
*/
public func CTRecommendViewController(_ dict : [String:Any]) -> UIViewController {
let vc = perform(target_Recommend, params: dict, shouldCacheTarget: false)
guard vc != nil else {
return errorController()
}
if let vc2 = vc as? CTRecommendViewController {
return vc2
}else {
return errorController()
}
}
}
2. 如何从路由中调用
let vc = MCRoute.shared. CTRecommendViewController()