遵循本风格指南应该:
请注意,简洁不是主要目标。只有当其他良好的代码质量(如可读性、简洁性和清晰性)保持不变或得到改进时,才应使代码更简洁。
这个 repo 包含一个 Swift Package Manager 命令插件,您可以使用它根据风格指南自动重新格式化或检查您的包。要将此命令插件与您的包一起使用,您只需将此 repo 添加为依赖项
dependencies: [
.package(url: "https://github.com/airbnb/swift", from: "1.0.0"),
]
然后在您的包目录中运行 format
命令插件
$ swift package format
# Supported in Xcode 14+. Prompts for permission to write to the package directory.
$ swift package format
# When using the Xcode 13 toolchain, or a noninteractive shell, you must use:
$ swift package --allow-writing-to-package-directory format
# To just lint without reformatting, you can use `--lint`:
$ swift package format --lint
# By default the command plugin runs on the entire package directory.
# You can exclude directories using `exclude`:
$ swift package format --exclude Tests
# Alternatively you can explicitly list the set of paths and/or SPM targets:
$ swift package format --paths Sources Tests Package.swift
$ swift package format --targets AirbnbSwiftFormatTool
# The plugin infers your package's minimum Swift version from the `swift-tools-version`
# in your `Package.swift`, but you can provide a custom value with `--swift-version`:
$ swift package format --swift-version 5.3
如果存在需要注意的 lint 失败,包插件将返回一个非零退出代码。
--lint
模式下,来自任何工具的任何 lint 失败都会导致非零退出代码。--lint
的标准自动更正模式下,只有来自 SwiftLint 仅 lint 规则的失败才会导致非零退出代码。您可以通过运行 此脚本 在 Xcode 中启用以下设置,例如,作为“运行脚本”构建阶段的一部分。
(链接) 类型和协议名称使用 PascalCase,其他所有内容使用 lowerCamelCase。
protocol SpaceThing {
// ...
}
class SpaceFleet: SpaceThing {
enum Formation {
// ...
}
class Spaceship {
// ...
}
var ships: [Spaceship] = []
static let worldName: String = "Earth"
func addShip(_ ship: Spaceship) {
// ...
}
}
let myFleet = SpaceFleet()
例外:如果私有属性支持具有更高访问级别的同名属性或方法,您可以以一个下划线作为私有属性的前缀。
在某些特定情况下,以下划线作为前缀的支持属性或方法可能比使用更具描述性的名称更容易阅读。
public final class AnyRequester<ModelType>: Requester {
public init<T: Requester>(_ requester: T) where T.ModelType == ModelType {
_executeRequest = requester.executeRequest
}
@discardableResult
public func executeRequest(
_ request: URLRequest,
onSuccess: @escaping (ModelType, Bool) -> Void,
onFailure: @escaping (Error) -> Void)
-> URLSessionCancellable
{
return _executeRequest(request, onSuccess, onFailure)
}
private let _executeRequest: (
URLRequest,
@escaping (ModelType, Bool) -> Void,
@escaping (Error) -> Void)
-> URLSessionCancellable
}
final class ExperiencesViewController: UIViewController {
// We can't name this view since UIViewController has a view: UIView property.
private lazy var _view = CustomView()
loadView() {
self.view = _view
}
}
(链接) 像 isSpaceship
, hasSpacesuit
等一样命名布尔值。 这可以清楚地表明它们是布尔值而不是其他类型。
(链接) 名称中的首字母缩略词(例如 URL
)应全部大写,除非它是以 lowerCamelCase 开头的名称,在这种情况下应统一小写。
// WRONG
class UrlValidator {
func isValidUrl(_ URL: URL) -> Bool {
// ...
}
func isProfileUrl(_ URL: URL, for userId: String) -> Bool {
// ...
}
}
let URLValidator = UrlValidator()
let isProfile = URLValidator.isProfileUrl(URLToTest, userId: IDOfUser)
// RIGHT
class URLValidator {
func isValidURL(_ url: URL) -> Bool {
// ...
}
func isProfileURL(_ url: URL, for userID: String) -> Bool {
// ...
}
}
let urlValidator = URLValidator()
let isProfile = urlValidator.isProfileURL(urlToTest, userID: idOfUser)
(链接) 名称应先写最通用的部分,最后写最具体的部分。 “最通用”的含义取决于上下文,但应该大致意味着“最能帮助您缩小搜索范围以找到您要查找的项目”。最重要的是,要与您订购名称各部分的方式保持一致。
// WRONG
let rightTitleMargin: CGFloat
let leftTitleMargin: CGFloat
let bodyRightMargin: CGFloat
let bodyLeftMargin: CGFloat
// RIGHT
let titleMarginRight: CGFloat
let titleMarginLeft: CGFloat
let bodyMarginRight: CGFloat
let bodyMarginLeft: CGFloat
(链接) 如果在其他情况下会产生歧义,请在名称中包含关于类型的提示。
// WRONG
let title: String
let cancel: UIButton
// RIGHT
let titleText: String
let cancelButton: UIButton
(链接) 事件处理函数应该像过去时态的句子一样命名。 如果为了清楚起见不需要,则可以省略主语。
// WRONG
class ExperiencesViewController {
private func handleBookButtonTap() {
// ...
}
private func modelChanged() {
// ...
}
}
// RIGHT
class ExperiencesViewController {
private func didTapBookButton() {
// ...
}
private func modelDidChange() {
// ...
}
}
(链接) 避免 Objective-C 风格的首字母缩略词前缀。 在 Swift 中,不再需要这样做来避免命名冲突。
// WRONG
class AIRAccount {
// ...
}
// RIGHT
class Account {
// ...
}
(链接) 避免在非视图控制器的类名中使用 *Controller
。
(链接) 在可以轻松推断类型的地方,不要包含类型。
// WRONG
let sun: Star = Star(mass: 1.989e30)
let earth: Planet = Planet.earth
// RIGHT
let sun = Star(mass: 1.989e30)
let earth = Planet.earth
// NOT RECOMMENDED. However, since the linter doesn't have full type information, this is not enforced automatically.
let moon: Moon = earth.moon // returns `Moon`
// RIGHT
let moon = earth.moon
let moon: PlanetaryBody? = earth.moon
// WRONG: Most literals provide a default type that can be inferred.
let enableGravity: Bool = true
let numberOfPlanets: Int = 8
let sunMass: Double = 1.989e30
// RIGHT
let enableGravity = true
let numberOfPlanets = 8
let sunMass = 1.989e30
// WRONG: Types can be inferred from if/switch expressions as well if each branch has the same explicit type.
let smallestPlanet: Planet =
if treatPlutoAsPlanet {
Planet.pluto
} else {
Planet.mercury
}
// RIGHT
let smallestPlanet =
if treatPlutoAsPlanet {
Planet.pluto
} else {
Planet.mercury
}
(链接) 首选让变量或属性的类型从右侧的值推断出来,而不是在左侧显式编写类型。
当右侧值是带有前导点的静态成员(例如 init
、static
属性/函数或枚举案例)时,首选使用推断类型。 这适用于局部变量和属性声明
// WRONG
struct SolarSystemBuilder {
let sun: Star = .init(mass: 1.989e30)
let earth: Planet = .earth
func setUp() {
let galaxy: Galaxy = .andromeda
let system: SolarSystem = .init(sun, earth)
galaxy.add(system)
}
}
// RIGHT
struct SolarSystemBuilder {
let sun = Star(mass: 1.989e30)
let earth = Planet.earth
func setUp() {
let galaxy = Galaxy.andromeda
let system = SolarSystem(sun, earth)
galaxy.add(system)
}
}
在其他情况下仍然允许使用显式类型
// RIGHT: There is no right-hand-side value, so an explicit type is required.
let sun: Star
// RIGHT: The right-hand-side is not a static member of the left-hand type.
let moon: PlantaryBody = earth.moon
let sunMass: Float = 1.989e30
let planets: [Planet] = []
let venusMoon: Moon? = nil
在极少数情况下,推断类型语法与显式类型语法具有不同的含义。 在这些情况下,仍然允许使用显式类型语法
extension String {
static let earth = "Earth"
}
// WRONG: fails with "error: type 'String?' has no member 'earth'"
let planetName = String?.earth
// RIGHT
let planetName: String? = .earth
struct SaturnOutline: ShapeStyle { ... }
extension ShapeStyle where Self == SaturnOutline {
static var saturnOutline: SaturnOutline {
SaturnOutline()
}
}
// WRONG: fails with "error: static member 'saturnOutline' cannot be used on protocol metatype '(any ShapeStyle).Type'"
let myShape2 = (any ShapeStyle).myShape
// RIGHT: If the property's type is an existential / protocol type, moving the type
// to the right-hand side will result in invalid code if the value is defined in an
// extension like `extension ShapeStyle where Self == SaturnOutline`.
// SwiftFormat autocorrect detects this case by checking for the existential `any` keyword.
let myShape1: any ShapeStyle = .saturnOutline
(链接) 除非为了消除歧义或语言要求,否则不要使用 self
。
final class Listing {
init(capacity: Int, allowsPets: Bool) {
// WRONG
self.capacity = capacity
self.isFamilyFriendly = !allowsPets // `self.` not required here
// RIGHT
self.capacity = capacity
isFamilyFriendly = !allowsPets
}
private let isFamilyFriendly: Bool
private var capacity: Int
private func increaseCapacity(by amount: Int) {
// WRONG
self.capacity += amount
// RIGHT
capacity += amount
// WRONG
self.save()
// RIGHT
save()
}
}
(链接) 从弱引用升级时绑定到 self
。
// WRONG
class MyClass {
func request(completion: () -> Void) {
API.request() { [weak self] response in
guard let strongSelf = self else { return }
// Do work
completion()
}
}
}
// RIGHT
class MyClass {
func request(completion: () -> Void) {
API.request() { [weak self] response in
guard let self else { return }
// Do work
completion()
}
}
}
(链接) 在多行数组的最后一个元素上添加一个尾随逗号。
// WRONG
let rowContent = [
listingUrgencyDatesRowContent(),
listingUrgencyBookedRowContent(),
listingUrgencyBookedShortRowContent()
]
// RIGHT
let rowContent = [
listingUrgencyDatesRowContent(),
listingUrgencyBookedRowContent(),
listingUrgencyBookedShortRowContent(),
]
(链接) 集合字面量括号内不应有空格。
// WRONG
let innerPlanets = [ mercury, venus, earth, mars ]
let largestObjects = [ .star: sun, .planet: jupiter ]
// RIGHT
let innerPlanets = [mercury, venus, earth, mars]
let largestObjects = [.star: sun, .planet: jupiter]
(链接) 命名元组的成员以获得额外的清晰度。 经验法则:如果您有超过 3 个字段,您可能应该使用一个结构体。
// WRONG
func whatever() -> (Int, Int) {
return (4, 4)
}
let thing = whatever()
print(thing.0)
// RIGHT
func whatever() -> (x: Int, y: Int) {
return (x: 4, y: 4)
}
// THIS IS ALSO OKAY
func whatever2() -> (x: Int, y: Int) {
let x = 4
let y = 4
return (x, y)
}
let coord = whatever()
coord.x
coord.y
(链接) 冒号后面应该始终跟着一个空格,但不应该在前面跟着一个空格。
// WRONG
let planet:CelestialObject = sun.planets[0]
let planet : CelestialObject = sun.planets[0]
// RIGHT
let planet: CelestialObject = sun.planets[0]
// WRONG
class Planet : CelestialObject {
// ...
}
// RIGHT
class Planet: CelestialObject {
// ...
}
// WRONG
let moons: [Planet : Moon] = [
mercury : [],
venus : [],
earth : [theMoon],
mars : [phobos,deimos],
]
// RIGHT
let moons: [Planet: Moon] = [
mercury: [],
venus: [],
earth: [theMoon],
mars: [phobos,deimos],
]
(链接) 为了可读性,在返回箭头的两侧放置一个空格。
// WRONG
func doSomething()->String {
// ...
}
// RIGHT
func doSomething() -> String {
// ...
}
// WRONG
func doSomething(completion: ()->Void) {
// ...
}
// RIGHT
func doSomething(completion: () -> Void) {
// ...
}
(链接) 省略不必要的括号。
// WRONG
if (userCount > 0) { ... }
switch (someValue) { ... }
let evens = userCounts.filter { (number) in number.isMultiple(of: 2) }
let squares = userCounts.map() { $0 * $0 }
// RIGHT
if userCount > 0 { ... }
switch someValue { ... }
let evens = userCounts.filter { number in number.isMultiple(of: 2) }
let squares = userCounts.map { $0 * $0 }
(链接) 当所有参数都没有标签时,从 case 语句中省略枚举关联的值。
// WRONG
if case .done(_) = result { ... }
switch animal {
case .dog(_, _, _):
...
}
// RIGHT
if case .done = result { ... }
switch animal {
case .dog:
...
}
(链接) 当解构枚举的 case 或元组时,将 let
关键字内联,紧邻每个属性赋值。
// WRONG
switch result {
case let .success(value):
// ...
case let .error(errorCode, errorReason):
// ...
}
// WRONG
guard let case .success(value) else {
return
}
// RIGHT
switch result {
case .success(let value):
// ...
case .error(let errorCode, let errorReason):
// ...
}
// RIGHT
guard case .success(let value) else {
return
}
一致性:我们应该倾向于始终内联 let
关键字,或者从不内联 let
关键字。在 Airbnb 的 Swift 代码库中,我们观察到,内联 let
在实践中使用得更为频繁(尤其是在解构具有单个关联值的枚举 case 时)。
清晰性:内联 let
关键字可以更清楚地表明哪些标识符是条件检查的一部分,哪些标识符正在绑定新变量,因为 let
关键字始终与变量标识符相邻。
// `let` is adjacent to the variable identifier, so it is immediately obvious
// at a glance that these identifiers represent new variable bindings
case .enumCaseWithSingleAssociatedValue(let string):
case .enumCaseWithMultipleAssociatedValues(let string, let int):
// The `let` keyword is quite far from the variable identifiers,
// so it is less obvious that they represent new variable bindings
case let .enumCaseWithSingleAssociatedValue(string):
case let .enumCaseWithMultipleAssociatedValues(string, int):
(链接) 将函数、类型和计算属性的属性放在声明行的上一行。
// WRONG
@objc class Spaceship {
@ViewBuilder var controlPanel: some View {
// ...
}
@discardableResult func fly() -> Bool {
// ...
}
}
// RIGHT
@objc
class Spaceship {
@ViewBuilder
var controlPanel: some View {
// ...
}
@discardableResult
func fly() -> Bool {
// ...
}
}
(链接) 将存储属性的简单属性与声明的其他部分放在同一行。 具有命名参数或多个未命名参数的复杂属性应放在上一行。
// WRONG. These simple property wrappers should be written on the same line as the declaration.
struct SpaceshipDashboardView {
@State
private var warpDriveEnabled: Bool
@ObservedObject
private var lifeSupportService: LifeSupportService
@Environment(\.controlPanelStyle)
private var controlPanelStyle
}
// RIGHT
struct SpaceshipDashboardView {
@State private var warpDriveEnabled: Bool
@ObservedObject private var lifeSupportService: LifeSupportService
@Environment(\.controlPanelStyle) private var controlPanelStyle
}
// WRONG. These complex attached macros should be written on the previous line.
struct SolarSystemView {
@Query(sort: \.distance) var allPlanets: [Planet]
@Query(sort: \.age, order: .reverse) var moonsByAge: [Moon]
}
// RIGHT
struct SolarSystemView {
@Query(sort: \.distance)
var allPlanets: [Planet]
@Query(sort: \.age, order: .reverse)
var oldestMoons: [Moon]
}
// WRONG. These long, complex attributes should be written on the previous line.
struct RocketFactory {
@available(*, unavailable, message: "No longer in production") var saturn5Builder: Saturn5Builder
@available(*, deprecated, message: "To be retired by 2030") var atlas5Builder: Atlas5Builder
@available(*, iOS 18.0, tvOS 18.0, macOS 15.0, watchOS 11.0) var newGlennBuilder: NewGlennBuilder
}
// RIGHT
struct RocketFactory {
@available(*, unavailable, message: "No longer in production")
var saturn5Builder: Saturn5Builder
@available(*, deprecated, message: "To be retired by 2030")
var atlas5Builder: Atlas5Builder
@available(*, iOS 18.0, tvOS 18.0, macOS 15.0, watchOS 11.0)
var newGlennBuilder: NewGlennBuilder
}
与其他类型的声明(具有大括号并跨越多行)不同,存储属性声明通常只是一行代码。存储属性通常是顺序编写的,它们之间没有任何空行。这使得代码紧凑,而不会损害可读性,并允许将相关的属性分组在一起。
struct SpaceshipDashboardView {
@State private var warpDriveEnabled: Bool
@State private var lifeSupportEnabled: Bool
@State private var artificialGravityEnabled: Bool
@State private var tractorBeamEnabled: Bool
@Environment(\.controlPanelStyle) private var controlPanelStyle
@Environment(\.toggleButtonStyle) private var toggleButtonStyle
}
如果存储属性的属性写在上一行(像其他类型的属性一样),那么属性开始在视觉上混在一起,除非你在它们之间添加空行
struct SpaceshipDashboardView {
@State
private var warpDriveEnabled: Bool
@State
private var lifeSupportEnabled: Bool
@State
private var artificialGravityEnabled: Bool
@State
private var tractorBeamEnabled: Bool
@Environment(\.controlPanelStyle)
private var controlPanelStyle
@Environment(\.toggleButtonStyle)
private var toggleButtonStyle
}
如果你添加空行,属性列表会变得更长,你将失去将相关属性分组在一起的能力
struct SpaceshipDashboardView {
@State
private var warpDriveEnabled: Bool
@State
private var lifeSupportEnabled: Bool
@State
private var artificialGravityEnabled: Bool
@State
private var tractorBeamEnabled: Bool
@Environment(\.controlPanelStyle)
private var controlPanelStyle
@Environment(\.toggleButtonStyle)
private var toggleButtonStyle
}
这不适用于具有命名参数或多个未命名参数的复杂属性。这些参数在视觉上很复杂,并且通常编码了大量信息,因此写在一行时会感到局促且难以阅读
// Despite being less than 100 characters long, these lines are very complex and feel unnecessarily long:
@available(*, unavailable, message: "No longer in production") var saturn5Builder: Saturn5Builder
@available(*, deprecated, message: "To be retired by 2030") var atlas5Builder: Atlas5Builder
@available(*, iOS 18.0, tvOS 18.0, macOS 15.0, watchOS 11.0) var newGlennBuilder: NewGlennBuilder
(链接) 多行数组的每个括号应该在单独的一行。 将开始和结束括号与数组的任何元素放在不同的行上。 还要在最后一个元素上添加一个尾随逗号。
// WRONG
let rowContent = [listingUrgencyDatesRowContent(),
listingUrgencyBookedRowContent(),
listingUrgencyBookedShortRowContent()]
// WRONG
let rowContent = [
listingUrgencyDatesRowContent(),
listingUrgencyBookedRowContent(),
listingUrgencyBookedShortRowContent()
]
// RIGHT
let rowContent = [
listingUrgencyDatesRowContent(),
listingUrgencyBookedRowContent(),
listingUrgencyBookedShortRowContent(),
]
(链接) 长协议组合的类型别名应在 =
之前和每个单独的 &
之前换行。
// WRONG (too long)
public typealias Dependencies = CivilizationServiceProviding & LawsOfPhysicsProviding & PlanetBuilderProviding & UniverseBuilderProviding & UniverseSimulatorServiceProviding
// WRONG (naive wrapping)
public typealias Dependencies = CivilizationServiceProviding & LawsOfPhysicsProviding & PlanetBuilderProviding &
UniverseBuilderProviding & UniverseSimulatorServiceProviding
// WRONG (unbalanced)
public typealias Dependencies = CivilizationServiceProviding
& LawsOfPhysicsProviding
& PlanetBuilderProviding
& UniverseBuilderProviding
& UniverseSimulatorServiceProviding
// RIGHT
public typealias Dependencies
= CivilizationServiceProviding
& LawsOfPhysicsProviding
& PlanetBuilderProviding
& UniverseBuilderProviding
& UniverseSimulatorServiceProviding
(链接) 按字母顺序排序协议组合类型别名。
协议组合类型别名是一个无序列表,没有自然的排序。 按字母顺序排序使这些列表更有条理,这对于长的协议组合尤其有价值。
// WRONG (not sorted)
public typealias Dependencies
= UniverseBuilderProviding
& LawsOfPhysicsProviding
& UniverseSimulatorServiceProviding
& PlanetBuilderProviding
& CivilizationServiceProviding
// RIGHT
public typealias Dependencies
= CivilizationServiceProviding
& LawsOfPhysicsProviding
& PlanetBuilderProviding
& UniverseBuilderProviding
& UniverseSimulatorServiceProviding
(链接) 当将可选属性解包为具有相同名称的非可选属性时,省略表达式的右侧。
按照 SE-0345 中的理由,这种简写语法消除了不必要的样板代码,同时保持了清晰度。
// WRONG
if
let galaxy = galaxy,
galaxy.name == "Milky Way"
{ … }
guard
let galaxy = galaxy,
galaxy.name == "Milky Way"
else { … }
// RIGHT
if
let galaxy,
galaxy.name == "Milky Way"
{ … }
guard
let galaxy,
galaxy.name == "Milky Way"
else { … }
(链接) Else 语句应该与前一个条件的结束大括号在同一行开始,除非条件被空行或注释分隔。
// WRONG
if let galaxy {
…
}
else if let bigBangService {
…
}
else {
…
}
// RIGHT
if let galaxy {
…
} else if let bigBangService {
…
} else {
…
}
// RIGHT, because there are comments between the conditions
if let galaxy {
…
}
// If the galaxy hasn't been created yet, create it using the big bang service
else if let bigBangService {
…
}
// If the big bang service doesn't exist, fail gracefully
else {
…
}
// RIGHT, because there are blank lines between the conditions
if let galaxy {
…
}
else if let bigBangService {
// If the galaxy hasn't been created yet, create it using the big bang service
…
}
else {
// If the big bang service doesn't exist, fail gracefully
…
}
(链接) 多行条件语句应该在引导关键字后断开。 将每个单独的语句缩进 2 个空格。
在引导关键字后断开会将缩进重置为标准2空格网格,这有助于避免与 Xcode 的 ^ + I 缩进行为作斗争。
// WRONG
if let galaxy,
galaxy.name == "Milky Way" // Indenting by two spaces fights Xcode's ^+I indentation behavior
{ … }
// WRONG
guard let galaxy,
galaxy.name == "Milky Way" // Variable width indentation (6 spaces)
else { … }
// WRONG
guard let earth = universe.find(
.planet,
named: "Earth"),
earth.isHabitable // Blends in with previous condition's method arguments
else { … }
// RIGHT
if
let galaxy,
galaxy.name == "Milky Way"
{ … }
// RIGHT
guard
let galaxy,
galaxy.name == "Milky Way"
else { … }
// RIGHT
guard
let earth = universe.find(
.planet,
named: "Earth"),
earth.isHabitable
else { … }
// RIGHT
if let galaxy {
…
}
// RIGHT
guard let galaxy else {
…
}
(链接) 在多行 if
或 switch
表达式之前,在赋值运算符 (=
) 之后添加换行符,并缩进后面的 if
/ switch
表达式。 如果声明适合单行,则不需要换行符。
这使得 if
和 switch
表达式总是具有与标准 if
和 switch
语句相同的“形状”,其中
if
/ switch
关键字始终是专用代码行中最左侧的标记。if
/ switch
关键字的右侧和下方。这与 if
/ switch
关键字用于控制流的方式最为一致,因此更容易一目了然地识别代码正在使用 if
或 switch
表达式。
// WRONG. Should have a line break after the first `=`.
let planetLocation = if let star = planet.star {
"The \(star.name) system"
} else {
"Rogue planet"
}
// WRONG. The first `=` should be on the line of the variable being assigned.
let planetLocation
= if let star = planet.star {
"The \(star.name) system"
} else {
"Rogue planet"
}
// WRONG. `switch` expression should be indented.
let planetLocation =
switch planet {
case .mercury, .venus, .earth, .mars:
.terrestrial
case .jupiter, .saturn, .uranus, .neptune:
.gasGiant
}
// RIGHT
let planetLocation =
if let star = planet.star {
"The \(star.name) system"
} else {
"Rogue planet"
}
// RIGHT
let planetType: PlanetType =
switch planet {
case .mercury, .venus, .earth, .mars:
.terrestrial
case .jupiter, .saturn, .uranus, .neptune:
.gasGiant
}
// ALSO RIGHT. A line break is not required because the declaration fits on a single line.
let moonName = if let moon = planet.moon { moon.name } else { "none" }
// ALSO RIGHT. A line break is permitted if it helps with readability.
let moonName =
if let moon = planet.moon { moon.name } else { "none" }
(链接) 当用条件语句(例如 if
或 switch
语句)的结果初始化新属性时,尽可能使用单个 if
/switch
表达式,而不是定义一个未初始化的属性,并在以下条件语句的每个分支上初始化它。
与简单地在以下条件语句的每个分支上执行赋值相比,使用 if
/switch
表达式有几个好处
var
的情况。// BEFORE
// 1. An explicit type annotation is required for the uninitialized property.
// 2. `var` is unnecessary here because `planetLocation` is never modified after being initialized, but the compiler doesn't diagnose.
// 3. The `planetLocation` property name is written on each branch so is redundant and visually noisy.
var planetLocation: String
if let star = planet.star {
planetLocation = "The \(star.name) system"
} else {
planetLocation = "Rogue planet"
}
print(planetLocation)
// AFTER
// 1. No need to write an explicit `: String` type annotation.
// 2. The compiler correctly diagnoses that the `var` is unnecessary and emits a warning suggesting to use `let` instead.
// 3. Each conditional branch is simply the value being assigned.
var planetLocation =
if let star = planet.star {
"The \(star.name) system"
} else {
"Rogue planet"
}
print(planetLocation)
// WRONG
let planetLocation: String
if let star = planet.star {
planetLocation = "The \(star.name) system"
} else {
planetLocation = "Rogue planet"
}
let planetType: PlanetType
switch planet {
case .mercury, .venus, .earth, .mars:
planetType = .terrestrial
case .jupiter, .saturn, .uranus, .neptune:
planetType = .gasGiant
}
let canBeTerraformed: Bool
if
let star = planet.star,
!planet.isHabitable,
planet.isInHabitableZone(of: star)
{
canBeTerraformed = true
} else {
canBeTerraformed = false
}
// RIGHT
let planetLocation =
if let star = planet.star {
"The \(star.name) system"
} else {
"Rogue planet"
}
let planetType: PlanetType =
switch planet {
case .mercury, .venus, .earth, .mars:
.terrestrial
case .jupiter, .saturn, .uranus, .neptune:
.gasGiant
}
let canBeTerraformed =
if
let star = planet.star,
!planet.isHabitable,
planet.isInHabitableZone(of: star)
{
true
} else {
false
}
// ALSO RIGHT. This example cannot be converted to an if/switch expression
// because one of the branches is more than just a single expression.
let planetLocation: String
if let star = planet.star {
planetLocation = "The \(star.name) system"
} else {
let actualLocaton = galaxy.name ?? "the universe"
planetLocation = "Rogue planet somewhere in \(actualLocation)"
}
(链接) 在具有多行主体的 switch case 之后插入一个空行。 单个 switch 语句中的间距应保持一致。 如果任何 case 具有多行主体,则所有 case 都应包含尾随空行。 最后一个 switch case 不需要空行,因为它已经被右大括号跟随。
与文件中的声明类似,在范围之间插入空行可以更容易地在视觉上区分它们。
如果没有 case 之间的空行,复杂的 switch 语句在视觉上会很忙,这使得读取代码更加困难,并且更难以一目了然地区分各个 case。 各个 case 之间的空行使复杂的 switch 语句更易于阅读。
// WRONG. These switch cases should be followed by a blank line.
func handle(_ action: SpaceshipAction) {
switch action {
case .engageWarpDrive:
navigationComputer.destination = targetedDestination
warpDrive.spinUp()
warpDrive.activate()
case .enableArtificialGravity:
artificialGravityEngine.enable(strength: .oneG)
case .scanPlanet(let planet):
scanner.target = planet
scanner.scanAtmosphere()
scanner.scanBiosphere()
scanner.scanForArtificialLife()
case .handleIncomingEnergyBlast:
energyShields.engage()
}
}
// WRONG. While the `.enableArtificialGravity` case isn't multi-line, the other cases are.
// For consistency, it should also include a trailing blank line.
func handle(_ action: SpaceshipAction) {
switch action {
case .engageWarpDrive:
navigationComputer.destination = targetedDestination
warpDrive.spinUp()
warpDrive.activate()
case .enableArtificialGravity:
artificialGravityEngine.enable(strength: .oneG)
case .scanPlanet(let planet):
scanner.target = planet
scanner.scanAtmosphere()
scanner.scanBiosphere()
scanner.scanForArtificialLife()
case .handleIncomingEnergyBlast:
energyShields.engage()
}
}
// RIGHT. All of the cases have a trailing blank line.
func handle(_ action: SpaceshipAction) {
switch action {
case .engageWarpDrive:
navigationComputer.destination = targetedDestination
warpDrive.spinUp()
warpDrive.activate()
case .enableArtificialGravity:
artificialGravityEngine.enable(strength: .oneG)
case .scanPlanet(let planet):
scanner.target = planet
scanner.scanAtmosphere()
scanner.scanBiosphere()
scanner.scanForArtificialLife()
case .handleIncomingEnergyBlast:
energyShields.engage()
}
}
// RIGHT. Since none of the cases are multi-line, blank lines are not required.
func handle(_ action: SpaceshipAction) {
switch action {
case .engageWarpDrive:
warpDrive.engage()
case .enableArtificialGravity:
artificialGravityEngine.enable(strength: .oneG)
case .scanPlanet(let planet):
scanner.scan(planet)
case .handleIncomingEnergyBlast:
energyShields.engage()
}
}
// ALSO RIGHT. Blank lines are still permitted after single-line switch cases if it helps with readability.
func handle(_ action: SpaceshipAction) {
switch action {
case .engageWarpDrive:
warpDrive.engage()
case .enableArtificialGravity:
artificialGravityEngine.enable(strength: .oneG)
case .scanPlanet(let planet):
scanner.scan(planet)
case .handleIncomingEnergyBlast:
energyShields.engage()
}
}
// WRONG. While it's fine to use blank lines to separate cases, spacing within a single switch statement should be consistent.
func handle(_ action: SpaceshipAction) {
switch action {
case .engageWarpDrive:
warpDrive.engage()
case .enableArtificialGravity:
artificialGravityEngine.enable(strength: .oneG)
case .scanPlanet(let planet):
scanner.scan(planet)
case .handleIncomingEnergyBlast:
energyShields.engage()
}
}
(链接) 在多行 guard 语句中,在 else
关键字前添加换行符。 对于单行 guard 语句,将 else
关键字与 guard
关键字保持在同一行。左大括号应紧跟 else
关键字。
// WRONG (else should be on its own line for multi-line guard statements)
guard
let galaxy,
galaxy.name == "Milky Way" else
{ … }
// WRONG (else should be on the same line for single-line guard statements)
guard let galaxy
else { … }
// RIGHT
guard
let galaxy,
galaxy.name == "Milky Way"
else { … }
// RIGHT
guard let galaxy else {
…
}
(链接) 缩进多行字符串字面量的内容和结尾的三引号,除非字符串字面量在其自己的行上开始,在这种情况下,字符串字面量的内容和结尾的三引号应与开头的三引号具有相同的缩进。
// WRONG
var spaceQuote = """
“Space,” it says, “is big. Really big. You just won’t believe how vastly, hugely, mindbogglingly big it is.
I mean, you may think it’s a long way down the road to the chemist’s, but that’s just peanuts to space.”
"""
// RIGHT
var spaceQuote = """
“Space,” it says, “is big. Really big. You just won’t believe how vastly, hugely, mindbogglingly big it is.
I mean, you may think it’s a long way down the road to the chemist’s, but that’s just peanuts to space.”
"""
// WRONG
var universeQuote: String {
"""
In the beginning the Universe was created.
This has made a lot of people very angry and been widely regarded as a bad move.
"""
}
// RIGHT
var universeQuote: String {
"""
In the beginning the Universe was created.
This has made a lot of people very angry and been widely regarded as a bad move.
"""
}
(链接) 对于 NSRange 等类型,使用构造函数而不是 Make() 函数。
// WRONG
let range = NSMakeRange(10, 5)
// RIGHT
let range = NSRange(location: 10, length: 5)
(链接) 对于具有规范简写形式的标准库类型(Optional
、Array
、Dictionary
),优先使用简写形式而不是完整的泛型形式。
// WRONG
let optional: Optional<String> = nil
let array: Array<String> = []
let dictionary: Dictionary<String, Any> = [:]
// RIGHT
let optional: String? = nil
let array: [String] = []
let dictionary: [String: Any] = [:]
(链接) 在不需要时省略显式的 .init
。
// WRONG
let universe = Universe.init()
// RIGHT
let universe = Universe()
(链接) 单行表达式后面的左大括号应与语句的其余部分位于同一行。
// WRONG
if !planet.isHabitable
{
planet.terraform()
}
class Planet
{
func terraform()
{
generateAtmosphere()
generateOceans()
}
}
// RIGHT
if !planet.isHabitable {
planet.terraform()
}
class Planet {
func terraform() {
generateAtmosphere()
generateOceans()
}
}
(链接) 多行表达式后面的左大括号应换到新行。
// WRONG
if
let star = planet.nearestStar(),
planet.isInHabitableZone(of: star) {
planet.terraform()
}
class Planet {
func terraform(
atmosphereOptions: AtmosphereOptions = .default,
oceanOptions: OceanOptions = .default) {
generateAtmosphere(atmosphereOptions)
generateOceans(oceanOptions)
}
}
// RIGHT
if
let star = planet.nearestStar(),
planet.isInHabitableZone(of: star)
{
planet.terraform()
}
class Planet {
func terraform(
atmosphereOptions: AtmosphereOptions = .default,
oceanOptions: OceanOptions = .default)
{
generateAtmosphere(atmosphereOptions)
generateOceans(oceanOptions)
}
}
(链接) 大括号的每一侧都应以单个空白字符(空格或换行符)分隔。
// WRONG
struct Planet{
…
}
// WRONG
if condition{
…
}else{
…
}
// RIGHT
struct Planet {
…
}
// RIGHT
if condition {
…
} else {
…
}
(链接) 对于函数调用和声明,参数列表的括号前后和内部不应有空格。
// WRONG
func install ( _ engine: Engine ) { }
install ( AntimatterDrive( ) )
// RIGHT
func install(_ engine: Engine) { }
install(AntimatterDrive())
(链接) 注释块应使用单行注释(代码注释使用 //
,文档注释使用 ///
),而不是多行注释(/* ... */
和 /** ... */
)。
// WRONG
/**
* A planet that exists somewhere in the universe.
*
* Planets have many properties. For example, the best planets
* have atmospheres and bodies of water to support life.
*/
class Planet {
/**
Terraforms the planet, by adding an atmosphere and ocean that is hospitable for life.
*/
func terraform() {
/*
Generate the atmosphere first, before generating the ocean.
Otherwise, the water will just boil off immediately.
*/
generateAtmosphere()
/* Now that we have an atmosphere, it's safe to generate the ocean */
generateOceans()
}
}
// RIGHT
/// A planet that exists somewhere in the universe.
///
/// Planets have many properties. For example, the best planets
/// have atmospheres and bodies of water to support life.
class Planet {
/// Terraforms the planet, by adding an atmosphere and ocean that is hospitable for life.
func terraform() {
// Generate the atmosphere first, before generating the ocean.
// Otherwise, the water will just boil off immediately.
generateAtmosphere()
// Now that we have an atmosphere, it's safe to generate the ocean
generateOceans()
}
}
(链接) 在类型主体内或顶层声明之前,使用文档注释 (///
) 而不是常规注释 (//
)。
// WRONG
// A planet that exists somewhere in the universe.
class Planet {
// Data about the composition and density of the planet's atmosphere if present.
var atmosphere: Atmosphere?
// Data about the size, location, and composition of large bodies of water on the planet's surface.
var oceans: [Ocean]
// Terraforms the planet, by adding an atmosphere and ocean that is hospitable for life.
func terraform() {
// This gas composition has a pretty good track record so far!
let composition = AtmosphereComposition(nitrogen: 0.78, oxygen: 0.22)
// Generate the atmosphere first, then the oceans. Otherwise, the water will just boil off immediately.
generateAtmosphere(using: composition)
generateOceans()
}
}
// RIGHT
/// A planet that exists somewhere in the universe.
class Planet {
/// Data about the composition and density of the planet's atmosphere if present.
var atmosphere: Atmosphere?
/// Data about the size, location, and composition of large bodies of water on the planet's surface.
var oceans: [Ocean]
/// Terraforms the planet, by adding an atmosphere and ocean that is hospitable for life.
func terraform() {
// This gas composition has a pretty good track record so far!
let composition = AtmosphereComposition(nitrogen: 0.78, oxygen: 0.22)
// Generate the atmosphere first, then the oceans. Otherwise, the water will just boil off immediately.
generateAtmosphere(using: composition)
generateOceans()
}
}
// ALSO RIGHT:
func terraform() {
/// This gas composition has a pretty good track record so far!
/// - Doc comments are not required before local declarations in function scopes, but are permitted.
let composition = AtmosphereComposition(nitrogen: 0.78, oxygen: 0.22)
/// Generate the `atmosphere` first, **then** the `oceans`. Otherwise, the water will just boil off immediately.
/// - Comments not preceding declarations can use doc comments, and will not be autocorrected into regular comments.
/// This can be useful because Xcode applies markdown styling to doc comments but not regular comments.
generateAtmosphere(using: composition)
generateOceans()
}
在某些情况下,允许在声明之前使用常规注释。
例如,注释指令(如 // swiftformat:
、// swiftlint:
、// sourcery:
、// MARK:
和 // TODO:
)通常需要使用常规注释,并且无法与文档注释一起正确使用。
// RIGHT
// swiftformat:sort
enum FeatureFlags {
case allowFasterThanLightTravel
case disableGravity
case enableDarkEnergy
case enableDarkMatter
}
// TODO: There are no more production consumers of this legacy model, so we
// should detangle the remaining code dependencies and clean it up.
struct LegacyGeocentricUniverseModel {
...
}
也允许在一组声明块之前使用常规注释,因为注释可能指的是整个块,而不仅仅是后面的声明。
// RIGHT
enum Planet {
// The inner planets
case mercury
case venus
case earth
case mars
// The outer planets
case jupiter
case saturn
case uranus
case neptune
}
// ALSO RIGHT
enum Planet {
/// The smallest planet
case mercury
case venus
case earth
case mars
/// The largest planet
case jupiter
case saturn
case uranus
case neptune
}
(链接) 将声明的文档注释放置在任何属性或修饰符之前。
// WRONG
@MainActor
/// A spacecraft with everything you need to explore the universe.
struct Spaceship { … }
public
/// A spacecraft with everything you need to explore the universe.
struct Spaceship { … }
// RIGHT
/// A spacecraft with everything you need to explore the universe.
@MainActor
struct Spaceship { … }
/// A spacecraft with everything you need to explore the universe.
public struct Spaceship { … }
(链接) 在注释分隔符(//
、///
、/*
和 */
)前后包含空格或换行符。
// WRONG
///A spacecraft with incredible performance characteristics
struct Spaceship {
func travelFasterThanLight() {/*unimplemented*/}
func travelBackInTime() { }//TODO: research whether or not this is possible
}
// RIGHT
/// A spacecraft with incredible performance characteristics
struct Spaceship {
func travelFasterThanLight() { /* unimplemented */ }
func travelBackInTime() { } // TODO: research whether or not this is possible
}
(链接) 在空的大括号 ({ }
) 中包含一个空格。
// WRONG
extension Spaceship: Trackable {}
extension SpaceshipView {
var accessibilityIdentifier: String {
get { spaceship.name }
set {}
}
}
// RIGHT
extension Spaceship: Trackable { }
extension SpaceshipView {
var accessibilityIdentifier: String {
get { spaceship.name }
set { }
}
}
(链接) 优先使用 for
循环而不是函数式 forEach(…)
方法,除非将 forEach(…)
用作函数式链中的最后一个元素。
与 forEach(…)
方法相比,For 循环更符合语言习惯,并且通常为具有 C 系列语言经验的所有开发人员所熟悉。
For 循环也比 forEach(…)
方法更具表现力。For 循环支持 return
、continue
和 break
控制流关键字,而 forEach(…)
仅支持 return
(其行为与 for 循环中的 continue
相同)。
// WRONG
planets.forEach { planet in
planet.terraform()
}
// WRONG
planets.forEach {
$0.terraform()
}
// RIGHT
for planet in planets {
planet.terraform()
}
// ALSO FINE, since forEach is useful when paired with other functional methods in a chain.
planets
.filter { !$0.isGasGiant }
.map { PlanetTerraformer(planet: $0) }
.forEach { $0.terraform() }
(链接) 在定义具有 internal 访问控制级别的类型、属性或函数时,省略 internal
关键字。
// WRONG
internal class Spaceship {
internal init() { … }
internal func travel(to planet: Planet) { … }
}
// RIGHT, because internal access control is implied if no other access control level is specified.
class Spaceship {
init() { … }
func travel(to planet: Planet) { … }
}
(链接) 从函数定义中省略 Void
返回类型。
// WRONG
func doSomething() -> Void {
...
}
// RIGHT
func doSomething() {
...
}
(链接) 在每个参数标签之前,以及在返回签名或任何 effect (async
, throws
) 之前,使用换行符分隔长函数声明。 将左大括号放在下一行,这样第一条可执行行看起来不像另一个参数。
class Universe {
// WRONG
func generateStars(at location: Point, count: Int, color: StarColor, withAverageDistance averageDistance: Float) -> String {
// This is too long and will probably auto-wrap in a weird way
}
// WRONG
func generateStars(at location: Point,
count: Int,
color: StarColor,
withAverageDistance averageDistance: Float) -> String
{
// Xcode indents all the arguments
}
// WRONG
func generateStars(
at location: Point,
count: Int,
color: StarColor,
withAverageDistance averageDistance: Float) -> String {
populateUniverse() // this line blends in with the argument list
}
// WRONG
func generateStars(
at location: Point,
count: Int,
color: StarColor,
withAverageDistance averageDistance: Float) throws
-> String {
populateUniverse() // this line blends in with the argument list
}
// WRONG
func generateStars(
at location: Point,
count: Int,
color: StarColor,
withAverageDistance averageDistance: Float) async throws // these effects are easy to miss since they're visually associated with the last parameter
-> String
{
populateUniverse()
}
// RIGHT
func generateStars(
at location: Point,
count: Int,
color: StarColor,
withAverageDistance averageDistance: Float)
-> String
{
populateUniverse()
}
// RIGHT
func generateStars(
at location: Point,
count: Int,
color: StarColor,
withAverageDistance averageDistance: Float)
async throws -> String
{
populateUniverse()
}
}
(链接) 长函数调用也应该在每个参数上换行。 将右括号放在调用的最后一行。
// WRONG
universe.generateStars(at: location, count: 5, color: starColor, withAverageDistance: 4)
// WRONG
universe.generateStars(at: location,
count: 5,
color: starColor,
withAverageDistance: 4)
// WRONG
universe.generateStars(
at: location,
count: 5,
color: starColor,
withAverageDistance: 4
)
// WRONG
universe.generate(5,
.stars,
at: location)
// RIGHT
universe.generateStars(
at: location,
count: 5,
color: starColor,
withAverageDistance: 4)
// RIGHT
universe.generate(
5,
.stars,
at: location)
(链接) 将未使用的函数参数命名为下划线 (_
)。
将未使用的函数参数命名为下划线可以更清楚地表明该参数在函数主体中未使用。 这可以更容易地捕获细微的逻辑错误,并可以突出显示简化方法签名的机会。
// WRONG
// In this method, the `newCondition` parameter is unused.
// This is actually a logical error, and is easy to miss, but compiles without warning.
func updateWeather(_ newCondition: WeatherCondition) -> Weather {
var updatedWeather = self
updatedWeather.condition = condition // this mistake inadvertently makes this method unable to change the weather condition
return updatedWeather
}
// In this method, the `color` parameter is unused.
// Is this a logical error (e.g. should it be passed through to the `universe.generateStars` method call),
// or is this an unused argument that should be removed from the method signature?
func generateUniverseWithStars(
at location: Point,
count: Int,
color: StarColor,
withAverageDistance averageDistance: Float)
{
let universe = generateUniverse()
universe.generateStars(
at: location,
count: count,
withAverageDistance: averageDistance)
}
// RIGHT
// Automatically reformatting the unused parameter to be an underscore
// makes it more clear that the parameter is unused, which makes it
// easier to spot the logical error.
func updateWeather(_: WeatherCondition) -> Weather {
var updatedWeather = self
updatedWeather.condition = condition
return updatedWeather
}
// The underscore makes it more clear that the `color` parameter is unused.
// This method argument can either be removed if truly unnecessary,
// or passed through to `universe.generateStars` to correct the logical error.
func generateUniverseWithStars(
at location: Point,
count: Int,
color _: StarColor,
withAverageDistance averageDistance: Float)
{
let universe = generateUniverse()
universe.generateStars(
at: location,
count: count,
withAverageDistance: averageDistance)
}
(链接) 移除链式函数之间的空行。
提高可读性和可维护性,使其更容易看到应用于对象的函数序列。
// WRONG
var innerPlanetNames: [String] {
planets
.filter { $0.isInnerPlanet }
.map { $0.name }
}
// WRONG
var innerPlanetNames: [String] {
planets
.filter { $0.isInnerPlanet }
// Gets the name of the inner planet
.map { $0.name }
}
// RIGHT
var innerPlanetNames: [String] {
planets
.filter { $0.isInnerPlanet }
.map { $0.name }
}
// RIGHT
var innerPlanetNames: [String] {
planets
.filter { $0.isInnerPlanet }
// Gets the name of the inner planet
.map { $0.name }
}
(链接) 在闭包声明中,倾向于使用 Void
返回类型而不是 ()
。 如果必须在函数声明中指定 Void
返回类型,请使用 Void
而不是 ()
以提高可读性。
// WRONG
func method(completion: () -> ()) {
...
}
// RIGHT
func method(completion: () -> Void) {
...
}
(链接) 将未使用的闭包参数命名为下划线 (_
)。
(链接) 闭包的每个大括号内应包含单个空格或换行符。 尾随闭包还应在每个大括号外包含单个空格或换行符。
// WRONG
let evenSquares = numbers.filter{$0.isMultiple(of: 2)}.map{ $0 * $0 }
// RIGHT
let evenSquares = numbers.filter { $0.isMultiple(of: 2) }.map { $0 * $0 }
// WRONG
let evenSquares = numbers.filter( { $0.isMultiple(of: 2) } ).map( { $0 * $0 } )
// RIGHT
let evenSquares = numbers.filter({ $0.isMultiple(of: 2) }).map({ $0 * $0 })
// WRONG
let evenSquares = numbers
.filter{
$0.isMultiple(of: 2)
}
.map{
$0 * $0
}
// RIGHT
let evenSquares = numbers
.filter {
$0.isMultiple(of: 2)
}
.map {
$0 * $0
}
(链接) 从闭包表达式中省略 Void
返回类型。
// WRONG
someAsyncThing() { argument -> Void in
...
}
// RIGHT
someAsyncThing() { argument in
...
}
(链接) 对于没有参数名称的闭包参数,首选尾随闭包语法。
// WRONG
planets.map({ $0.name })
// RIGHT
planets.map { $0.name }
// ALSO RIGHT, since this closure has a parameter name
planets.first(where: { $0.isGasGiant })
// ALSO FINE. Trailing closure syntax is still permitted for closures
// with parameter names. However, consider using non-trailing syntax
// in cases where the parameter name is semantically meaningful.
planets.first { $0.isGasGiant }
(链接) 避免使用 unowned
捕获。 而是首选更安全的选择,例如 weak
捕获,或直接捕获变量。
// WRONG: Crashes if `self` has been deallocated when closures are called.
final class SpaceshipNavigationService {
let spaceship: Spaceship
let planet: Planet
func colonizePlanet() {
spaceship.travel(to: planet, onArrival: { [unowned self] in
planet.colonize()
})
}
func exploreSystem() {
spaceship.travel(to: planet, nextDestination: { [unowned self] in
planet.moons?.first
})
}
}
weak
捕获更安全,因为它们要求作者显式处理引用的对象不再存在的情况。
// RIGHT: Uses a `weak self` capture and explicitly handles the case where `self` has been deallocated
final class SpaceshipNavigationService {
let spaceship: Spaceship
let planet: Planet
func colonizePlanet() {
spaceship.travel(to: planet, onArrival: { [weak self] in
guard let self else { return }
planet.colonize()
})
}
func exploreSystem() {
spaceship.travel(to: planet, nextDestination: { [weak self] in
guard let self else { return nil }
return planet.moons?.first
})
}
}
或者,考虑直接捕获闭包中使用的变量。 这可以让您避免处理 self
为 nil 的情况,因为您甚至不需要引用 self
// RIGHT: Explicitly captures `planet` instead of capturing `self`
final class SpaceshipNavigationService {
let spaceship: Spaceship
let planet: Planet
func colonizePlanet() {
spaceship.travel(to: planet, onArrival: { [planet] in
planet.colonize()
})
}
func exploreSystem() {
spaceship.travel(to: planet, nextDestination: { [planet] in
planet.moons?.first
})
}
}
(链接) 中缀运算符的两侧应包含单个空格。 但是,在运算符定义中,省略运算符和左括号之间的尾随空格。 此规则不适用于范围运算符(例如 1...3
)。
// WRONG
let capacity = 1+2
let capacity = currentCapacity??0
let capacity=newCapacity
let latitude = region.center.latitude-region.span.latitudeDelta/2.0
// RIGHT
let capacity = 1 + 2
let capacity = currentCapacity ?? 0
let capacity = newCapacity
let latitude = region.center.latitude - region.span.latitudeDelta / 2.0
// WRONG
static func == (_ lhs: MyView, _ rhs: MyView) -> Bool {
lhs.id == rhs.id
}
// RIGHT
static func ==(_ lhs: MyView, _ rhs: MyView) -> Bool {
lhs.id == rhs.id
}
(链接) 长三元运算符表达式应在 ?
之前和 :
之前换行,将每个条件分支放在单独的一行上。
// WRONG (too long)
let destinationPlanet = solarSystem.hasPlanetsInHabitableZone ? solarSystem.planetsInHabitableZone.first : solarSystem.uninhabitablePlanets.first
// WRONG (naive wrapping)
let destinationPlanet = solarSystem.hasPlanetsInHabitableZone ? solarSystem.planetsInHabitableZone.first :
solarSystem.uninhabitablePlanets.first
// WRONG (unbalanced operators)
let destinationPlanet = solarSystem.hasPlanetsInHabitableZone ?
solarSystem.planetsInHabitableZone.first :
solarSystem.uninhabitablePlanets.first
// RIGHT
let destinationPlanet = solarSystem.hasPlanetsInHabitableZone
? solarSystem.planetsInHabitableZone.first
: solarSystem.uninhabitablePlanets.first
(链接) 在条件语句 (if
、guard
、while
) 中,使用逗号 (,
) 而不是 &&
运算符分隔布尔条件。
// WRONG
if let star = planet.star, !planet.isHabitable && planet.isInHabitableZone(of: star) {
planet.terraform()
}
if
let star = planet.star,
!planet.isHabitable
&& planet.isInHabitableZone(of: star)
{
planet.terraform()
}
// RIGHT
if let star = planet.star, !planet.isHabitable, planet.isInHabitableZone(of: star) {
planet.terraform()
}
if
let star = planet.star,
!planet.isHabitable,
planet.isInHabitableZone(of: star)
{
planet.terraform()
}
(链接) 在扩展绑定泛型类型时,首选使用泛型括号语法 (extension Collection<Planet>
),或者适用于标准库类型的简化语法 (extension [Planet]
) 代替泛型类型约束。
// WRONG
extension Array where Element == Star { … }
extension Optional where Wrapped == Spaceship { … }
extension Dictionary where Key == Moon, Element == Planet { … }
extension Collection where Element == Universe { … }
extension StateStore where State == SpaceshipState, Action == SpaceshipAction { … }
// RIGHT
extension [Star] { … }
extension Spaceship? { … }
extension [Moon: Planet] { … }
extension Collection<Universe> { … }
extension StateStore<SpaceshipState, SpaceshipAction> { … }
// ALSO RIGHT. There are multiple types that could satisfy this constraint
// (e.g. [Planet], [Moon]), so this is not a "bound generic type" and isn't
// eligible for the generic bracket syntax.
extension Array where Element: PlanetaryBody { }
(链接) 避免使用分号。 行尾不需要分号,因此应省略。 虽然您可以使用分号将两个语句放在同一行上,但更常见和首选的方法是使用换行符分隔它们。
// WRONG. Semicolons are not required and can be omitted.
let mercury = planets[0];
let venus = planets[1];
let earth = planets[2];
// WRONG. While you can use semicolons to place multiple statements on a single line,
// it is more common and preferred to separate them using newlines instead.
let mercury = planets[0]; let venus = planets[1]; let earth = planets[2];
// RIGHT
let mercury = planets[0]
let venus = planets[1]
let earth = planets[2]
// WRONG
guard let moon = planet.moon else { completion(nil); return }
// WRONG
guard let moon = planet.moon else {
completion(nil); return
}
// RIGHT
guard let moon = planet.moon else {
completion(nil)
return
}
(链接) 尽可能在 init
时初始化属性,而不是使用隐式解包的可选项。 一个值得注意的例外是 UIViewController 的 view
属性。
// WRONG
class MyClass {
init() {
super.init()
someValue = 5
}
var someValue: Int!
}
// RIGHT
class MyClass {
init() {
someValue = 0
super.init()
}
var someValue: Int
}
(链接) 避免在 init()
中执行任何有意义或耗时的工作。 避免执行诸如打开数据库连接、发出网络请求、从磁盘读取大量数据等操作。 如果需要在对象准备好使用之前完成这些事情,则创建一个类似 start()
的方法。
(链接) 将复杂的属性观察器提取到方法中。 这减少了嵌套,将副作用与属性声明分开,并使诸如 oldValue
之类的隐式传递参数的使用变得明确。
// WRONG
class TextField {
var text: String? {
didSet {
guard oldValue != text else {
return
}
// Do a bunch of text-related side-effects.
}
}
}
// RIGHT
class TextField {
var text: String? {
didSet { textDidUpdate(from: oldValue) }
}
private func textDidUpdate(from oldValue: String?) {
guard oldValue != text else {
return
}
// Do a bunch of text-related side-effects.
}
}
(链接) 将复杂的回调块提取到方法中。 这限制了块中 weak-self 引入的复杂性并减少了嵌套。 如果您需要在方法调用中引用 self,请使用 guard
在回调期间解包 self。
// WRONG
class MyClass {
func request(completion: () -> Void) {
API.request() { [weak self] response in
if let self {
// Processing and side effects
}
completion()
}
}
}
// RIGHT
class MyClass {
func request(completion: () -> Void) {
API.request() { [weak self] response in
guard let self else { return }
self.doSomething(with: self.property, response: response)
completion()
}
}
func doSomething(with nonOptionalParameter: SomeClass, response: SomeResponseClass) {
// Processing and side effects
}
}
(链接) 首选在作用域的开头使用 guard
。
(链接) 访问控制应尽可能严格。 除非您需要该行为,否则首选 public
而不是 open
,首选 private
而不是 fileprivate
。
// WRONG
public struct Spaceship {
// WRONG: `engine` is used in `extension Spaceship` below,
// but extensions in the same file can access `private` members.
fileprivate let engine: AntimatterEngine
// WRONG: `hull` is not used by any other type, so `fileprivate` is unnecessary.
fileprivate let hull: Hull
// RIGHT: `navigation` is used in `extension Pilot` below,
// so `fileprivate` is necessary here.
fileprivate let navigation: SpecialRelativityNavigationService
}
extension Spaceship {
public func blastOff() {
engine.start()
}
}
extension Pilot {
public func chartCourse() {
spaceship.navigation.course = .andromedaGalaxy
spaceship.blastOff()
}
}
// RIGHT
public struct Spaceship {
fileprivate let navigation: SpecialRelativityNavigationService
private let engine: AntimatterEngine
private let hull: Hull
}
extension Spaceship {
public func blastOff() {
engine.start()
}
}
extension Pilot {
public func chartCourse() {
spaceship.navigation.course = .andromedaGalaxy
spaceship.blastOff()
}
}
(链接) 尽可能避免全局函数。 首选类型定义中的方法。
// WRONG
func age(of person: Person, bornAt: TimeInterval) -> Int {
// ...
}
func jump(person: Person) {
// ...
}
// RIGHT
class Person {
var bornAt: TimeInterval
var age: Int {
// ...
}
func jump() {
// ...
}
}
(链接) 使用无 case 的 enum
将 public
或 internal
常量和函数组织到命名空间中。
private
全局变量,因为它们的作用域限定为单个文件,并且不会污染全局命名空间。 考虑将 private 全局变量放在 enum
命名空间中,以匹配其他声明类型的准则。无 case 的 enum
作为命名空间效果很好,因为它们无法实例化,这符合它们的意图。
// WRONG
struct Environment {
static let earthGravity = 9.8
static let moonGravity = 1.6
}
// WRONG
struct Environment {
struct Earth {
static let gravity = 9.8
}
struct Moon {
static let gravity = 1.6
}
}
// RIGHT
enum Environment {
enum Earth {
static let gravity = 9.8
}
enum Moon {
static let gravity = 1.6
}
}
(链接) 使用 Swift 的自动枚举值,除非它们映射到外部源。 添加注释解释为什么定义了显式值。
为了最大限度地减少用户错误、提高可读性并更快地编写代码,请依赖 Swift 的自动枚举值。 但是,如果该值映射到外部源(例如,它来自网络请求)或跨二进制文件持久存在,请显式定义这些值,并记录这些值映射到的内容。
这确保了如果有人在中间添加一个新值,他们不会意外地破坏某些东西。
// WRONG
enum ErrorType: String {
case error = "error"
case warning = "warning"
}
// WRONG
enum UserType: String {
case owner
case manager
case member
}
// WRONG
enum Planet: Int {
case mercury = 0
case venus = 1
case earth = 2
case mars = 3
case jupiter = 4
case saturn = 5
case uranus = 6
case neptune = 7
}
// WRONG
enum ErrorCode: Int {
case notEnoughMemory
case invalidResource
case timeOut
}
// RIGHT
// Relying on Swift's automatic enum values
enum ErrorType: String {
case error
case warning
}
// RIGHT
/// These are written to a logging service. Explicit values ensure they're consistent across binaries.
// swiftformat:disable redundantRawValues
enum UserType: String {
case owner = "owner"
case manager = "manager"
case member = "member"
}
// swiftformat:enable redundantRawValues
// RIGHT
// Relying on Swift's automatic enum values
enum Planet: Int {
case mercury
case venus
case earth
case mars
case jupiter
case saturn
case uranus
case neptune
}
// RIGHT
/// These values come from the server, so we set them here explicitly to match those values.
enum ErrorCode: Int {
case notEnoughMemory = 0
case invalidResource = 1
case timeOut = 2
}
(链接) 仅当可选值具有语义意义时才使用。
(链接) 尽可能选择不可变值。 使用 map
和 compactMap
代替附加到新集合。 使用 filter
代替从可变集合中移除元素。
可变变量会增加复杂性,因此尽量将其保持在尽可能小的作用域内。
// WRONG
var results = [SomeType]()
for element in input {
let result = transform(element)
results.append(result)
}
// RIGHT
let results = input.map { transform($0) }
// WRONG
var results = [SomeType]()
for element in input {
if let result = transformThatReturnsAnOptional(element) {
results.append(result)
}
}
// RIGHT
let results = input.compactMap { transformThatReturnsAnOptional($0) }
(链接) 尽可能选择不可变或计算型静态属性,而不是可变静态属性。 尽可能使用存储的 static let
属性或计算的 static var
属性,而不是存储的 static var
属性,因为存储的 static var
属性是全局可变状态。
全局可变状态会增加复杂性,并使应用程序的行为更难以推断。 应该尽可能避免它。
// WRONG
enum Fonts {
static var title = UIFont(…)
}
// RIGHT
enum Fonts {
static let title = UIFont(…)
}
// WRONG
struct FeatureState {
var count: Int
static var initial = FeatureState(count: 0)
}
// RIGHT
struct FeatureState {
var count: Int
static var initial: FeatureState {
// Vend static properties that are cheap to compute
FeatureState(count: 0)
}
}
(链接) 使用 assert
方法结合生产环境中的适当日志记录来处理意外但可恢复的情况。 如果意外情况不可恢复,则首选 precondition
方法或 fatalError()
。 这样可以在崩溃和提供对野外意外情况的洞察之间取得平衡。 只有当失败消息是动态的时,才优先选择 fatalError
而不是 precondition
方法,因为 precondition
方法不会在崩溃报告中报告该消息。
func didSubmitText(_ text: String) {
// It's unclear how this was called with an empty string; our custom text field shouldn't allow this.
// This assert is useful for debugging but it's OK if we simply ignore this scenario in production.
guard !text.isEmpty else {
assertionFailure("Unexpected empty string")
return
}
// ...
}
func transformedItem(atIndex index: Int, from items: [Item]) -> Item {
precondition(index >= 0 && index < items.count)
// It's impossible to continue executing if the precondition has failed.
// ...
}
func makeImage(name: String) -> UIImage {
guard let image = UIImage(named: name, in: nil, compatibleWith: nil) else {
fatalError("Image named \(name) couldn't be loaded.")
// We want the error message so we know the name of the missing image.
}
return image
}
(链接) 默认将类型方法设为 static
。
(链接) 默认将类设为 final
。
(链接) 当切换枚举时,通常更喜欢枚举所有情况,而不是使用 default
情况。
枚举每种情况要求开发人员和审查人员在将来添加新情况时必须考虑每个 switch 语句的正确性。
// NOT PREFERRED
switch trafficLight {
case .greenLight:
// Move your vehicle
default:
// Stop your vehicle
}
// PREFERRED
switch trafficLight {
case .greenLight:
// Move your vehicle
case .yellowLight, .redLight:
// Stop your vehicle
}
// COUNTEREXAMPLES
enum TaskState {
case pending
case running
case canceling
case success(Success)
case failure(Error)
// We expect that this property will remain valid if additional cases are added to the enumeration.
public var isRunning: Bool {
switch self {
case .running:
true
default:
false
}
}
}
extension TaskState: Equatable {
// Explicitly listing each state would be too burdensome. Ideally this function could be implemented with a well-tested macro.
public static func == (lhs: TaskState, rhs: TaskState) -> Bool {
switch (lhs, rhs) {
case (.pending, .pending):
true
case (.running, .running):
true
case (.canceling, .canceling):
true
case (.success(let lhs), .success(let rhs)):
lhs == rhs
case (.failure(let lhs), .failure(let rhs)):
lhs == rhs
default:
false
}
}
}
(链接) 如果您不需要使用该值,请检查 nil 而不是使用可选绑定。
(链接) 当语言不需要时,省略 return
关键字。
// WRONG
["1", "2", "3"].compactMap { return Int($0) }
var size: CGSize {
return CGSize(
width: 100.0,
height: 100.0)
}
func makeInfoAlert(message: String) -> UIAlertController {
return UIAlertController(
title: "ℹ️ Info",
message: message,
preferredStyle: .alert)
}
var alertTitle: String {
if issue.severity == .critical {
return "💥 Critical Error"
} else {
return "ℹ️ Info"
}
}
func type(of planet: Planet) -> PlanetType {
switch planet {
case .mercury, .venus, .earth, .mars:
return .terrestrial
case .jupiter, .saturn, .uranus, .neptune:
return .gasGiant
}
}
// RIGHT
["1", "2", "3"].compactMap { Int($0) }
var size: CGSize {
CGSize(
width: 100.0,
height: 100.0)
}
func makeInfoAlert(message: String) -> UIAlertController {
UIAlertController(
title: "ℹ️ Info",
message: message,
preferredStyle: .alert)
}
var alertTitle: String {
if issue.severity == .critical {
"💥 Critical Error"
} else {
"ℹ️ Info"
}
}
func type(of planet: Planet) -> PlanetType {
switch planet {
case .mercury, .venus, .earth, .mars:
.terrestrial
case .jupiter, .saturn, .uranus, .neptune:
.gasGiant
}
}
(链接) 在协议定义中使用 AnyObject
代替 class
。
SE-0156 引入了对使用 AnyObject
关键字作为协议约束的支持,建议优先使用 AnyObject
而不是 class
此提案合并了
class
和AnyObject
的概念,它们现在具有相同的含义:它们代表类的存在类型。 为了消除重复,我们建议只保留AnyObject
。 为了最大限度地减少源代码中断,class
可以重新定义为typealias class = AnyObject
,并在 Swift 的第一个实现此提案的版本中对 class 发出弃用警告。 之后,class
可以在 Swift 的后续版本中删除。
// WRONG
protocol Foo: class { }
// RIGHT
protocol Foo: AnyObject { }
(链接) 分别指定扩展中每个声明的访问控制。
在声明本身上指定访问控制有助于工程师更快地确定单个声明的访问控制级别。
// WRONG
public extension Universe {
// This declaration doesn't have an explicit access control level.
// In all other scopes, this would be an internal function,
// but because this is in a public extension, it's actually a public function.
func generateGalaxy() { }
}
// WRONG
private extension Spaceship {
func enableHyperdrive() { }
}
// RIGHT
extension Universe {
// It is immediately obvious that this is a public function,
// even if the start of the `extension Universe` scope is off-screen.
public func generateGalaxy() { }
}
// RIGHT
extension Spaceship {
// Recall that a private extension actually has fileprivate semantics,
// so a declaration in a private extension is fileprivate by default.
fileprivate func enableHyperdrive() { }
}
(链接) 优先使用专用的日志系统,例如 os_log
或 swift-log
,而不是使用 print(…)
, debugPrint(…)
, 或 dump(…)
直接写入标准输出。
(链接) 不要使用 #file
。 酌情使用 #fileID
或 #filePath
。
#file
字面量(或 Swift 5.9 中的宏)的行为已经从评估为完整的源文件路径(#filePath
的行为)演变为包含模块和文件名称的可读字符串(#fileID
的行为)。 使用最适合您的用例的字面量(或宏)。
(链接) 不要在生产代码中使用 #filePath
。 请改用 #fileID
。
#filePath
应该只在非生产代码中使用,在这种代码中,源文件的完整路径可以为开发人员提供有用的信息。 因为 #fileID
不会嵌入源文件的完整路径,所以它不会暴露您的文件系统并减少已编译二进制文件的大小。
(链接) 避免总是立即调用的单表达式闭包。 而是更喜欢内联表达式。
// WRONG
lazy var universe: Universe = {
Universe()
}()
lazy var stars = {
universe.generateStars(
at: location,
count: 5,
color: starColor,
withAverageDistance: 4)
}()
// RIGHT
lazy var universe = Universe()
lazy var stars = universe.generateStars(
at: location,
count: 5,
color: starColor,
withAverageDistance: 4)
(链接) 从不包含 set
、willSet
或 didSet
子句的计算属性声明中省略 get
子句。
// WRONG
var universe: Universe {
get {
Universe()
}
}
// RIGHT
var universe: Universe {
Universe()
}
// RIGHT
var universe: Universe {
get { multiverseService.current }
set { multiverseService.current = newValue }
}
(链接) 尽可能优先使用不透明泛型参数(使用 some
)而不是冗长的命名泛型参数语法。
不透明泛型参数语法的冗长程度明显低于完整的命名泛型参数语法,因此更易于阅读。
// WRONG
func spaceshipDashboard<WarpDriveView: View, CaptainsLogView: View>(
warpDrive: WarpDriveView,
captainsLog: CaptainsLogView)
-> some View
{ … }
func generate<Planets>(_ planets: Planets) where Planets: Collection, Planets.Element == Planet {
…
}
// RIGHT
func spaceshipDashboard(
warpDrive: some View,
captainsLog: some View)
-> some View
{ … }
func generate(_ planets: some Collection<Planet>) {
…
}
// Also fine, since there isn't an equivalent opaque parameter syntax for expressing
// that two parameters in the type signature are of the same type:
func terraform<Body: PlanetaryBody>(_ planetaryBody: Body, into terraformedBody: Body) {
…
}
// Also fine, since the generic parameter name is referenced in the function body so can't be removed:
func terraform<Body: PlanetaryBody>(_ planetaryBody: Body) {
planetaryBody.generateAtmosphere(Body.idealAtmosphere)
}
完全不受约束的泛型参数不太常见,但等同于 some Any
。 例如
func assertFailure<Value>(
_ result: Result<Value, Error>,
file: StaticString = #filePath,
line: UInt = #line)
{
if case .failure(let error) = result {
XCTFail(error.localizedDescription, file: file, line: line)
}
}
// is equivalent to:
func assertFailure(
_ result: Result<some Any, Error>,
file: StaticString = #filePath,
line: UInt = #line)
{
if case .failure(let error) = result {
XCTFail(error.localizedDescription, file: file, line: line)
}
}
some Any
有些不直观,在这种情况下,命名泛型参数对于弥补弱类型信息很有用。 因此,首选使用命名泛型参数而不是 some Any
。
(链接) 尽量避免使用 @unchecked Sendable
。 尽可能使用标准的 Sendable
一致性。 如果使用尚未更新为支持 Swift 并发的模块中的类型,请使用 @preconcurrency import
抑制与并发相关的错误。
@unchecked Sendable
不提供关于类型线程安全性的任何保证,而是不安全地抑制与并发检查相关的编译器错误。
通常有其他更安全的方法来抑制与并发相关的错误
Sendable
一致性是声明类型是线程安全的首选方法。 如果符合 Sendable
的类型不是线程安全的,则编译器将发出错误。 例如,简单的值类型和不可变类始终可以安全地符合 Sendable
,但可变类不能
// RIGHT: Simple value types are thread-safe.
struct Planet: Sendable {
var mass: Double
}
// RIGHT: Immutable classes are thread-safe.
final class Planet: Sendable {
let mass: Double
}
// WRONG: Mutable classes are not thread-safe.
final class Planet: Sendable {
// ERROR: stored property 'mass' of 'Sendable'-conforming class 'Planet' is mutable
var mass: Double
}
// WRONG: @unchecked is unnecessary because the compiler can prove that the type is thread-safe.
struct Planet: @unchecked Sendable {
var mass: Double
}
如果可变类被隔离到单个 actor / 线程 / 并发域,则可以使其 Sendable
和线程安全。 通过使用类似 @MainActor
的注释将其隔离到全局 actor(这将其隔离到主 actor),可以使任何可变类都 Sendable
// RIGHT: A mutable class isolated to the main actor is thread-safe.
@MainActor
final class Planet: Sendable {
var mass: Double
}
// WRONG: @unchecked Sendable is unsafe because mutable classes are not thread-safe.
struct Planet: @unchecked Sendable {
var mass: Double
}
如果使用尚未采用 Swift 并发的模块中的非 Sendable
类型,请使用 @preconcurrency import
抑制与并发相关的错误。
/// Defined in `UniverseKit` module
class Planet: PlanetaryBody {
var star: Star
}
// WRONG: Unsafely marking a non-thread-safe class as Sendable only to suppress errors
import PlanetaryBody
extension PlanetaryBody: @unchecked Sendable { }
// RIGHT
@preconcurrency import PlanetaryBody
如果可能,请重构代码,以便编译器可以验证它是线程安全的。 这样您就可以使用 Sendable
一致性而不是不安全的 @unchecked Sendable
一致性。
在符合 Sendable
时,如果您尝试进行不线程安全的更改,编译器将来会发出错误。 使用 @unchecked Sendable
时会丢失此保证,这使得更容易意外地引入不线程安全的更改。
例如,给定这组类
class PlanetaryBody {
let mass: Double
}
class Planet: PlanetaryBody {
let star: Star
}
// NOT IDEAL: no compiler-enforced thread safety.
extension PlanetaryBody: @unchecked Sendable { }
编译器无法验证 PlanetaryBody
是否为 Sendable
,因为它不是 final
。 您可以重构代码以不使用子类化,而不是使用 @unchecked Sendable
// BETTER: Compiler-enforced thread safety.
protocol PlanetaryBody: Sendable {
var mass: Double { get }
}
final class Planet: PlanetaryBody, Sendable {
let mass: Double
let star: Star
}
有时确实需要使用 @unchecked Sendable
。 在这些情况下,您可以添加一个 // swiftlint:disable:next no_unchecked_sendable
注释,并解释我们如何知道该类型是线程安全的,以及为什么我们必须使用 @unchecked Sendable
而不是 Sendable
。
@unchecked Sendable
的一个规范且安全的用例是一个类,其中可变状态受到其他线程安全机制(如锁)的保护。 此类型是线程安全的,但编译器无法验证这一点。
struct Atomic<Value> {
/// `value` is thread-safe because it is manually protected by a lock.
var value: Value { ... }
}
// WRONG: disallowed by linter
extension Atomic: @unchecked Sendable { }
// WRONG: suppressing lint error without an explanation
// swiftlint:disable:next no_unchecked_sendable
extension Atomic: @unchecked Sendable { }
// RIGHT: suppressing the linter with an explanation why the type is thread-safe
// Atomic is thread-safe because its underlying mutable state is protected by a lock.
// swiftlint:disable:next no_unchecked_sendable
extension Atomic: @unchecked Sendable { }
对于在现有用法中是线程安全的,但无法重构以支持正确的 Sendable
一致性(例如,由于向后兼容性约束)的类型,使用 @unchecked Sendable
也是合理的
class PlanetaryBody {
let mass: Double
}
class Planet: PlanetaryBody {
let star: Star
}
// WRONG: disallowed by linter
extension PlanetaryBody: @unchecked Sendable { }
// WRONG: suppressing lint error without an explanation
// swiftlint:disable:next no_unchecked_sendable
extension PlanetaryBody: @unchecked Sendable { }
// RIGHT: suppressing the linter with an explanation why the type is thread-safe
// PlanetaryBody cannot conform to Sendable because it is non-final and has subclasses.
// PlanetaryBody itself is safely Sendable because it only consists of immutable values.
// All subclasses of PlanetaryBody are also simple immutable values, so are safely Sendable as well.
// swiftlint:disable:next no_unchecked_sendable
extension PlanetaryBody: @unchecked Sendable { }
(链接) 避免定义然后立即返回的属性。 而是直接返回值。
立即返回的属性声明通常是多余和不必要的。 有时,这些声明是重构的副产品,并非有意创建的。 自动清理它们可以简化代码。 在某些情况下,这也会导致 return
关键字本身变得不必要,从而进一步简化代码。
// WRONG
var spaceship: Spaceship {
let spaceship = spaceshipBuilder.build(warpDrive: warpDriveBuilder.build())
return spaceship
}
// RIGHT
var spaceship: Spaceship {
spaceshipBuilder.build(warpDrive: warpDriveBuilder.build())
}
// WRONG
var spaceship: Spaceship {
let warpDrive = warpDriveBuilder.build()
let spaceship = spaceshipBuilder.build(warpDrive: warpDrive)
return spaceship
}
// RIGHT
var spaceship: Spaceship {
let warpDrive = warpDriveBuilder.build()
return spaceshipBuilder.build(warpDrive: warpDrive)
}
(link) 当比较类型的全部属性时,首选使用生成的 Equatable 实现。 对于结构体,如果可能,首选使用编译器合成的 Equatable 实现。
手动实现的 Equatable 实现很冗长,并且保持它们的最新状态容易出错。 例如,在添加新属性时,可能会忘记更新 Equatable 实现以比较它。
/// WRONG: The `static func ==` implementation is redundant and error-prone.
struct Planet: Equatable {
let mass: Double
let orbit: OrbitalElements
let rotation: Double
static func ==(lhs: Planet, rhs: Planet) -> Bool {
lhs.mass == rhs.mass
&& lhs.orbit == rhs.orbit
&& lhs.rotation == rhs.rotation
}
}
/// RIGHT: The `static func ==` implementation is synthesized by the compiler.
struct Planet: Equatable {
let mass: Double
let orbit: OrbitalElements
let rotation: Double
}
/// ALSO RIGHT: The `static func ==` implementation differs from the implementation that
/// would be synthesized by the compiler and compared all properties, so is not redundant.
struct CelestialBody: Equatable {
let id: UUID
let orbit: OrbitalElements
static func ==(lhs: Planet, rhs: Planet) -> Bool {
lhs.id == rhs.id
}
}
在提供 @Equatable
宏的项目中,首选使用该宏为类生成 static func ==
,而不是手动实现。
/// WRONG: The `static func ==` implementation is verbose and error-prone.
final class Planet: Equatable {
let mass: Double
let orbit: OrbitalElements
let rotation: Double
static func ==(lhs: Planet, rhs: Planet) -> Bool {
lhs.mass == rhs.mass
&& lhs.orbit == rhs.orbit
&& lhs.rotation == rhs.rotation
}
}
/// RIGHT: The `static func ==` implementation is generated by the `@Equatable` macro.
@Equatable
final class struct Planet: Equatable {
let mass: Double
let orbit: OrbitalElements
let rotation: Double
}
(link) 首选使用 @Entry
宏在 EnvironmentValues
中定义属性。 当向 SwiftUI EnvironemtnValues
添加属性时,如果可能,首选使用编译器合成的属性实现。
手动实现的环境键很冗长,并且被认为是一种遗留模式。 考虑到 @Entry
已向后移植到 iOS 13,因此专门用于替换它。
/// WRONG: The `EnvironmentValues` property depends on `IsSelectedEnvironmentKey`
struct IsSelectedEnvironmentKey: EnvironmentKey {
static var defaultValue: Bool { false }
}
extension EnvironmentValues {
var isSelected: Bool {
get { self[IsSelectedEnvironmentKey.self] }
set { self[IsSelectedEnvironmentKey.self] = newValue }
}
}
/// RIGHT: The `EnvironmentValues` property uses the @Entry macro
extension EnvironmentValues {
@Entry var isSelected: Bool = false
}
(link) 避免使用 ()
作为类型。 首选 Void
。
// WRONG
let result: Result<(), Error>
// RIGHT
let result: Result<Void, Error>
(link) 避免使用 Void()
作为 Void
的实例。 首选 ()
。
let completion: (Result<Void, Error>) -> Void
// WRONG
completion(.success(Void()))
// RIGHT
completion(.success(()))
(link) 首选使用 count(where: { … })
而不是 filter { … }.count
。
Swift 6.0 (终于!) 向标准库添加了 count(where:)
方法。 首选使用 count(where:)
方法,而不是使用 filter(_:)
方法后跟 count
调用。
// WRONG
let planetsWithMoons = planets.filter { !$0.moons.isEmpty }.count
// RIGHT
let planetsWithMoons = planets.count(where: { !$0.moons.isEmpty })
(link) 对文件中的模块导入进行字母排序和去重。 将所有导入放置在文件的顶部,位于标题注释下方。 不要在 import 语句之间添加额外的换行符。 在第一个 import 语句之前和最后一个 import 语句之后添加一个空行。
// WRONG
// Copyright © 2018 Airbnb. All rights reserved.
//
import DLSPrimitives
import Constellation
import Constellation
import Epoxy
import Foundation
// RIGHT
// Copyright © 2018 Airbnb. All rights reserved.
//
import Constellation
import DLSPrimitives
import Epoxy
import Foundation
例外:@testable import
应在常规 import 之后分组,并用空行分隔。
// WRONG
// Copyright © 2018 Airbnb. All rights reserved.
//
import DLSPrimitives
@testable import Epoxy
import Foundation
import Nimble
import Quick
// RIGHT
// Copyright © 2018 Airbnb. All rights reserved.
//
import DLSPrimitives
import Foundation
import Nimble
import Quick
@testable import Epoxy
(link) 将连续空格限制为一个空行或空格(不包括缩进)。 优先使用以下格式化准则,而不是不同高度或宽度的空格。
// WRONG
struct Planet {
let mass: Double
let hasAtmosphere: Bool
func distance(to: Planet) { }
}
// RIGHT
struct Planet {
let mass: Double
let hasAtmosphere: Bool
func distance(to: Planet) { }
}
(link) 文件应以换行符结尾。
(link) 包含跨越多行的作用域的声明应与同一作用域中的相邻声明用换行符分隔。 在多行作用域声明(例如,类型、扩展、函数、计算属性等)和相同缩进级别的其他声明之间插入一个空行。
将作用域声明与同一作用域中的其他声明分开,可以在视觉上分隔它们,使相邻的声明更容易与作用域声明区分开。
// WRONG
struct SolarSystem {
var numberOfPlanets: Int {
…
}
func distance(to: SolarSystem) -> AstronomicalUnit {
…
}
}
struct Galaxy {
func distance(to: Galaxy) -> AstronomicalUnit {
…
}
func contains(_ solarSystem: SolarSystem) -> Bool {
…
}
}
// RIGHT
struct SolarSystem {
var numberOfPlanets: Int {
…
}
func distance(to: SolarSystem) -> AstronomicalUnit {
…
}
}
struct Galaxy {
func distance(to: Galaxy) -> AstronomicalUnit {
…
}
func contains(_ solarSystem: SolarSystem) -> Bool {
…
}
}
(link) 删除作用域顶部和底部的空行,不包括可以选择性地包含空行的类型主体。
// WRONG
class Planet {
func terraform() {
generateAtmosphere()
generateOceans()
}
}
// RIGHT
class Planet {
func terraform() {
generateAtmosphere()
generateOceans()
}
}
// Also fine!
class Planet {
func terraform() {
generateAtmosphere()
generateOceans()
}
}
(link) 每个实现一致性的类型和扩展都应以 MARK
注释开头。
// MARK: - TypeName
注释开头。// MARK: - TypeName + ProtocolName
注释开头。// MARK: ProtocolName
。MARK
注释。MARK
注释。MARK
。// MARK: - GalaxyView
final class GalaxyView: UIView { … }
// MARK: ContentConfigurableView
extension GalaxyView: ContentConfigurableView { … }
// MARK: - Galaxy + SpaceThing, NamedObject
extension Galaxy: SpaceThing, NamedObject { … }
(link) 使用 // MARK:
将类型定义和扩展的内容按以下顺序列出的部分分开。 所有类型定义和扩展都应以这种一致的方式划分,使代码的读者可以轻松地跳转到他们感兴趣的内容。
// MARK: Lifecycle
用于 init
和 deinit
方法。// MARK: Open
用于 open
属性和方法。// MARK: Public
用于 public
属性和方法。// MARK: Package
用于 package
属性和方法。// MARK: Internal
用于 internal
属性和方法。// MARK: Fileprivate
用于 fileprivate
属性和方法。// MARK: Private
用于 private
属性和方法。// MARK:
上方。internal
属性组成),则可以省略 // MARK:
。// MARK:
,因为它会损害可读性。(link) 在每个顶层部分中,按以下顺序放置内容。 这使您的代码的新读者可以更轻松地找到他们想要的内容。
计算属性和带有属性观察器的属性应该出现在同类型声明的末尾。(例如:实例属性。)
// WRONG
class PlanetView: UIView {
static var startOfTime { -CGFloat.greatestFiniteMagnitude / 0 }
var atmosphere: Atmosphere {
didSet {
print("oh my god, the atmosphere changed")
}
}
override class var layerClass: AnyClass {
PlanetLayer.self
}
var gravity: CGFloat
static let speedOfLight: CGFloat = 300_000
}
// RIGHT
class PlanetView: UIView {
static let speedOfLight: CGFloat = 300_000
static var startOfTime { -CGFloat.greatestFiniteMagnitude / 0 }
override class var layerClass: AnyClass {
PlanetLayer.self
}
var gravity: CGFloat
var atmosphere: Atmosphere {
didSet {
print("oh my god, the atmosphere changed")
}
}
}
SwiftUI 属性是一种特殊的属性,存在于 SwiftUI 视图内部。 这些视图遵循 DynamicProperty
协议,并导致视图的 body 重新计算。 考虑到这个常见的功能以及相似的语法,最好将它们分组。
// WRONG
struct CustomSlider: View {
// MARK: Internal
var body: some View {
...
}
// MARK: Private
@Binding private var value: Value
private let range: ClosedRange<Double>
@Environment(\.sliderStyle) private var style
private let step: Double.Stride
@Environment(\.layoutDirection) private var layoutDirection
}
// RIGHT
struct CustomSlider: View {
// MARK: Internal
var body: some View {
...
}
// MARK: Private
@Environment(\.sliderStyle) private var style
@Environment(\.layoutDirection) private var layoutDirection
@Binding private var value: Value
private let range: ClosedRange<Double>
private let step: Double.Stride
}
此外,在 SwiftUI 属性的分组中,最好将这些属性也按其动态属性类型进行分组。 格式化程序应用的组顺序由类型首次出现的时间决定。
// WRONG
struct CustomSlider: View {
@Binding private var value: Value
@State private var foo = Foo()
@Environment(\.sliderStyle) private var style
@State private var bar = Bar()
@Environment(\.layoutDirection) private var layoutDirection
private let range: ClosedRange<Double>
private let step: Double.Stride
}
// RIGHT
struct CustomSlider: View {
@Binding private var value: Value
@State private var foo = Foo()
@State private var bar = Bar()
@Environment(\.sliderStyle) private var style
@Environment(\.layoutDirection) private var layoutDirection
private let range: ClosedRange<Double>
private let step: Double.Stride
}
(链接) 在不同类型的属性声明之间添加空行。 (例如:静态属性和实例属性之间。)
// WRONG
static let gravityEarth: CGFloat = 9.8
static let gravityMoon: CGFloat = 1.6
var gravity: CGFloat
// RIGHT
static let gravityEarth: CGFloat = 9.8
static let gravityMoon: CGFloat = 1.6
var gravity: CGFloat
(链接) 移除未使用的 private 和 fileprivate 属性、函数和类型别名。
提高可读性,因为这些代码没有作用,应该为了清晰起见而删除。
// WRONG: Includes private declarations that are unused
struct Planet {
var ageInBillionYears: Double {
ageInMillionYears / 1000
}
private var ageInMillionsOfYears: Double
private typealias Dependencies = UniverseBuilderProviding // unused
private var mass: Double // unused
private func distance(to: Planet) { } // unused
}
// RIGHT
struct Planet {
var ageInBillionsOfYears: Double {
ageInMillionYears / 1000
}
private var ageInMillionYears: Double
}
(链接) 移除不定义任何属性、函数或一致性的空扩展。
(链接) 优先选择纯 Swift 类,而不是 NSObject 的子类。 如果您的代码需要被某些 Objective-C 代码使用,请将其包装以暴露所需的功能。 仅在必要时在单个方法和变量上使用 @objc
,而不是通过 @objcMembers
将类上的所有 API 暴露给 Objective-C。
class PriceBreakdownViewController {
private let acceptButton = UIButton()
private func setUpAcceptButton() {
acceptButton.addTarget(
self,
action: #selector(didTapAcceptButton),
forControlEvents: .touchUpInside)
}
@objc
private func didTapAcceptButton() {
// ...
}
}
我们鼓励您 fork 此指南并更改规则以适应您团队的风格指南。 您可以在下面列出对风格指南的一些修改。 这允许您定期更新您的风格指南,而无需处理合并冲突。