语言规约
命名规范
【强制】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
这些模块,调用同名的扩展方法会报下面这个错误。
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")
避免内存泄露
- 【强制】闭包使用
wea
k或者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
(默认)等修饰符。
- 函数使用
final
修饰会走静态分发,性能更好。private
修饰则不向外暴露,编译器优化可做内联。final和private
都可以减少Xcode自动提示的信息量,提高Xcode的反应速度,好处多多。- 对于动态库暴露的类,
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的二进制兼容做的尤其差,如果向外输出二进制库的话,增加、删除属性;新增、删除、调整接口顺序,都会导致二进制不兼容,需要更新主版本号。