总结了笔者平常使用 Swift 的一些小 Tips。html
把代码里的 var 全改为 let,只保留不能编译经过的。git
ObjC 的 Foundation 层几乎都是继承 NSObject
实现的,平时都在操做指针,因此要区分 Mutable 和 Imutable 的设计,好比 NSString
和 NSMutableString
。github
Swift 使用了 let 和 var 关键字直接用于区分是否可变。可变会更容易出错,因此尽可能采用不可变设计,等到须要改变才改成 var 吧。objective-c
!遇到 nil 时会 crash(包括 as!
进行强制转换)。可使用 if let
/guard let
/case let
配合 as?
将可选值消化掉。可能返回 nil 的 API,为何要本身骗本身呢?编程
当遇到 ObjC 代码暴露给 Swift 使用时,给接口 .h 文件加上 NS_ASSUME_NONNULL_BEGIN
和 NS_ASSUME_NONNULL_END
并检查接口参数是否能够为 nil 吧。swift
struct 是值类型,class 是引用类型。类类型分配在堆区,默认浅拷贝,容易被不经意间被改变,而值类型分配在栈区,默认深拷贝。而且 Swift 还有写时复制(copy on write)。网络
即便是使用 class 时,也仅在必要时(如桥接到 ObjC,使用 Runtime 一些特性)继承自 NSObject
。闭包
多使用 String
、Array
、Dictionary
、Int
、Bool
,少使用 Foundation 里面的 NSString
、NSArray
、NSDictionary
、NSNumber
。Cocoa Foundation 里面的都是类类型,而 Swift 标准库的是值类型,有不少标准库的方便方法。app
还有用 print
代替 NSLog
。框架
forEach
,map
,conpactMap
,flatMap
,zip
,reduce
是好帮手,代替一些使用变量并在循环中处理的例子吧。用上高阶函数,不只代码更清晰,还能将状态控制在更小的做用域内。
和 ObjC 基本都在函数的回调中返回 NSError
不同,Swift 函数可使用 throw
关键字抛出错误。
func test() throws {
//...
}
do {
try test()
} catch {
print(error)
}
// 若是对错误不敏感
try? test()
复制代码
Swift 里面的 String
的 index 和 count 不是一一对应的(兼容 Unicode),因此 stirng.count == 0
的效率不如 string.isEmpty
。
Swift 有着 framework 级别的命名空间,因此命名重复时能够经过 framework 名肯定,不用担忧重复命名问题。
尽可能只有在须要桥接给 ObjC 时,才使用 @objc(前缀 + 类名)
进行别名声明。
对应访问成员变量,方法时,都不用像 ObjC 那些写 self
了。
只在闭包内、函数实参和成员变量名字相同和方法形参须要自身时使用。闭包内 self
是强制的,而且能够提醒注意循环引用问题。函数调用时实参名和成员变量相同时,函数做用域内会优先使用函数实参,因此访问成员变量是须要 self
。
直接使用 ClassA()
,代替 ClassA.init()
,代码更简洁。
// no bad
let flag:Bool = false
// better
let flag = false
// not bad
view.contentMode = UIView.ContentMode.center
// better
view.contentMode = .center
复制代码
在设计接口时,再也不须要为每个形参是否须要而编写一个方法了,减小方法数吧。
// not bad
func test() {
//...
}
func test(param1:String) {
//...
}
func test(param2:String) {
//...
}
// better
func test(param1:String = "", param2:String = "") {
//...
}
复制代码
let _ = [0].removeLast()
[0].forEach{ _ in print("hh")}
复制代码
而对于本身设计的接口,若是返回值可有可无,只是附加功能的话,可使用 @discardableResult
进行标注。
好比系统有个 default
关键字,而你也但愿使用这个命名时就能派上用场。
let `default` = A()
func `default`() {}
复制代码
比起 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
}
复制代码
类型嵌套用于在类型里定义类型,让类型的命名空间的精细化程度更高。
struct GameService {
enum APIError {
enum ResultError {
case noResult
}
}
// use APIError
}
// use GameService.APIError
复制代码
有时候某一块逻辑只须要在方法内复用或者作逻辑分割,能够在方法内定义方法,这样访问域会更清晰。
func big(){
func small() {
//...
}
small()
}
复制代码
有时候初始化时一个对象时还须要赋值其中的一些属性,这个时候就可使用闭包代码块的整合。
let someView: UIView = {
let view = UIView(frame:.zero)
view.backgroundColor = .red
return view
}()
复制代码
和 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)
复制代码
定义一些常量时,用命名空间作隔离是最好的,Swift 的 Enum 比较适合用于命名空间的定义,能嵌套,且不存在初始化方法不会被用于其余做用。
enum Event {
enum Name {
static let login = "event.name.login"
}
}
// use
Event.Name.login
// not allow
Event()
复制代码
// not bad
var name:String?
if let aName = dic["name"] as? String {
name = aName
} else {
name = ""
}
// better
let name = dic["name"] as? String ?? ""
复制代码
除了常规的字符串插值,Swift5 还增长了更强大可自定义的字符串插值系统,详情见 文章。
let a = 2
print("\(a) is 2")
复制代码
Swift 在设计上,为协议作了不少强大的功能。Swift 标准库里大量的方法和类都使用了协议进行抽象。在编写代码时优先考虑使用协议进行逻辑的抽象,详情能够参考 Apple WWDC 2015 Session 408 - Protocol-Oriented Programming in Swift。
guard
是 if
的反义词,能够提早将异常状况 return。配合 guard let
使用,能够在正常分支下使用正确的条件。
guard let a = a as? String else { return }
// 下面的 a 就是 string 而且 non-nil 的了
复制代码
元组(Tuple)是个包含多个值的简单对象,使用元组,能够简单的用来函数返回多参数,也能够在集合类型中存取一对对的值。
typealias Pair<T> = (T, T)
let pair = Pair(1, 2)
复制代码
比起 ObjC 仅支持在集合类型里使用轻量级范型,Swift 的范型更强大,除了集合,还支持类、枚举、协议(Associate Type)。
protocol View {
associatedtype Model
func update(model:Model)
}
复制代码
比起 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
}
}
复制代码
和 ObjC 的 Categories 相似,拓展能够添加类的方法。Swift 的 Extension 还能拓展值类型,枚举的方法,且不须要新建文件编写和支持权限访问关键字。经过 Extension,还能给 Protocol 增长默认实现。也能在 Extension 中遵循协议,让方法划分更加清晰。
fileprivate extension Date {
var toString: String {
//...
}
}
Date().toString
复制代码
懒加载不须要像 ObjC 同样重写 getter 方法,并判空了,在属性前面加上 lazy
关键字就能够实现了。
lazy var view = UIView(frame:.zero)
复制代码
where
关键字能够对范围进行限定,详情见这篇 文章。
typealias
能够用来命名闭包类型、协议类型、范型类型,还支持组合。更多用法见 文章。
typealias NewName<D> = ClassA<D>&ProtocolA&ProtocolB
复制代码
Result
是一个枚举类型,包含成功或者失败的枚举值,并持有相应成功或者失败的值,经过范型肯定类型信息。由于成功和失败是互斥的,这样就能够避免多个可选参数返回。
好比对网络请求回调进行改造:
URLSession.shared.dataTask(with: request) { result in
switch result {
case .success(let (data, _)):
handle(data: data)
case .failure(let error):
handle(error: error)
}
}
复制代码
在 ObjC 中,开发者更习惯用相似 FBKVOController 等第三方库进行 KVO 的监听,那是由于原生的写法太难用了。Swift 为 KVO 增长了闭包的 API,更简洁好用。
scrollObserver = observe(\.scrollView!.contentOffset, options: [.new], changeHandler: { object, change in
//...
})
复制代码
同理,Swift 的 GCD API 也是专门通过 Swift 化的,也更加简洁好用。
在 Swift4 加入 Codable 协议后,JSON 等通用结构转模型,终于有了原生的支持。详情见 文章。
在 WWDC19 推出的 Swift Only 的库,SwiftUI 有着相似 React 的声明式 UI 开发框架,配合实时调试,在 Demo 和简单页面,跨 Apple 平台应用适配时有必定优点。而 Combine 时相似 RxSwift 的响应式编程框架,能使事件流更统一。
关于 SwiftUI 和 Combine 的介绍,能够参考 WWDC 2019 相关 Session。也能够参考笔者翻译的 文章 1,文章 2。
若有错误,欢迎交流&指出。