Swift代码规范 2022-02-24 周四
语言规约
命名规范
-
【强制】Swift并不需要使用;结束一行代码。
-
【推荐】变量命名多参考苹果库或者优秀的开源库的命名方式。比如Swift 3.0开始,枚举类型首字母都改成小写,去掉了冗余信息,比如UIColor.redColor变成UIColor.red。argument label也去掉了冗余信息,变得非常简洁。
//Swift 2.3
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
//Swift 3.0,cellForRowAtIndexPath简化成cellForRowAt。
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
-
【强制】虽然Swift命名倾向于不加任何前缀,但是仍然强制所有的自定义类型加上一个统一的前缀,比如阿里云App统一使用ALY。
-
【强制】extension跟Objective-C一样,函数必须加一个前缀,比如aly_loadImage,便于理解和使用。不同的模块给同一个类增加相同命名的扩展,编译链接都不会有问题。但是如果同时import这些模块,调用同名的扩展方法会报下面这个错误。
-
【强制】extension跟Objective-C一样,函数必须加一个前缀,比如aly_loadImage,便于理解和使用。不同的模块给同一个类增加相同命名的扩展,编译链接都不会有问题。但是如果同时import这些模块,调用同名的扩展方法会报下面这个错误。
main.swift:10:5: error: 'test' is inaccessible due to 'internal' protection level
str.test()
^
<unknown>:0: note: 'test' declared here
<unknown>:0: note: 'test' declared here
- 【强制】专有名词,如ECS,使用大写,即使出现在方法和属性中。
代码组织
-
【推荐】相同逻辑代码、同一个protocol函数的实现等,比如使用//MARK: ALYUIViewControllerRefreshDataProtocol标记,方便阅读代码。
-
【推荐】类的属性使用lazy var实现,并且放到class的后面,方便阅读代码。
lazy var textLabel : UILabel = {
let label = UILabel()
label.font = UIFont.aly_f10
label.textColor = UIColor.aly_ct_2
label.textAlignment = .center
label.text = "添加"
self.contentView.addSubview(label)
return label
}()
最佳实践
消除警告
-
【强制】在Build Settings里面找到Swift Compiler - Warning Policies,将Treat Warning as Erros设置为Yes,Swift这个设置跟Objective-C不在一起,消除一切编译警告非常有必要。
-
【强制】返回值不需要强制使用的,请使用@discardableResult关键字,否则会产生warning。
@discardableResult
func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
return SessionManager.default.request(urlRequest)
}
避免严重的崩溃
- 【推荐】不要强解Optional类型。强解非常危险,刚开始使用Swift开发非常容易在这块犯错误,导致crash率居高不下。可以通过guard let、if let、??来避免强解。
//guard let适合后面有大量代码依赖foo有值
guard let _foo = foo else {
return
}
print(_foo)
//if let比较灵活
if let _foo = foo {
//do something
}
//??更加灵活,但是一行代码使用过多??可能有性能问题。
print(foo?? "hello world")
避免内存泄露
- 【强制】闭包使用weak或者unowned避免循环依赖。如果明确外部变量在执行代码的过程中,不会变成nil,那么使用unowned,比如视觉元素的lazy代码块中。网络接口的回调是异步的,回调发生时,页面可能已经不存在了,这种场景下,需要使用weak。
let cell = ALYCommonCellObject.Builder()
.title(title: "使用许可协议", color: UIColor.aly_black)
.selectionAction(select: { [unowned self] (cell) in
self?.utlogCounter("Setting", withMonitorPoint: "TermOfService")
})
.build()
- 【强制】deinit里面要移除对所有通知和KVO的观察。
合理选择数据类型
-
【推荐】尽量使用Swift的类型,而非Objective-C的桥接类型,比如使用URL而非NSURL,IndexPath而非NSIndexPath。
-
【参考】数据对象尽量使用struct,而非class。struct是Swift的基础类型,翻看苹果的基础库,可以发现所有的基础类型,比如Int、String等类型都是struct。
-
【强制】Swift支持字符串枚举类型,表达清晰,不易犯错,是一个非常好用的功能。
enum ALYVote : String {
case approve = "approve"
case clean = "clean"
case oppose = "oppose"
}
- 【强制】Swift 3.0新增了Decimal类型,使用的便捷性比之前桥接的NSDecimalNumber有质的飞越,需要使用高精度的场景,比如跟钱有关系的,一定要使用Decimal。
let foo : Decimal = 10.12373223423
let bar : Decimal = 1.23423432432432
print(foo+bar) //11.3579665585543176192
print(foo-bar) //11.3579665585543176192
print(foo*bar) //12.4950578137552000654026872358296354816
合理选择修饰符
-
【推荐】函数和类的声明采用最小够用使用原则,加上private、final、open、public、internal(默认)等修饰符。
a. 函数使用final修饰会走静态分发,性能更好。
b. private修饰则不向外暴露,编译器优化可做内联。final和private都可以减少Xcode自动提示的信息量,提高Xcode的反应速度,好处多多。
c. 对于动态库暴露的类,open表示可以被继承的接口,public表示不能被继承的接口。明确不需要被外部继承使用的,请使用public关键字。 -
【参考】关键的Swift代码,如果考虑未来需要打hotpatch,那么接口可以使用dynamic修饰,走Objective-C的动态派发。
使用尾随闭包
-
【强制】如果函数接受一个闭包作为参数,那么将闭包放在最后一个位置,方便用户采用最简方式调用。
-
【推荐】使用闭包的最简调用方式。
//最复杂版本
let fullGreetings = guestList.map({(person: String) -> String in return "Hello, \(person)!"})
//最简单版本
let fullGreetings = guestList.map{ "Hello, \($0)!" }
使用Swift的新方式
- 【强制】统一使用下面这种单例的编写方式,非常简洁,混编的时候也能被Objective-C识别。
class ALYXXX {
static let sharedInstance = ALYXXX()
private override init() {}
}
- 【推荐】多用lazy var声明属性,代码紧凑、好看、好维护。
lazy var textLabel : UILabel = {
let label = UILabel()
label.font = UIFont.aly_f10
label.textColor = UIColor.aly_ct_2
label.textAlignment = .center
label.text = "添加"
self.contentView.addSubview(label)
return label
}()
- 【推荐】foreach遍历数组非常简洁美观。map、filter能用的也尽量用起来吧。
self.groupList.forEach { (id, name) -> Void in
vc.groupList[id] = name
}
- 【推荐】defer可以简化异常处理逻辑,在作用域结束的时候,会执行defer代码块。
if exists(filename) {
let file = open(filename, O_READ)
defer close(file)
while let line = try file.readline() {
//
}
}
优秀的Swift开发资源
-
【推荐】尽量采用Swift开源库,减少混编的场景。
-
【推荐】Swift处理JSON不是一件容易的事情,推荐使用HandyJSON。我们从Swift 2.x一直用到3.0,非常稳定且好用。
-
【推荐】ObjectMapper是比较传统的JSON解析方式。如果场景比较简单,也是不错的选择。
-
【推荐】使用SnapKit写AutoLayout约束。
Swift与Objective-C混编
- 【强制】Swift不支持宏,所以要使用变量。
//#define NW_NETWOEK_STATUS_NOTIFY @"TBNetworkStatusChangeNotify"
extern NSString* const NW_NETWOEK_STATUS_NOTIFY;
- 【强制】Objective-C使用typedef enum定义的枚举类型,Swift不能使用,需要使用NS_ENUM或者NS_OPTIONS。
//typedef enum {
typedef NS_ENUM(NSUInteger, NetworkStatus) {
NotReachable = 0,
ReachableViaWiFi,
ReachableVia2G,
ReachableVia3G,
ReachableVia4G
};
//} NetworkStatus;
- 【强制】构造函数务必返回instanceType。如果返回id,Swift必须要转型才能使用。
//返回id,在Swift就必须要转型了。
//+ (id)sharedInstantce;
//(TBLoginCenter.sharedInstantce() as? LoginProtocol)
//使用instanceType符合规范
+ (instanceType)sharedInstantce;
- 【推荐】合理使用Nullability Annotations,让Swift更加理解Objective-C的语义。
- (__nullable id)itemWithName:(NSString * __nonnull)name;
//NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END将中间的代码都加上nonull,
//只需要对nullable的属性和参数单独声明就好了。
//iOS SDK惯用这种方法。可以多跳进去看看。
NS_ASSUME_NONNULL_BEGIN
@interface Foo : NSObject
@property (nonatomic, copy, nullable) NSString *bar1;
@property (nonatomic, copy) NSArray *bar2;
@end
NS_ASSUME_NONNULL_END
显著的坑
- open lazy var和WMOSwift 3.0上会冲突,出现编译报错。如果不需要被继承,使用public。如果需要被继承,不采用WMO或不使用open关键字。
//可能会出现编译问题
open lazy var promptTitleLabel : UILabel = {
let label = UILabel()
return label
}()
- weak delegate需要使用class关键字。否则会报如下的错误:'weak' cannot be applied to non-class type 'MyClassDelegate'。这是因为 Swift 的 protocol 是可以被除了 class 以外的其他类型遵守的,而对于像 struct 或是 enum 这样的类型,本身就不通过引用计数来管理内存,所以也不可能用 weak 这样的 ARC 的概念来进行修饰。
protocol MyClassDelegate: class {
func method()
}
class MyClass {
weak var delegate: MyClassDelegate?
}
class ViewController: UIViewController, MyClassDelegate {
// ...
var someInstance: MyClass!
override func viewDidLoad() {
super.viewDidLoad()
someInstance = MyClass()
someInstance.delegate = self
}
//...
}
-
用private修饰的类,如果使用KVC来给属性设置值,编译不会报错,运行时也不会报错,但就是设置不上。去掉private就好了。
-
Swift和OC混着写的时候,有时候会出现OC的类在CloudConsoleApp-Bridging-Header.h里面提供给Swift使用,但是这个类又需要引入CloudConsoleApp-Swift.h使用Swift的一些功能,这样就循环包含了,没法玩下去了。
-
Swift的二进制兼容做的尤其差,如果向外输出二进制库的话,增加、删除属性;新增、删除、调整接口顺序,都会导致二进制不兼容,需要更新主版本号。