如何假装写过 Swift?
总结了笔者日常使用 Swift 的一些小 Tips。
Safe & Fast
1. 能用 let,尽量不用 var
把代码里的 var 全改成 let,只保留不能编译通过的。
ObjC 的 Foundation 层几乎都是继承 NSObject 实现的,平时都在操作指针,所以要区分 Mutable 和 Imutable 的设计,比如 NSString 和 NSMutableString 。
Swift 使用了 let 和 var 关键字直接用于区分是否可变。可变会更容易出错,所以尽量采用不可变设计,等到需要改变才改为 var 吧。
2. 尽量不用 !
!遇到 nil 时会 crash(包括 as! 进行强制转换)。可以使用 if let / guard let / case let配合 as? 将可选值消化掉。可能返回 nil 的 API,为什么要自己骗自己呢?
当遇到 ObjC 代码暴露给 Swift 使用时,给接口 .h 文件加上 NS_ASSUME_NONNULL_BEGIN 和 NS_ASSUME_NONNULL_END 并检查接口参数是否可以为 nil 吧。
3. 多定义 struct,少定义 class
struct 是值类型,class 是引用类型。类类型分配在堆区,默认浅拷贝,容易被不经意间被改变,而值类型分配在栈区,默认深拷贝。并且 Swift 还有写时复制(copy on write)。
即使是使用 class 时,也仅在必要时(如桥接到 ObjC,使用 Runtime 一些特性)继承自 NSObject 。
4. 能用 Swift 标准库类型,尽量不用对应的 Foundation 类型
多使用 String 、 Array 、 Dictionary 、 Int 、 Bool ,少使用 Foundation 里面的 NSString 、 NSArray 、 NSDictionary 、 NSNumber 。Cocoa Foundation 里面的都是类类型,而 Swift 标准库的是值类型,有很多标准库的方便方法。
还有用 print 代替 NSLog 。
5. 优先使用内置高阶函数
forEach , map , conpactMap , flatMap , zip , reduce 是好帮手,代替一些使用变量并在循环中处理的例子吧。用上高阶函数,不仅代码更清晰,还能将状态控制在更小的作用域内。
6. 使用 try catch 捕获错误
和 ObjC 基本都在函数的回调中返回 NSError 不一样,Swift 函数可以使用 throw 关键字抛出错误。
func test() throws {
//...
}
do {
try test()
} catch {
print(error)
}
// 如果对错误不敏感
try? test()
7. 对 String 判空时优先采用 isEmpty
Swift 里面的 String 的 index 和 count 不是一一对应的(兼容 Unicode),所以 stirng.count == 0 的效率不如 string.isEmpty 。
Clean Code
8. 文件名字去掉前缀
Swift 有着 framework 级别的命名空间,所以命名重复时可以通过 framework 名确定,不用担心重复命名问题。
尽量只有在需要桥接给 ObjC 时,才使用 @objc(前缀 + 类名) 进行别名声明。
9. 省略 self
对应访问成员变量,方法时,都不用像 ObjC 那些写 self 了。
只在闭包内、函数实参和成员变量名字相同和方法形参需要自身时使用。闭包内 self 是强制的,并且可以提醒注意循环引用问题。函数调用时实参名和成员变量相同时,函数作用域内会优先使用函数实参,所以访问成员变量是需要 self 。
10. 省略 init()
直接使用 ClassA() ,代替 ClassA.init() ,代码更简洁。
11. 能推导的类型不用显式编写
// no bad
let flag:Bool = false
// better
let flag = false
// not bad
view.contentMode = UIView.ContentMode.center
// better
view.contentMode = .center
12. 使用默认形参,简化接口设计
在设计接口时,不再需要为每一个形参是否需要而编写一个方法了,减少方法数吧。
// not bad
func test() {
//...
}
func test(param1:String) {
//...
}
func test(param2:String) {
//...
}
// better
func test(param1:String = "", param2:String = "") {
//...
}
13. 使用 _ 表示不使用的返回值
let _ = [0].removeLast()
[0].forEach{ _ in print("hh")}
而对于自己设计的接口,如果返回值无关紧要,只是附加功能的话,可以使用 @discardableResult 进行标注。
14. 使用 来定义和关键字重名的方法和属性
比如系统有个 default 关键字,而你也希望使用这个命名时就能派上用场。
let `default` = A()
func `default`() {}
15. Strong-Weak Dance 很简单
比起 ObjC 里需要每次需要写
__weak typeof(self) weak_self = self;
__typeof__(self) strong_self = weak_self;
尽管很多人会采用宏来简化,但重复的宏定义又会冲突,且 ObjC 没有访问权限关键字。
Swift 在闭包中可以使用 weak 和 unowned 指定闭包对值的捕获,配合 guard 就可以实现同样的功能。
test(){ [weak self] in
guard let self = self else { return }
// self is strong without retain cycle
}
16. 类型嵌套
类型嵌套用于在类型里定义类型,让类型的命名空间的精细化程度更高。
struct GameService {
enum APIError {
enum ResultError {
case noResult
}
}
// use APIError
}
// use GameService.APIError
17. func 嵌套
有时候某一块逻辑只需要在方法内复用或者做逻辑分割,可以在方法内定义方法,这样访问域会更清晰。
func big(){
func small() {
//...
}
small()
}
18. 使用闭包做初始化
有时候初始化时一个对象时还需要赋值其中的一些属性,这个时候就可以使用闭包代码块的整合。
let someView: UIView = {
let view = UIView(frame:.zero)
view.backgroundColor = .red
return view
}()
19. 使用更简洁的函数实参和形参
和 ObjC 不同,有形参实参的 Swift,可以在调用和编写的时候都有更合适简洁的表达。
// not bad
func updateWithView(view:UIView)
updateWithView(view:viewA)
// better
func updateWithView(_ view:UIView)
updateWith(view:viewA)
// not bad
func didSelectAtIndex(index:Int)
didSelectAtIndex(index:2)
// better
func didSelect(at index:Int)
didSelect(at:2)
20. Enum 用于命名空间声明
定义一些常量时,用命名空间做隔离是最好的,Swift 的 Enum 比较适合用于命名空间的定义,能嵌套,且不存在初始化方法不会被用于其他作用。
enum Event {
enum Name {
static let login = "event.name.login"
}
}
// use
Event.Name.login
// not allow
Event()
21. 使用 ?? 返回默认值
// not bad
var name:String?
if let aName = dic["name"] as? String {
name = aName
} else {
name = ""
}
// better
let name = dic["name"] as? String ?? ""
22. 使用字符串插值
除了常规的字符串插值,Swift5 还增加了更强大可自定义的字符串插值系统,详情见 [文章]()。
let a = 2
print("\(a) is 2")
Syntactic sugar
23. 更 POP(Protocol Oriented Programming,面向协议编程)
Swift 在设计上,为协议做了很多强大的功能。Swift 标准库里大量的方法和类都使用了协议进行抽象。在编写代码时优先考虑使用协议进行逻辑的抽象,详情可以参考 Apple WWDC 2015 Session 408 - Protocol-Oriented Programming in Swift 。
24. 优先使用 guard
guard 是 if 的反义词,可以提前将异常情况 return。配合 guard let 使用,可以在正常分支下使用正确的条件。
guard let a = a as? String else { return }
// 下面的 a 就是 string 并且 non-nil 的了
25. 尝试元组
元组(Tuple)是个包含多个值的简单对象,使用元组,可以简单的用来函数返回多参数,也可以在集合类型中存取一对对的值。
typealias Pair<T> = (T, T)
let pair = Pair(1, 2)
26. 尝试范型
比起 ObjC 仅支持在集合类型里使用轻量级范型,Swift 的范型更强大,除了集合,还支持类、枚举、协议(Associate Type)。
protocol View {
associatedtype Model
func update(model:Model)
}
27. 尝试枚举
比起 ObjC 那和 C 语言差不多的枚举,Swift 的枚举更强大。Swift 的枚举不一定需要 Int 作为枚举的原始值,可以不需要原始值,也可以使用 String 、 Float 、 Boolean 作为原始值。能在枚举值上关联值,实现很多有趣的功能(Rx,Promise 的状态机)。能给枚举编写函数,能给枚举增加 Extension。
enum State<Value> {
case pending
case fulfill(value:Value)
case reject(reason:Error)
mutating func update(to state:State){
guard case .pending = self else {
return
}
self = state
}
}
28. 尝试 Extension
和 ObjC 的 Categories 类似,拓展可以添加类的方法。Swift 的 Extension 还能拓展值类型,枚举的方法,且不需要新建文件编写和支持权限访问关键字。通过 Extension,还能给 Protocol 增加默认实现。也能在 Extension 中遵循协议,让方法划分更加清晰。
fileprivate extension Date {
var toString: String {
//...
}
}
Date().toString
29. lazy 关键字
懒加载不需要像 ObjC 一样重写 getter 方法,并判空了,在属性前面加上 lazy 关键字就可以实现了。
lazy var view = UIView(frame:.zero)
30. where 关键字
where 关键字可以对范围进行限定,详情见这篇 文章 。
31. typealias 关键字
typealias 可以用来命名闭包类型、协议类型、范型类型,还支持组合。更多用法见 文章 。
typealias NewName<D> = ClassA<D>&ProtocolA&ProtocolB
32. Result 类型
Result 是一个枚举类型,包含成功或者失败的枚举值,并持有相应成功或者失败的值,通过范型确定类型信息。因为成功和失败是互斥的,这样就可以避免多个可选参数返回。
比如对网络请求回调进行改造:
URLSession.shared.dataTask(with: request) { result in
switch result {
case .success(let (data, _)):
handle(data: data)
case .failure(let error):
handle(error: error)
}
}
33. KVO
在 ObjC 中,开发者更习惯用类似 FBKVOController 等第三方库进行 KVO 的监听,那是因为原生的写法太难用了。Swift 为 KVO 增加了闭包的 API,更简洁好用。
scrollObserver = observe(\.scrollView!.contentOffset, options: [.new], changeHandler: { object, change in
//...
})
同理,Swift 的 GCD API 也是专门经过 Swift 化的,也更加简洁好用。
资源搜索网站大全https://55wd.com 广州品牌设计公司http://www.maiqicn.com
34. Codable
在 Swift4 加入 Codable 协议后,jsON 等通用结构转模型,终于有了原生的支持。详情见 文章 。
35. SwiftUI&Combine
在 WWDC19 推出的 Swift Only 的库,SwiftUI 有着类似 react 的声明式 UI 开发框架,配合实时调试,在 Demo 和简单页面,跨 Apple 平台应用适配时有一定优势。而 Combine 时类似 RxSwift 的响应式编程框架,能使事件流更统一。
关于 SwiftUI 和 Combine 的介绍,可以参考 WWDC 2019 相关 Session。