相比于直接使用compactMap,你可以扩展集合类型将其封装起来
www.swiftwithvincent.com/tips/better…
import Foundation
extension Collection {
func nilValuesRemoved<Wrapped>() -> [Wrapped] where Element == Wrapped? {
self.compactMap { $0 }
}
}
let optionals = [1, 2, nil, 3, nil]
// for nonNilValue in optionals.compactMap { $0 }()
for nonNilValue in optionals.nilValuesRemoved() {
print(nonNilValue)
}
使用 private(set) 修饰懒加载属性,达到无法懒加载let的效果
www.swiftwithvincent.com/tips/lazy-p…
import UIKit
class ViewController: UIViewController {
// lazy let
private(set) lazy var subview: UIView = {
let view = UIView()
// configure `view`
return view
}()
}
多使用guard来守卫必须达成的条件
www.swiftwithvincent.com/tips/guard-…
guard let user = getUser() else {
throw UserError.noUser
}
guard let address = getAddress(of: user) else {
throw UserError.noAddress
}
guard let paymentMethod = getPaymentMethod(of: user) else {
throw UserError.noPaymentMethod
}
// do something
闭包通过捕获列表补货weak self后,在函数体里使用的self属性可以不写self.
www.swiftwithvincent.com/tips/concis…
publisher.sink { [weak self] newValue in
guard let self else { return }
doSomething(with: newValue)
}.store(in: &cancellables)
函数、闭包、计算属性等,单行表达式 省略return
www.swiftwithvincent.com/tips/omit-r…
// Functions
func helloWorld() -> String {
"Hello, world!"
}
// Closures
let helloWorld = {
"Hello, world!"
}
// Computed Properties
var helloWorld: String {
"Hello, world!"
}
使用 @available来标记一些过期API
www.swiftwithvincent.com/tips/ios-ve…
import UIKit
@available(iOS, deprecated: 14)
func warningOnceiOS13IsDropped(_ message: String) { }
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
warningOnceiOS13IsDropped("remove once we drop iOS 13")
/* some workaround for an iOS 13 bug */
}
}
将 body计算属性中使用的常量 扔到 ViewBuilder中
import SwiftUI
struct MyView: View {
var body: some View {
// let localConstant = 42
VStack {
// 从body计算属性中,移动到VStack的ViewBuilder中
let localConstant = 42
/* ... */
}
}
}
多考虑使用 LazySequence来减少计算量
www.swiftwithvincent.com/tips/lazyse…
import Foundation
(1...10_000)
.lazy
// 如果不使用lazy,将会执行完整10_000次
.map { $0 * $0 } // executed 15 times
.filter { $0.isMultiple(of: 5) } // executed 15 times
.first (where: { $0 > 100 })
多使用扩展 将重复逻辑封装
www.swiftwithvincent.com/tips/generi… www.swiftwithvincent.com/tips/or-emp… www.swiftwithvincent.com/tips/is-nil…
import Foundation
extension String? {
var orEmpty: String {
self ?? ""
}
}
// 和上面的写法一样,都是针对 可选String类型,即Optional<String>
extension Optional where Wrapped == String {
var orEmpty: String {
self ?? ""
}
var isNilOrEmpty: Bool {
self == nil || self == ""
}
}
let optionalString = Bool.random() ? "Hello, world!" : nil
print(optionalString.orEmpty)
func handles(optionalString: String?) {
if optionalString.isNilOrEmpty == false {
// use `optionalString`
}
}
对于字符串包含转义子串的,多考虑使用原始字符串。前后一个#号。对于多行字符传考虑使用"""
www.swiftwithvincent.com/tips/raw-st… www.swiftwithvincent.com/tips/multil…
import Foundation
let json = #"{"name":"Vincent"}"#
let multilineString = """
1st line
2nd line
3rd line
"""
写单元测试时,对于会抛出错误的case,可以将测试函数声明成throws可抛出错误的。然后case内使用try
www.swiftwithvincent.com/tips/xctest…
import XCTest
class Test: XCTestCase {
func test() throws {
XCTAssertEqual(try throwingFunction(), "Expected Result")
}
}
多条件判断考虑使用switch代替if
www.swiftwithvincent.com/tips/if-els…
switch (boolean1: boolean1, boolean2: boolean2, boolean3: boolean3) {
case (boolean1: true, _, boolean3: false):
functionA()
case (_, boolean2: false, boolean3: true):
functionB()
case (boolean1: true, boolean2: false, boolean3: false):
functionC()
default:
break
}
接受多个参数时,考虑使用可变参数而不是Array
www.swiftwithvincent.com/tips/variad…
import UIKit
extension UIView {
// func add(subviews: [UIView])
func add(subviews: UIView...) {
for subview in subviews {
self.addSubview(subview)
}
}
}
view.add(subviews: firstView, secondView, thirdView)
对于枚举,最好实现CaseIterable,编译器会帮助生成allCases
www.swiftwithvincent.com/tips/caseit…
import Foundation
enum Direction: CaseIterable {
case north
case south
case east
case west
}
Direction.allCases // [.north, .south, .east, .west]
多使用协议编程思想。有泛型参数时多考虑使用some关键字
www.swiftwithvincent.com/tips/opaque… www.swiftwithvincent.com/tips/array-…
import Foundation
// func handle<T: Identifiable>:(value: T)
// func handle<T>:(value: T) where T: Identifiable
func handle(value: some Identifiable) {
}
struct User {
let firstName: String
}
// func display(users: Array<User>)
func display(users: some Collection<User>) {
for user in users {
print(user)
}
}
多使用is判断类型,而不是每次尝试使用as?做类型转换
www.swiftwithvincent.com/tips/as-vs-…
let mystery: Any = Bool.random() ? 1 : "One"
// if let _ = mystery as? Int
if mystery is Int {
print("It's an Int!")
}
多使用函数式简洁的filter,少使用命令式复杂的 for loop
www.swiftwithvincent.com/tips/for-lo…
import Foundation
let integers = [1, 2, 3, 4, 5]
// for integer in integers where integer.isMultiple(of: 2)
let evensOnly = integers.filter { $0.isMultiple(of: 2) }
使用闭包时,多考虑使用捕获列表捕获直接使用的变量,不要捕获weak self后再通过self去引用
www.swiftwithvincent.com/tips/closur…
class ViewController: UIViewController {
let service = Service()
let formatter = Formatter()
let label = UILabel()
var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
label.text = "Retrieved data: "
// service.call().sink { [weak self] data in guard let self else {return} }
service.call().sink { [formatter, label] data in
let formatted = formatter.format(data: data)
label.text?.append(formatted)
}.store(in: &cancellables)
}
}
对于有两个返回值的,优先考虑使用Result而不是Optional
www.swiftwithvincent.com/tips/option…
import Foundation
// func fetchData(_ completion: @escaping (Data?, Error?) -> Void)
func fetchData(_ completion: @escaping (Result<Data, Error>) -> Void) {
/* ... */
}
fetchData { result in
switch result {
case .success(let data):
// use the data
case .failure(let error):
// handle the error
}
}
多使用有可变参数的函数,而不是自己拼凑完整参数
www.swiftwithvincent.com/tips/variad…
import Foundation
var person = "Vincent"
var nationality = "French 🇫🇷"
// print("\(person) is \(nationality)")
print(person, " is ", nationality)
对于大数,书写的时候多使用千分符提高可读性
www.swiftwithvincent.com/tips/large-…
let bigNumber = 123_456_789
KeyPath也可以用于高阶函数的函数参数
www.swiftwithvincent.com/tips/keypat…
import Foundation
extension Int {
var isEven: Bool { self.isMultiple(of: 2) }
}
let numbers = [1, 2, 3, 4, 5]
// let evens = numbers.filter{ $0.isHeven}
let evens = numbers.filter(\.isEven)
多使用Swift5.7的简短if let绑定
www.swiftwithvincent.com/tips/shorth…
var userName: String?
// if let userName = userName
if let userName {
// `userName` is non-optional here
}
在for loop循环中多使用where语句,少在执行体中用if判断
www.swiftwithvincent.com/tips/where-…
import Foundation
let numbers = [1, 2, 3, 4, 5]
for number in numbers where number.isMultiple(of: 2) {
print(number)
}
对于打印对象,多考虑使用dump而不是print
www.swiftwithvincent.com/tips/dump-v…
class Person {
init(name: String, age: Int) {
self.name = name
self.age = age
}
var name: String
var age: Int
}
let me = Person(name: "Vincent", age: 31)
dump(me)
多个if嵌套可以考虑改成if多条件判断
www.swiftwithvincent.com/tips/combin…
func displayIfConditions(value: Int) {
if value.isMultiple(of: 2),
value.isMultiple(of: 3),
value.isMultiple(of: 5),
value.isMultiple(of: 7) {
print(value)
}
}
多使用纯净的Swift API,少使用OC风格的API
www.swiftwithvincent.com/tips/modern…
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
@IBOutlet weak var button: UIButton!
var counter = 0 {
didSet {
label.text = "Counter: \(counter)"
}
}
override func viewDidLoad() {
super.viewDidLoad()
// 不要使用addAction、#selector、@objc等OC风格的API
button.addAction(UIAction { [weak self] _ in
self?.counter += 1
}, for: .touchUpInside)
}
}
在单元测试中建议使用XCTUnwrap解包可选值,而不是自己进行强制解包
www.swiftwithvincent.com/tips/xctunw…
import XCTest
class MyTests: XCTestCase {
func test() throws {
let myData = [1, 2, 3]
let first = try XCTUnwrap(myData.first)
XCTAssert(first<3)
}
}