directory
-
Source file basics
- The file name
- File coding
- White space characters
- Special escape sequences
- Invisible characters and modifiers
- String literals
- Invisible characters and modifiers
-
Source file format
- File comment
- [import statement](#id-import statement)
- Type, variable, and function declarations
- Overload declaration
- File comment
-
A common format
- Column limit
- Braces (Braces)
- A semicolon
- A line of
- Fold line
- Function declaration
- Type and extension declarations
- A function call
- Control flow statement
- Other expressions
- Horizontal whitespace character
- Horizontal alignment
- The vertical blank line
- parentheses
-
Formatting a specific structure
- Non-document comment
- Attributes (Properties)
- [Switch statement](#id-Switch statement)
- The enumeration
- End closure
- At the end of a comma
- Numeric literal
- Properties (Attributes)
-
named
- [Apple’s API Style Guidelines](#id-Apple’s API Style Guidelines)
- Naming conventions are not access control
- Identifier (Identifiers)
- Initializers
- [Static and Class Properties](#id-Static and Class Properties)
- [Global Constants](#id- Global Constants)
- Delegate Methods
-
Programming practice
- Compiler warning
- Initializers
- Attributes (Properties)
- [Types with Shorthand Names](#id-Types with Shorthand Names)
- [Optional Types](#id-Optional Types)
- Wrong type
- Force Unwrapping and Force Casts
- Implicitly Unwrapped Optionals (optional)
- Access permissions
- Nesting and Namespacing
- [Early Exit guard] #id- Early Exit guard
- [for-where loop](#id-for-where loop)
- [switch fallthrough](#id-switch fallthrough)
- Pattern Matching
- [id- Tuple Patterns](#id- Tuple Patterns)
- Number and string literals
- [Playground Literals](#id-Playground Literals)
- [arithmetic trap and overflow](#id- arithmetic trap and overflow)
- Define a new operator (Operators)
- [Overloading Existing Operators](#id- Overloading Existing Operators)
-
Documentation Comments
- A common format
- [#id- Sentence Summary](#id- Sentence Summary)
- [Parameter, Returns, Throws tag](# id-parameter, Returns, Throws tag)
- [Apple’s Markup Format](#id-Apple’s Markup Format)
- Where do I need documentation
Source file basics
The file name
All Swift source files end with an extension. Swift. In general, the file name accurately describes the entity contained in the file’s content. A file name is the type name of a file. You are advised to use the type name and protocol name to extend a file that meets the requirements of a protocol. Judge for yourself in more complex cases.
For example,
-
Contains a simple MyType file named mytype.swift
-
The name myType.swift contains a type MyType and its top-level helper function
-
A file that extends a protocol MyProtocol only for type MyType, named MyType+ myprotocol.swift
-
Additional extensions, inline types, or other types that can be named more generically for the type MyType should be prefixed with MyType+; Such as buy + Additions. Swift
-
Contains related declarations that cannot be covered by a type or namespace (e.g. a global set of mathematical functions) can be named descriptively; Such as Math. Swift
File coding
The source file is encoded in UTF-8.
White space characters
In addition to line breaks, Spaces (U+0020) are the only whitespace characters available. This means:
- All other whitespace characters present in strings and character literals must be represented by their respective escape sequences
- Do not use tabs for code alignment
Special escape sequences
For sequences with special escape (arbitrary characters \t, \n, \r, “, ‘, \, and \0), the sequence is used instead of the Equivalent Unicode (for example, \u{000a}) escape sequence.
Invisible characters and modifiers
Invisible characters and invisible Modifiers Invisible characters, that is, 0-width characters and other control characters that do not affect the image output of the string, are always written as Unicode transfer sequences. Control characters, combination characters, and variation selectors (fe00-FE0F) that affect image output cannot be escaped when they modify strings or characters. If such a Unicode scalar appears isolated or does not modify other characters in the same string, it should be written as a Unicode transfer sequence. The following strings are formatted correctly because diacritics (¨) and dark skin tones (the sixth skin tone) modify adjacent characters
Let size = "Ubergro ße" ✅ let shrug = "🤷🏿️"Copy the code
In the following example, the diacritics and variant selectors are themselves, so they are escaped
Let skinToneType6 = "\u{1F3FF}" ✅ let skinToneType6 = "\u{1F3FF}"Copy the code
Otherwise, diacritics will affect readability of adjacent double quotes if literals are used. Even though most systems represent the skin color selector as a color block, the following example does not accept it because it is a modifier but cannot find the character being modified.
Let diaeresis = "̈" ⛔️ let skinToneType6 = "🏿"Copy the code
String literals
Unicode escape sequences (\u{???? }) can only be used with 7-bit ASCII characters, and cannot be used in the same string as other Unicode literal points (e.g. U). In particular, string literals can only be one of the following • Only pure literal characters and/or single-character escapes (\t, not \u{???? • Consists of only 7-bit ASCII plus any number of Unicode escape sequences and/or other escape sequences
For example, the first two lines are correct because \n can be both; But the second is not recommended because it is difficult to identify. The third line is disallowed because the first character is a non-7-bit ASCII literal.
//✅ let size = "\u{00DC}bergr\u{00F6}\u{00DF}e\n" //✅ //wrong let size = "Ubergro ße\n" //✅ let size = "\u{00DC}bergr\u{00F6}\u{00DF}e\n" //✅ //wrong let size = Ubergr \ u {00 f6} \ "u \ {00 df} e n" / / ⛔ ️Copy the code
Don’t worry that some programs won’t be able to handle non-ASCII encodings and reduce code readability (write all Unicode escape sequences?). . If that happens, it’s the programs that go wrong that are the problem, and they should be fixed.
Source file format
File comment
Comments describing the contents of the file are optional. If the file contains only one abstraction (i.e. only one class declaration), it is recommended not to annotate the file. Because abstracting its own comments is sufficient, file comments are not recommended if they do not provide more information. File comments are primarily used to describe multiple abstractions as a whole.
The import declaration
A source file contains only the top-level modules it depends on; No more, no less. If a file relies on both Foundation and UIKit, explicitly import both. Don’t rely on implementation details such as some Apple frameworks transitively importing another Framework in their implementation. It is recommended to import the entire Module, not individual declarations or submodules
There are several reasons to avoid importing individual members:
1 No automated tools parse/handle the import process 2 Existing tools (such as Xcode's Migrator) see this as a boundary case that may not work properly 3 The prevailing opinion of the Swift community (based on official examples and community code) is to import the entire ModuleCopy the code
Allow individual declarations to be imported when importing a module as a whole would pollute the global namespace with top-level definitions (such as interface C). Deal with these situations yourself. Import SubModules are allowed in some cases. Such as UIKit. UIGestureRecognizerSubclass when subclassing UIGestureRecognizer is a must, but only the import UIKit is useless, Must explicitly import UIKit. UIGestureRecognizerSubclass.
Import declarations do not allow line folding
An import declaration is the first time a non-commented token appears in a source file. The testable testable testable testable testable testable testable modules are classified into three distinct categories: 1 non-testable module/submodule 2 Testable module declaration (class, enum, func, struct, etc) 3 @testable module
import CoreLocation import MyThirdPartyModule import SpriteKit import UIKit import func Darwin.C.isatty @testable import MyModuleUnderTestCopy the code
Type, variable, and function declarations
Generally speaking, a file contains only one top-level type, especially one with many declarations. There are exceptions to a file defining multiple related types, such as
- A class and its delegate protocol can be in the same file
- A type and its smaller auxiliary types can be placed in the same file. With Fileprivate, proper code isolation can be achieved.
The order of types, variables, and functions within a file, as well as the order of the members of those types, greatly affects readability. However, there is no simple prescription for this, and different documents, different types, may be used in different order. Importantly, when the maintainer is asked, some logic order can be described. For example, new methods shouldn’t just be added habitually to the end of the type, because that’s just in date order and doesn’t involve much logic. This will help readers and future maintainers (including yourself) provide group descriptions with “// MARK: Comments” when the logic of the order of members is determined. Xcode parses these tags as bookmarks (for example, // MARK: – is parsed as a dividing line).
class MovieRatingViewController: UITableViewController { // MARK: - View controller lifecycle methods override func viewDidLoad() { // ... } override func viewWillAppear(_ animated: Bool) { // ... } // MARK: - Movie rating manipulation methods @objc private func ratingStarWasTapped(_ sender: UIButton?) {/ /... } @objc private func criticReviewWasTapped(_ sender: UIButton?) {/ /... }}Copy the code
Overload declaration
If a type has multiple initialization functions or subscripts, or if there are multiple functions within a file/type with the same parameter name and different type, and these overrides occur within the same type or exension, they should occur sequentially and without additional code.
Extension (Extension)
Extensions can be used to give type organization functionality across multiple units. Similar to membership order, organized structure/grouping greatly affects readability; When a Reviewer asks, you should be able to explain the logical structure to follow.
A common format
Column limit
Normal lines are 100 characters, unless the following exceptions are noted. The exceptions are: 1 breaking meaningful and important content (such as a long link in a comment) by complying with the word limit; 2 import statements; 3 code generated by other tools
Braces (Braces)
In general, braces follow the Kernighan and Ritchie(K&R) style of non-empty code blocks, while containing the following Swift exceptions:
- An opening brace (” {“) does not appear at the beginning of a line unless the folding rules require it
- An open parenthesis appears as a line end, unless
- In closures, the closure’s signature is placed on the same line as the opening parenthesis, with the line break following the in keyword
- When it may be omitted (see one line)
- An empty code block can be written as {}
- A closing brace (“} “) appears as the beginning of a line, unless it can be omitted (see a single line) or it is the end of an empty code block
- The closing parenthesis appears as the end of a line if and only if it is the end of a statement or declaration. For example, else code blocks can be written as “} else {“, and parentheses can be placed on the same line
A semicolon
Do not use semicolons, either as end-of-line or split statements. In other words, semicolons can only appear in string literals or comments. Correct examples:
func printSum(_ a: Int, _ b: Int) {
let sum = a + b
print(sum)
}
Copy the code
Wrong example:
func printSum(_ a: Int, _ b: Int) { let sum = a + b; / / ⛔ ️ print (sum); / / ⛔ ️}Copy the code
A line of
A line contains at most one statement, each statement followed by a line break character. Unless the line ends with a block that contains 0 or 1 statements.
guard let value = value else { return 0 } defer { file.close() } switch someEnum { case .first: return 5 case .second: return 10 case .third: return 20 } let squares = numbers.map { $0 * $0 } var someProperty: Int { get { return otherObject.property } set { otherObject.property = newValue } } var someProperty: Int { return otherObject.somethingElse() } required init? (coder aDecoder: NSCoder) { fatalError("no coder") }Copy the code
A single block of content in a single line is always allowed. Whether the conditional statement and the body are on the same line is up to your discretion in practice. For example, early-return and basic cleanup are best written on the same line; However, this may not be true if the content contains function calls with important logic. If in doubt, write in multi-line format.
Fold line
Line-wrapping is the practice of splitting a legitimate Line of code into multiple lines
For the purposes of Google Swift Style, many declarations (such as type and function declarations) and other expressions (such as function calls) can be broken down into breakable units, They are separated by unbreakable delimiting token sequences. The following function declaration, for example, is too long to break lines:
public func index<Elements: Collection, Element>(of element: Element, in collection: Elements) -> Elements.Index? where Elements.Element == Element, Element: Equatable {
// ...
}
Copy the code
It can be divided into the following sections: 1,3,5,7 are indivisible tokens, and 2,4,6,8 can be separated.
public func index< //1
Elements: Collection, Element //2
>( //3
of element: Element, in collection: Elements //4
) -> //5
Elements.Index? //6
where //7
Elements.Element == Element, Element: Equatable //8
{
// ...
}
Copy the code
1. Sequence of indivisible tokens from the left to the left Angle bracket of the generic argument list 2. Separable generic argument list 3. Sequence of indivisible markers (>() separates generic arguments from function formal arguments 4. 5. Sequence of indivisible tokens from right parenthesis (")") to arrow (->), followed by return type 6. Separable return type 7. Indivisible WHERE keyword followed by generic restriction list 8. A comma-separated, divisible list of generic restrictionsCopy the code
Using the above concepts, the main rule for folding lines in Google Swift Style is
1. Do not break lines if the complete declaration, statement, or expression fits on a single line. Comma-separated lists are placed either horizontally or vertically. That is, either all elements are in the same row, or each element is on a row. Horizontal lists do not contain line breaks, even on the left and right sides of the outside. The exception is conditional branch statements, where vertically placed lists are broken before the first element and before each subsequent element. 3. Indent the middle line starting with an indivisible tag sequence, consistent with the original line 4. A comma-separated middle line placed vertically, indented by 2 Spaces relative to the original line. When an open curly brace ({) follows a folded line declaration or expression, it is on the same line as the last consecutive line, unless the line has been indented by 2 Spaces from the original line. In this case, the braces are placed on a single line to prevent successive lines from being visually mixed up with subsequent blocks of code.Copy the code
Good example
public func index<Elements: Collection, Element>( of element: Element, in collection: Elements ) -> Elements.Index? where Elements.Element == Element, Element: Equatable { // GOOD. for current in elements { // ... }}Copy the code
Bad example
public func index<Elements: Collection, Element>( of element: Element, in collection: Elements ) -> Elements.Index? where Elements.Element == Element, Element: Equatable { // AVOID. for current in elements { // ... }}Copy the code
Add the following rule for declarations containing generic restrictions with WHERE clauses:
1. If a generic restricted list of return types would exceed the character limit on the same line, insert a newline character before the where keyword while maintaining the same indentation level as the original line. 2. If the limit is still exceeded after a newline is inserted, add a newline after where, place the limit list vertically, and add a newline after the last limit.Copy the code
Specific examples will be provided in the relevant sections below. This line folding style ensures that different parts of the declaration can be quickly and easily identified by indentation and line breaking, while preserving the same indentation level for those parts of the file. In particular, it avoids the sawtooth effect of indentation of arguments based on opening parentheses, which is common in other languages:
public func index<Elements: Collection, Element>(of element: Element, // AVOID.
in collection: Elements) -> Elements.Index?
where Elements.Element == Element, Element: Equatable {
doSomething()
}
Copy the code
Function declaration
modifiers func name(formal arguments){
modifiers func name(formal arguments) ->result{
modifiers func name<generic arguments>(formal arguments) throws ->result{
modifiers func name<generic arguments>(formal arguments) throws ->result where generic constraints{
Copy the code
Applying the above rule from left to right, we get
public func index<Elements: Collection, Element>( of element: Element, in collection: Elements ) -> Elements.Index? where Elements.Element == Element, Element: Equatable { for current in elements { // ... }}Copy the code
Function declarations in protocols that end with close parentheses (“)”) may place the closing parentheses on the same line as the last argument or on a separate line.
public protocol ContrivedExampleDelegate {
func contrivedExample(
_ contrivedExample: ContrivedExample,
willDoSomethingTo someValue: SomeValue)
}
public protocol ContrivedExampleDelegate {
func contrivedExample(
_ contrivedExample: ContrivedExample,
willDoSomethingTo someValue: SomeValue
)
}
Copy the code
If the type is too complex and/or deeply nested, the argument list and restriction list and individual elements in the return type can also be folded. In extreme cases, the same line folding rules that apply to declarations also apply to these sections
public func performanceTrackingIndex<Elements: Collection, Element>( of element: Element, in collection: Elements ) -> ( Element.Index? , PerformanceTrackingIndexStatistics.Timings, PerformanceTrackingIndexStatistics.SpaceUsed ) { // ... }Copy the code
However, where possible, TypeAlias or some other method is a better way to simplify complex declarations.
Type and Extension Declarations (Type and Extension Declarations)
The following examples also apply to class, struct, enum, extension, and Protocol (the obvious exception is that only class has superclasses in the inheritance list, others do not. They are otherwise structurally similar.)
modifiers class Name{
modifiers class Name:superclass and protocols{
modifiers class Name<generic arguments>:superclass and protocols{
modifiers class Name<generic arguments>:superclass and protocolswheregeneric constraints{
Copy the code
class MyClass:
MySuperclass,
MyProtocol,
SomeoneElsesProtocol,
SomeFrameworkProtocol
{
// ...
}
class MyContainer<Element>:
MyContainerSuperclass,
MyContainerProtocol,
SomeoneElsesContainerProtocol,
SomeFrameworkContainerProtocol
{
// ...
}
class MyContainer<BaseCollection>:
MyContainerSuperclass,
MyContainerProtocol,
SomeoneElsesContainerProtocol,
SomeFrameworkContainerProtocol
where BaseCollection: Collection {
// ...
}
class MyContainer<BaseCollection>:
MyContainerSuperclass,
MyContainerProtocol,
SomeoneElsesContainerProtocol,
SomeFrameworkContainerProtocol
where
BaseCollection: Collection,
BaseCollection.Element: Equatable,
BaseCollection.Element: SomeOtherProtocolOnlyUsedToForceLineWrapping
{
// ...
}
Copy the code
Function Calls
If a function call breaks a line, each argument is on a single line, indented 2 Spaces from the original line. As with function declarations, if the function call ends in a close parenthesis (“)”) (that is, the last parameter is not a trailing closure), the parenthesis can be placed on the same line as the last parameter, or it can be a separate line.
let index = index(
of: veryLongElementVariableName,
in: aCollectionOfElementsThatAlsoHappensToHaveALongName)
let index = index(
of: veryLongElementVariableName,
in: aCollectionOfElementsThatAlsoHappensToHaveALongName
)
Copy the code
If the function is trailing closure and the closure’s signature must be folded, then the signature should be folded on a separate line and (if necessary) the argument list in parentheses to separate it from the implementation of the following closure.
someAsynchronousAction.execute(withDelay: howManySeconds, context: actionContext) {
(context, completion) in
doSomething(withContext: context)
completion()
}
Copy the code
Control Flow Statements
When a control-flow statement (if, while, guard, for) is folded, the first concatenated line is indented to the code (token) position immediately following the control key. If subsequent lines are syntactically at the same level, align the second line; Otherwise, indent each level by 2 Spaces according to the syntax level. The opening curly brace (“{“) before the body of the control flow statement can either be at the end of the last consecutive line preceding it or on another line with the same indentation as the beginning of the statement. The “else {” in guard statements must be on the same line and cannot be broken.
if aBooleanValueReturnedByAVeryLongOptionalThing() &&
aDifferentBooleanValueReturnedByAVeryLongOptionalThing() &&
yetAnotherBooleanValueThatContributesToTheWrapping() {
doSomething()
}
if aBooleanValueReturnedByAVeryLongOptionalThing() &&
aDifferentBooleanValueReturnedByAVeryLongOptionalThing() &&
yetAnotherBooleanValueThatContributesToTheWrapping()
{
doSomething()
}
if let value = aValueReturnedByAVeryLongOptionalThing(),
let value2 = aDifferentValueReturnedByAVeryLongOptionalThing() {
doSomething()
}
if let value = aValueReturnedByAVeryLongOptionalThing(),
let value2 = aDifferentValueReturnedByAVeryLongOptionalThingThatForcesTheBraceToBeWrapped()
{
doSomething()
}
guard let value = aValueReturnedByAVeryLongOptionalThing(),
let value2 = aDifferentValueReturnedByAVeryLongOptionalThing() else {
doSomething()
}
guard let value = aValueReturnedByAVeryLongOptionalThing(),
let value2 = aDifferentValueReturnedByAVeryLongOptionalThing()
else {
doSomething()
}
for element in collection
where element.happensToHaveAVeryLongPropertyNameThatYouNeedToCheck {
doSomething()
}
Copy the code
Other expressions
When the expression where the line folding occurs is not a function call statement (see the rule above), the second line (the line immediately following the first line break) is indented by 2 Spaces relative to the original line. If there is more than one fold, the indentation can be inconsistent, increasing by 2 Spaces at a time. Generally, two subsequent lines indent identically if and only if they begin with syntactically parallel elements. However, if a long expression folds into a large number of consecutive lines, it is recommended that intermediate temporary variables be introduced to simplify the break.
✅ let result = anExpression + thatIsMadeUpOf * aLargeNumber + ofTerms/andTherefore % mustBeWrapped + (andWeWill - keepMakingItLonger * soThatWeHave / aContrivedExample)Copy the code
⛔️ let result = anExpression + thatIsMadeUpOf * aLargeNumber + ofTerms/andTherefore % mustBeWrapped + (andWeWill - keepMakingItLonger * soThatWeHave / aContrivedExample)Copy the code
Horizontal Whitespace
Term: Horizontal whitespace characters in this section refer exclusively to internal Spaces. These rules have nothing to do with adding or subtracting whitespace at the beginning of a line.
Except as required by language or other style rules, text and comments, a single space may appear only in the following cases:
- If conditional branch keywords (if, guard, while, switch) are immediately followed by open parentheses (“(“), separate them with Spaces
If (x = = 0 && y = = 0) | | z = = 0 {/ / ✅ / /... } the if (x = = 0 && y = = 0) | | z = = 0 {/ / ⛔ ️ / /... }Copy the code
- Not before the closing brace (“}”) at the beginning of the line, before the opening brace (“{“), and after the opening brace if there is code to the right
//✅ let nonNegativeCubes = numbers. Map {$0 * $0 * $0}. Filter {$0 >= 0} //✅ let nonNegativeCubes = numbers * $0}. Filter {$0 >= 0} //⛔️ let nonNegativeCubes = numbers. Map {$0 * $0 * $0}. Filter {$0 >= 0}Copy the code
-
Spaces are required around binary or ternary operators (including operator-like symbols described below). The exceptions are explained at the end.
-
The assignment operator (=), used for assignment, initialization of variables and attributes, and default values for function arguments
Var x = 5 //✅Copy the code
func sum(_ numbers: [Int], initialValue: Int = 0) { // … }
Var x=5 //⛔️ func sum(_ numbers: [Int], initialValue: Int=0) {//… }
- ampersand is used for protocol combinationsCopy the code
func sayHappyBirthday(to person: NameProviding & AgeProviding) { // … }
Copy the code
func sayHappyBirthday(to person: NameProviding&AgeProviding) {
// ...
}
Copy the code
- Declare and implement an operator
Static func == (LHS: MyType, RHS: MyType) -> Bool {//✅ //... } the static func = = (LHS: buy, RHS: buy) - > Bool {/ / ⛔ ️ / /... }Copy the code
-
Arrow in front of function return type (->)
Copy the code
Func sum(_ numbers: [Int]) -> Int {//✅ //… }
Func sum(_ numbers: [Int])->Int {//⛔️ //… }
- Exception: dot operator (.) References to values or type members do not require SpacesCopy the code
Let width = view.bounds.width //✅Copy the code
I let width = view.bounds. width //⛔️
-
Exception: range operator (“.. < “and”…” On both sides)
for number in 1... 5 {//✅ //... } let substring = string[index..<string.endIndex] for number in 1 ... 5 {//⛔️ //... }Copy the code
let substring = string[index ..< string.endIndex] “`
-
You don’t need to start a comma (,) before it, but you do need space after it, especially for tuple/array/dictionary literals.
Copy the code
Let numbers = [1, 2, 3] //✅ ‘ ‘
` ` `Copy the code
⛔️ let numbers = [1,2,3] let numbers = [1,2,3] ‘ ‘
- You don’t need a semicolon (:) before it, but you need a space after it:
- Superclasses and Protocol conformance lists and generic constraints
Struct HashTable: Collection {//✅ //...Copy the code
}
struct AnyEquatable<Wrapped: Equatable>: Equatable { // … } ` ` `
Struct HashTable: Collection {//⛔️ //... } struct AnyEquatable<Wrapped : Equatable> : Equatable { // ... }Copy the code
-
Function parameter tag and element tag of tuple
Copy the code
(x: Int, y: Int) //✅
func sum(_ numbers: [Int]) { // ... } ` ` ` ` ` ` let tuple: (x: Int, y: Int) / / ⛔ ️ let tuple: (x: Int, y: Int) func sum (_ Numbers: [Int]) {/ /... } func sum(_ numbers : [Int]) { // ... } ' '- variable and attribute display type declaration' 'let number:Int = 5 //✅ let number:Int = 5 //⛔️ let number: Var nameAgeMap: [String: Int] = [] //✅ var nameAgeMap: [] [String:Int] = [] //⛔️ var nameAgeMap: [String:Int] = [] - dictionary let nameAgeMap = ["Ed": 40, "Timmy" : 9] / / ✅ let nameAgeMap = [40, "Ed" : "Timmy" : 9] / / ⛔ ️ let nameAgeMap = [40, "Ed" : "Timmy" : 9] ` ` `Copy the code
-
A single-line comment (//) at the end of a line, preceded by at least two Spaces and followed by one
//✅ 1 let initialFactor = 2 // Warm up the modulator. //⛔️Copy the code
-
Outside the square brackets (“[]”) of array and dictionary literals, and outside the parentheses of tuple literals. You don’t need it inside.
Let numbers = [1, 2, 3] //✅Copy the code
Let numbers = [1, 2, 3] //⛔️
Horizontal Alignment
Horizontal alignment refers to the practice of adding extra white space characters to make certain tokens appear directly below certain tokens on the top line.
Horizontal alignment is forbidden, except when writing obvious tabular data, where ignoring alignment compromises readability. In other cases (for example, when arranging the declared type of a class or stored property of a struct), horizontal alignment is an invitation to maintenance problems: introducing a new member may require realigning all other members.
Struct DataPoint {//✅ var value: Int var primaryColor: UIColor} struct DataPoint {//⛔️ var value: Int var primaryColor: UIColor }Copy the code
Vertical Whitespace
A single blank line occurs when:
- Between continuous members of a type, including properties, initializers, methods, enum cases, and embedded types, excluding:
1. Empty lines between two storage properties or two enumerated instances are optional, as long as their declaration fits on a single line. This space-time row is used to create logic groupings. 2. Blank lines between two highly related attributes are optional, otherwise they do not meet the above criteria. For example, a private Store Property and the associated public computed PropertyCopy the code
- Statements are used only to divide code into logic subsections.
- Before and after the first member of a type is optional (not recommended or encouraged).
- As explicitly required elsewhere in this document.
Multiple blank lines are allowed, but not required (and discouraged). If you use consecutive blank lines, be consistent in your code base.
Parentheses (Parentheses)
The expressions immediately following the keywords if, guard, switch, while do not require parentheses.
If x = = 0 {/ / ✅ print (" x is zero ")} the if (x = = 0 | | y = = 1) && z = = {print 2 ("..." )}Copy the code
If (x = = 0) {/ / ⛔ ️ print (" x is zero ")} the if ((x = = 0 | | y = = 1) && z = = 2) {print ("..." )}Copy the code
Optional grouping parentheses should be omitted only if both the author and reviewer agree that there will be no misunderstanding and that adding parentheses will not contribute to readability. It is unreasonable to require everyone to remember the complete Swift operator priority table.
Formatting Specific Constructs
Non-document comment
Non-document comments use C++ style single-line comments (//), not C style multi-line comments (/*… * /)
Attributes (Properties)
Local variables should be as close as possible to the declaration before first use (within a reasonable range) to reduce the scope.
Except for tuple destructuring, each let/var can only declare one constant/variable (either an attribute or a local variable)
Var a = 5 //✅ var b = 10 let (Quotient, remainder) = divide(100, 9)Copy the code
Var a = 5, b = 10 //⛔️Copy the code
A Switch statement
The indentation of case statements is the same as that of switch statements. The code block inside the case is indented by 2 Spaces.
Button order {//✅ case. descending: print(" ascending ") case. descending: print(" descending ") case. same: print(" same ")}Copy the code
Switch order {//⛔️ case. ascending: print(" ascending ") case. descending: print(" descending ") case. same: print("Same") }Copy the code
Switch order {//⛔️ case. ascending: print(" ascending ") case. descending: print(" descending ") case. same: print("Same") }Copy the code
Enumeration (Enum Cases)
Generally, there is only one case per row in an enum. When all cases have no associated value or original value, they can be placed on the same line, separated by commas, and no further documentation is required because the name is complete.
Public enum Token {//✅ case comma case semicolon case identifier} Public enum Token {case comma, semicolon, identifier } public enum Token { case comma case semicolon case identifier(String) }Copy the code
Public enum Token {//⛔️ case comma, semicolon, identifier(String)}Copy the code
When all cases are indirect, the indirect keyword can be promoted forward to the enum declaration and omitted from the individual case.
Public Indirect enum DependencyGraphNode {//✅ case userDefined(dependencies: [DependencyGraphNode]) case synthesized(dependencies: [DependencyGraphNode]) }Copy the code
Public enum DependencyGraphNode {//⛔️ indirect case userDefined(dependencies: [DependencyGraphNode]) indirect case synthesized(dependencies: [DependencyGraphNode]) }Copy the code
Do not add empty parentheses when there is no associated value in a case
Public enum BinaryTree<Element> {//✅ indirect Case node(Element: Element, left: BinaryTree, right: BinaryTree) case empty // GOOD. }Copy the code
Public enum BinaryTree<Element> {//⛔️ Indirect Case node(Element: Element, left: BinaryTree, right: BinaryTree) case empty() // AVOID. }Copy the code
When asked, authors should be able to state the logical order in which cases in the enum are arranged. If not, you are advised to follow the dictionary order of the names. In the following example, cases are grouped with blank lines in the order of the numbers in the HTTP status code.
public enum HTTPStatus: Int {//✅ case OK = 200 case badRequest = 400 case notAuthorized = 401 case paymentRequired = 402 case Forbidden = 403 case notFound = 404 case internalServerError = 500 }Copy the code
The following example is a little less readable. Although sorted lexicographically, the meaningful grouping information associated with the values is lost.
public enum HTTPStatus: Int {//⛔️ case badRequest = 400 case Forbidden = 403 case internalServerError = 500 case notAuthorized = 401 case notFound = 404 case ok = 200 case paymentRequired = 402 }Copy the code
Trailing Closures
Overloading is not recommended: the only difference between the two functions is the label of the trailing closure argument. Tail closure syntax is not recommended in this case — you can’t tell which function is being called by omitting the tag. This prevents the greet function from being called with the tail closure syntax:
//⛔️ func greet(enthusiastically: () -> String) {print("Hello, \(nameProvider())! It's a pleasure to see you!" ) } func greet(apathetically nameProvider: () -> String) { print("Oh, look. It's \(nameProvider()).") } greet { "John" } // error: ambiguous use of 'greet'Copy the code
The fix for this example is to distinguish between two functions by modifying the part of the function signature other than the closure argument — in this case, modifying the base name:
Mean past good with (_ nameProvider: () -> String) {print("Hello, \(nameProvider()))! It's a pleasure to see you!" ) } func greetApathetically(_ nameProvider: () -> String) { print("Oh, look. It's \(nameProvider()).") } greetEnthusiastically { "John" } greetApathetically { "not John" }Copy the code
If a function call contains multiple closure arguments, none of them use tail closure syntax; All use tags and are nested within parentheses in the argument list.
Uiview.animate (//✅ withDuration: 0.5, animations: {//... }, completion: { finished in // ... })Copy the code
Uiview.animate (//⛔️ withDuration: 0.5, animations: {//... }) { finished in // ... }Copy the code
If a function has only one closure argument, and that argument is the last one, it will always be called with tail-closure syntax, except to avoid ambiguity or parsing errors: 1 As described earlier, the function signature is identical except for the argument label; In this case, parameter labels must be written to avoid ambiguity. 2 When using the tail closure format, the body of the closure can be resolved to the body of the control flow statement. In this case, you must revert to the label parameter form.
//✅ timer.scheduledtimer (timeInterval: 30, repeats: false) {Timer in print("Timer done!") ) } if let firstActive = list.first(where: { $0.isActive }) { process(firstActive) }Copy the code
//⛔️ timer.scheduledtimer (timeInterval: 30, repeats: false, block: {Timer in print("Timer done!") ) }) // This example fails to compile. if let firstActive = list.first { $0.isActive } { process(firstActive) }Copy the code
When a function call uses a tail-closure syntax that contains no additional arguments, the empty parenthesis pairs (“()”) after the function name are omitted.
Let squares = [1, 2, 3].map {$0 * $0} //✅Copy the code
Let squares = [1, 2, 3]. The map ($0} {$0 *) / / ⛔ ️ let squares = [1, 2, 3]. The map () {$0 * $0} / / ⛔ ️Copy the code
Trailing Commas
In array and Dictionary literals, trailing commas are mandatory when each element is ona line. Doing so makes it easier to add new elements to the tail later.
Let configurationKeys = [//✅ "bufferSize", "compression", "encoding", // GOOD.]Copy the code
Let configurationKeys = [//⛔️ "bufferSize", "compression", "encoding" // AVOID.]Copy the code
Numeric Literals
It is recommended (but not mandatory) that long numeric literals (decimal, hexadecimal, octal, binary) be separated by underscores (_) to improve readability when they are numeric or have field-specific grouping meanings. The recommended grouping is the decimal group of three digits. English habit, thousands, can consider to domestic, 4), hex, a group of four binary is a group of four or eight, or a specific value field boundary (if they exist, such as Unix file permissions are three 8 hexadecimal number to describe) if literal as opaque identifier shall have no effect on the meaning of value, Do not group numbers.
Properties (Attributes)
Parameterized properties (such as @Availability (…)) Or @ objc (…). ), each on a separate line, in lexicographical order, with indentation consistent with the declaration.
@available(iOS 9.0, *) //✅ public func coolNewFeature() {//... }Copy the code
@available(iOS 9.0, *) public func coolNewFeature() {//⛔️ //... }Copy the code
Properties that take no arguments (such as the individual @objc, @ibOutlet, @nsmanaged) are in lexicographical order and can be placed on the same line as declarations (if and only if a line fits without causing line breaking). If adding an attribute to the declaration line triggers the line breaking of a previous declaration that did not require line breaking, that attribute can be on a single line.
Public class MyViewController: UIViewController {//✅ @ibOutlet private var tableView: UITableView! }Copy the code
Name (Naming)
Apple ‘s API Style Guidelines
Apple’s official Swift naming and API design guidelines are considered part of this style guide. The contents need to be followed, as we have repeated here.
Naming conventions are not access control
Restricted access controls (internal, private, fileprivate) are for hiding information from customers, not for naming conventions. Naming conventions (such as using underscores as prefixes) are used only in rare cases where a declaration needs to give a higher level of visibility in order to overcome language limitations. For example, functions of a type that are designed to be called by other parts of the library that cross module boundaries must be declared public
Identifier (Identifiers)
In general, identifiers consist of only 7-bit ASCII characters. Unicode identifiers are also available if they have clear and reasonable meanings in the problem domain of the code base (for example, Greek letters can be used to express mathematical concepts) and are fully understood by the team that owns the code base.
Let smile = "😊" //✅ let deltaX = newx-previousx let δ x = newx-previousxCopy the code
Let 😊 = "😊" //⛔️Copy the code
Initializers
For clarity, the parameter names in the initializer function correspond directly to the stored property and are the same as the property names. Explicitly add “self.” to avoid ambiguity.
Public struct Person {//✅ public let name: String public let phoneNumber: String // GOOD. String, phoneNumber: String) { self.name = name self.phoneNumber = phoneNumber } }Copy the code
Public struct Person {//⛔️ public let name: String public let phoneNumber: String // AVOID. public init(name otherName: String, phoneNumber otherPhoneNumber: String) { name = otherName phoneNumber = otherPhoneNumber } }Copy the code
Static and Class Properties
Static and class attributes that return an instance of a type do not need to be suffixed with the type name.
Public class UIColor {//✅ public class var red: UIColor {// GOOD. //... } } public class URLSession { public class var shared: URLSession { // GOOD. // ... }}Copy the code
Public class UIColor {//⛔️ public class var redColor: UIColor {// AVOID... } } public class URLSession { public class var sharedSession: URLSession { // AVOID. // ... }}Copy the code
If static or thunder attributes are used to implement singleton patterns that return an instance of the type to which they belong, the name is recommended to be “shared” or “default.” This style guide is not mandatory; it is up to the author to choose the name that best fits the type.
Global Constants
Like other variables, global constants use lowerCamelCase, with a lower – case, hump format. Don’t use g or K Hungarian notation.
Let secondsPerMinute = 60 //✅Copy the code
Let SecondsPerMinute = 60 //⛔️ let kSecondsPerMinute = 60 let gSecondsPerMinute = 60 let SECONDS_PER_MINUTE = 60Copy the code
Delegate Methods
Inspired by Cocoa, delegate protocols and methods in similar delegate protocols (such as Data Sources, data sources) are named using the language syntax described below.
The term “delegate’s source object” refers to the object that invokes methods within the delegate. For example, a UITableView is the source object that calls the methods in the UITableViewDelegate (set to the Delegate property of the UITableView).
All methods take the source object as their first argument.
If the method takes only one argument from the source object:
-
If the method does not return a value (for example, some methods are simply used to inform the delegate object that an event has occurred), then the base name of the method consists of the type of the delegate’s source object followed by an indicative phrasal verb describing the event. Parameters do not need unlabeled.
Func scrollViewDidBeginScrolling (_ scrollView: UIScrollView) / / ✅Copy the code
-
If the method returns a Bool (such as code asserting a delegate’s source object), the method name consists of the type of the delegate’s source object followed by an indicative or conditional phrasal verb describing the assertion. Parameters do not need labels.
Func scrollViewShouldScrollToTop (_ scrollView: UIScrollView) - > Bool / / ✅Copy the code
-
If the method returns other values (such as attribute information for the source object of the query delegate), the method name is a nominal phrase describing the query. The label of a method parameter can be a preposition, or a phrase followed by a preposition (which appropriately combines the noun phrase with the delegate’s source object)
Func numberOfSections(in scrollView: UIScrollView) -> Int //✅Copy the code
If the method needs additional arguments, the method name is the delegate source object’s own type, and the first argument does not need a label:
-
If the method returns no value, the label of the second argument to the method is an indicative phrasal verb that describes the event. The parameter object constitutes the immediate object or preposition object of the event. Additional parameters, if present, provide further context.
Func tableView(//✅ _ tableView: UITableView, willDisplayCell: UITableViewCell, forRowAt indexPath: indexPath)Copy the code
-
If the method returns a Bool, the label for the second parameter consists of an indicative or conditional phrasal verb that describes the return value as an argument. Additional parameters, if present, provide further context.
Func tableView(//✅ _ tableView: UITableView, shouldSpringLoadRowAt indexPath: indexPath, with context: UISpringLoadedInteractionContext ) -> BoolCopy the code
-
If the method returns another value, the second parameter is labeled with a noun phrase and ends with a preposition, and it describes the returned value with an argument. Additional parameters, if present, provide further context.
Func tableView(//✅ _ tableView: UITableView, heightForRowAt indexPath: indexPath) -> CGFloatCopy the code
The Apple documentation on delegates and data sources also contains some good general guidance on these names.
Programming practice
Common themes in the rules in this section are: avoid redundancy, avoid ambiguity, and implicit is better than explicit unless explicit improves readability and/or reduces ambiguity.
Compiler warning
Eliminate all warnings if possible. Warnings that are easy to resolve must be addressed.
A reasonable exception is deprecation warnings when migration to an alternative API is not possible immediately, or when an API is deprecated for external users but must be supported in the library during the deprecation period.
Initializers
For structs, Swift synthesizes a non-public, member-initialization function, init, that provides arguments for the declared var attribute and for the let attribute, which has no default value. When the initializer is appropriate (that is, public is not required), it is used and no explicit initializer is written.
Initialization functions declared by the special ExpressibleBy*Literal compiler protocol cannot be called directly.
Struct Kilometers: ExpressibleByIntegerLiteral {/ / ✅ init (integerLiteral value: Int) {/ /... } } let k1: Kilometers = 10 // GOOD. let k2 = 10 as Kilometers // ALSO GOOD. struct Kilometers: ExpressibleByIntegerLiteral {/ / ⛔ ️ init (integerLiteral value: Int) {/ /... } } let k = Kilometers(integerLiteral: 10) // AVOID.Copy the code
Explicit calls to.init(…) are allowed only if the receiver of the call is a metatype. . When an initializer is called indirectly with a literal type name,.init is omitted. (It is permissible to convert an initializer directly to a closure using the mytype.init syntax.)
Let x = MyType(arguments) //✅ let type = lookupType(context) let x = type.init(arguments) let x = makeValue(factory: MyType.init)Copy the code
Let x = mytype.init (arguments) //⛔️Copy the code
Attributes (Properties)
The GET block in read-only computed Property is omitted, and its body is embedded directly in the property declaration.
Var totalCost: Int {//✅ return items.sum {$0.cost}}Copy the code
Var totalCost: Int {//⛔️ get {return items. Sum {$0.cost}}}Copy the code
Types with Shorthand Names
Array, dictionary, and optional are always written as abbreviations; [Element], [Key: Value], Wrapped? . The long formats Array, Dictionary<Key, Value>, Optional are required only if the compiler requires it. For example, the Swift parser requires array. Index and does not accept [Element].index.
Func enumeratedDictionary<Element>(//✅ from values: [Element], start: Array<Element>. = nil ) -> [Int: Element] { // ... }Copy the code
Func enumeratedDictionary<Element>(// ️ from values: Array<Element>, start: Optional<Array<Element>.Index> = nil ) -> Dictionary<Int, Element> { // ... }Copy the code
Void is the typeAlias of a blank tuple (), and from an implementation point of view they are the same thing. In function type declarations (closures that hold variables referenced by the function), the return type is always Void instead of (). In func declarations, Void is completely omitted as the return type.
Func doSomething() {//✅ //... } let callback: () -> VoidCopy the code
Func doSomething() -> Void {//⛔️ //... } func doSomething2() -> () { // ... } let callback: () -> ()Copy the code
Optional Types
When designing the algorithm, Sentinel Values should be avoided (e.g., return index -1 if no results are found). Because the type system cannot distinguish between alert values and valid values, alert values are prone to errors and propagate through other logical layers. Optional is used to pass error-free results that are either a value or nonexistent. For example, an empty search result is a valid and expected result, not an error.
func index(of thing: Thing, in things: [Thing]) -> Int? {/ / ✅ / /... } if let index = index(of: thing, in: lotsOfThings) { // Found it. } else { // Didn't find it. }Copy the code
Func index(of thing: thing, in things: [thing]) -> Int {//⛔️ //... } let index = index(of: thing, in: lotsOfThings) if index ! = -1 { // Found it. } else { // Didn't find it. }Copy the code
Optional is also used in error scenarios where a single obvious error occurs, such as an operation that may cause an error for a domain-specific reason that the client can understand. (Domain-specific limitations mean that serious errors, such as memory overflow errors, are excluded in most cases that the user cannot handle.) For example, converting a string to a number will fail if it does not represent a significant number that fits the bit-width.
Struct Int17 {//✅ init? (_ string: String) { // ... }}Copy the code
The conditional statement that tests whether Optional is empty but does not access the wrapped value is written as a comparison to nil. The following example clearly shows the programmer’s intent:
Struct Int17 {//✅ init? (_ string: String) { // ... }}Copy the code
Conditional statements that test that an Optional is non-nil but do not access the wrapped value are written as Word order to nil. The following example is clear about The programmer’s intent:
if value ! = nil {//✅ print("value was not nil")}Copy the code
The following example, which uses Swift’s patternmatching and binding syntax, confuses the intent by unpacking the value and then immediately discarding it.
If let _ = value {//⛔️ print("value was not nil")}Copy the code
Wrong type
Use error types when there are multiple possible error states. Throwing errors instead of including them in return types is an entirely different consideration in the API. Valid inputs and valid states generate valid outputs in the result field and are processed using standard sequential control flow. Invalid input and invalid states are treated as errors, and related syntactic structures (do-catch and try) are used. Such as:
Struct Document {//✅ enum ReadError: Error {case notFound case permissionDenied case malformedHeader} init(path: String) throws { // ... } } do { let document = try Document(path: "important.data") } catch Document.ReadError.notFound { // ... } catch Document.ReadError.permissionDenied { // ... } catch { // ... }Copy the code
This design forces the caller to acknowledge the error case consciously:
- Tuck the calling code into a do-catch block and process the error case to an appropriate degree
- Declare the function whose calling code is indented as throws, passing the error, or
- If the cause of the error doesn’t matter and all you need to know is whether the call was successful, use try?
In general, with the exceptions mentioned below, mandatory try! Is forbidden; It is equivalent to a try followed by a fataError and has no meaningful information. If the occurrence of an error means that the program enters an irreversible state, and immediate termination is the most reasonable choice, do catch or try is better? And then provide more context in error messages to help debug.
Exception: mandatory try! Can be used for unit testing and testing code. Can be used for non-test code if it is clear that the error could only be thrown because of a programmer error; In particular, we define it as the ability to evaluate a single expression without context in the Swift REPL. For example, consider initializing a regular expression with a string literal:
let regex = try! NSRegularExpression(pattern: "a*b+c?" ) / / ✅Copy the code
NSRegularExpression’s initialization function throws an error if the regular expression format is wrong, but it is a string literal and the only way it can go wrong is if the programmer typed it wrong. There is no benefit in writing error handling logic here. If the schema entered above is not literal but dynamic or generated from user input, then try! Mistakes should be handled properly.
Force Unwrapping and Force Casts
Both are bad code smells and are strongly discouraged. Unless the surrounding code makes it very clear why such code is safe, you need a comment that describes the invariants that keep the code safe.
Let value = getSomeInteger() //✅ //... intervening code... // This force-unwrap is safe because `value` is guaranteed to fall within the // valid enum cases because it came from some data source that only permits // those raw values. return SomeEnum(rawValue: value)!Copy the code
Exception: Forced unpacking can be used in unit tests and test code without additional explanation. This removes unnecessary control flow from the code. A test failure is the expected result if nil is unpacked or the conversion fails due to incompatible types.
Implicitly Unwrapped Optionals (optional)
Implicit resolution of optional types is inherently unsafe and should be avoided as much as possible, opting for non-optional declarations or plain optional types instead. The exceptions are described in detail below.
IUO can be used by user interface objects whose lifetime is based on the lifecycle of the UI rather than strictly based on the lifetime of the holding object. Properties that are declared with @ibOutlet and connected to XIB files or storyboards that are externally initialized (like in the prepareForSegue implementation of the view Controller that’s called), And properties initialized elsewhere in the class lifecycle (such as views in viewDidLoad of the View Controller). Implementing these properties as normal Optional imposes an excessive burden on subsequent unpacking, because once they are ready, they will always be non-null.
Class SomeViewController: UIViewController {//✅ @iboutlet var button: UIButton! override func viewDidLoad() { populateLabel(for: button) } private func populateLabel(for button: UIButton) { // ... }}Copy the code
IUO can also occur when Swift calls objective-C apis without the appropriate Nullability attribute modifier. If possible, contact the author to add annotations so that the relevant API can more clearly import to Swift. If the author cannot be contacted, keep the IUO’s footprint as small as possible. That is, don’t propagate them through your own abstraction layer.
IUO can also be used in unit testing. Similar to the UI object scenario mentioned earlier, the life cycle of the test appliance usually starts not with an initialization function, but in the setUp() of the test, so that it can be reset before each test execution.
Access Levels
Access permissions can be omitted from the declaration. The default access for the top-level declaration is internal. For inline declarations, the default access is the more restrictive one between internal and the declaration enclosing it.
Disallow explicit marking of access permissions on file-level extensions. If each member of the extension does not have the same access rights as the default, please indicate one by one.
//✅ public var isUppercase: Bool {//... } public var isLowercase: Bool { // ... }}Copy the code
Public extension String {//⛔️ var isUppercase: Bool {//... } var isLowercase: Bool { // ... }}Copy the code
Nesting and Namespacing
Swift allows nesting of enums, structs, and classes, so nesting is a better choice (rather than a naming convention) when expressing ranges and hierarchies between types, if possible. For example, token enumerations and error types associated with a particular type are nested within that type.
Class Parser {//✅ enum Error: Swift.Error {case invalidToken(String) case unexpectedEOF} func parse(text: String) throws { // ... }}Copy the code
Class Parser {//⛔️ func parse(text: String) throws {//... } } enum ParseError: Error { case invalidToken(String) case unexpectedEOF }Copy the code
Swift currently does not allow protocols to be nested in other types and vice versa. So the above rules do not apply to scenarios involving protocols, such as a controller class and its delegate protocol.
Declaring an enum without a case is the standard way to define a “namespace” for a set of related declarations (such as constants or helper functions). This enum automatically has no instances, and no additional boilerplate code is required to prevent instantiation.
Enum Dimensions {//✅ static let tileMargin: CGFloat = 8 static let tilePadding: CGFloat = 4 static let tileContentSize: CGSize(width: 80, height: 64) }Copy the code
Struct Dimensions {//⛔️ private init() {} static let tileMargin: CGFloat = 8 static let tilePadding: CGFloat = 4 static let tileContentSize: CGSize(width: 80, height: 64) }Copy the code
“Early Exit” means Early Exit from the guard
The GUARD statement provides visual emphasis compared to the if statement of the opposite condition. The condition being tested is the special case that causes an early exit from the closed range.
In addition, guard statements improve readability by eliminating additional nested levels (“pyramid of doom”); The error conditions and the conditions that trigger them are close to each other, and the master logic remains flush within its scope.
This can be seen in the following example. There is a clear procedure to check for invalid status to exit, and then execute the master logic in the event of success. In the second example, where guard is not used, the main logic is buried at an arbitrary nesting level, throwing false statements far from their corresponding conditions.
func discombobulate(_ values: (Int)) throws - > Int {/ / ✅ guard let first = values. The first else {throw DiscombobulationError. ArrayWasEmpty} guard first >= 0 else { throw DiscombobulationError.negativeEnergy } var result = 0 for value in values { result += invertedCombobulatoryFactory(of: value) } return result }Copy the code
The guard-continue statement is also useful in loops where the entire body of the loop needs to be executed only under partial conditions to avoid increasing indentation (but also see for-WHERE below).
The for – where circulation
When the entire body of the for loop is surrounded by an if condition that tests each element, the test is placed in the WHERE clause following the for statement.
For item in collection where item.hasProperty {//✅ //... }Copy the code
For item in collection {//⛔️ if item.hasProperty {//... }}Copy the code
Fallthrough in the switch
When the Switch has multiple cases executing the same code, the case schema is synthesized into a range or comma-separated list. Multiple case branches that do nothing but fallthrough to the next case branch are not allowed.
Switch value {//✅ case 1: print("one") case 2... 4: print("two to four") case 5, 7: print("five or seven") default: break }Copy the code
Switch value {//⛔️ case 1: print("one") case 2: fallthrough case 3: fallthrough case 4: print("two to four") case 5: fallthrough case 7: print("five or seven") default: break }Copy the code
In other words, a case branch is not allowed to contain only fallthrough statements. Case branches containing additional statements and then fallthrough to subsequent case branches are allowed.
Pattern Matching
The keywords let and var are placed in front of each element to be matched. The short form of let/var distributed before and in the middle of the entire pattern is disallowed because it can lead to unexpected behavior if the value being matched in the pattern is itself a variable.
Enum DataPoint {//✅ case unlabeled(Int) case labeled(String, Int)} let label = "goodbye" It's thought of as a value, // DataPoint switch datapoint.labeled ("hello", 100) {case. Labeled (label, let value): / /... } // Writing 'let' before each individual binding makes it clear that the intention is to introduce a new binding (in this case, shadowing local variables) // instead of matching the values of local variables. // DataPoint switch datapoint.labeled ("hello", 100) {case. Labeled (let label, let value): //... }Copy the code
In the following example, if the author’s intention was to match the value of the label variable above, it would have been lost, because the let is distributed throughout the pattern, overwriting the meaning of taking the value of the variable with the meaning of the binding that matches any string value. (There is always ambiguity.)
Switch datapoint.labeled ("hello", 100) {//⛔️ case let.labeled (label, value): //... }Copy the code
Label and enum associated values of tuple parameters During binding, if the variable name of the bound value has the same name as the label, the label and the associated value can be omitted.
Enum BinaryTree<Element> {//✅ indirect Case subtree(left: BinaryTree<Element>, right: BinaryTree<Element>) case leaf(element: Element) } switch treeNode { case .subtree(let left, let right): // ... case .leaf(let element): // ... }Copy the code
Keeping labels at this point adds redundant and useless noise.
Switch treeNode {//⛔️ case. subtree(left: let left, right: let right): //... case .leaf(element: let element): // ... }Copy the code
### Tuple Patterns
Assign to a variable using multiple groups (sometimes called tuple shuffle) only if there is no unlabeled left side of the assignment operator.
Let (a, b) = (y: 4, x: 5.0) //✅Copy the code
Let (x: a, y: b) = (y: 4, x: 5.0) //⛔️Copy the code
The label on the left is very similar to the type annotation and can lead to misunderstanding.
//⛔️ // We declare two variables called 'Int', which are actually of type 'Double' with a value of 5.0; // 'x' and 'y' are not variables let (x: Int, y: Double) = (y: 4, x: 5.0)Copy the code
Number and string literals
Swift’s integers and strings have no inherent type. For example, 5 itself is not an Int; It is a special literal value that can be converted to satisfy
ExpressibleByIntegerLiteral of any type, if the type is derived (type inference) without further map it to a more specific type it is Int. Similarly, “x” is not a String, nor is it Character or UnicodeScalar, but it can be converted to one of three depending on the context, returning String by default.
Therefore, if a literal is used to initialize the value of a non-default type, and the type cannot be inferred from the context, specify the type explicitly in the declaration or enforce it with an AS expression.
//✅ // Without specifying the type more explicitly, x1 will be inferred as Int let x1 = 50 // explicitly specifying Int32 let x2: Int32 = 50 let x3 = 50 as Int32 // no more explicit type, y1 will be assumed to be String let y1 = "a" // explicit type let y2: Character = "a" let y3 = "a" as Character UnicodeScalar = "a" let y5 = "a" as UnicodeScalar func writeByte(_ byte: UInt8) { // ... // UInt8 writeByte(50) = writeByte(50)Copy the code
For example, if a number cannot be cast as an integer or a multi-byte string cannot be cast as a character, the compiler will give appropriate error messages for such invalid literal casts. So an error in the following example is “good” behavior, because the error was caught at compile time for the right reason.
/ / ✅ / / error: integer literal '9223372036854775808' overflows when stored into 'Int64' let a = 0x8000_0000_0000_0000 as Int64 // error: cannot convert value of type 'String' to type 'Character' in coercion let b = "ab" as CharacterCopy the code
Using initialization syntax to force these types can result in misleading compiler errors or, worse, run-time errors that are difficult to debug.
//⛔️ // The literal is converted to a signed 'Int' type and then to 'UInt64' // Although the literal can be directly converted to 'UInt64', the first conversion to // 'Int' fails. Let A1 = UInt64(0x8000_0000_0000_0000) String) ', // so at runtime (involving slow heap memory) a String 'a' is created // parses characters from it and then frees it. // This is obviously slower than a reasonable type coercion. Let b = Character("a") // Create 'String' and then call 'character.init (_: String)' // try to parse out a single Character from it. This invalidates prerequisite checks and creates runtime traps. let c = Character("ab")Copy the code
Playground Literals
Non-playground production code disallows graphically rendered playground literals, such as #colorLiteral(…) , #imageLiteral(…) , and # fileLiteral (…). . They are allowed in the playground code.
//✅ let color = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0) 1.0, Blue: 1.0, Alpha: 1.0) //⛔️Copy the code
Arithmetic traps and overflows
Standard (overflow trigger the trap) arithmetic and operators (+, -, *, < <, > >) used in the vast majority of normal operation, rather than mask (starting with &) operation. Trap overflows are safer because they prevent bad data from spreading out through other layers of the system. Trap is an interrupt that causes the program to exit. Think again!
//✅ let newBankBalance = oldBankBalance + recentHugeProfitCopy the code
// AVOID/ AVOID. If the increment is too large, the overflow results in a negative newBankBalance //⛔️ let newBankBalance = oldBankBalance &+ recentHugeProfitCopy the code
Mask operations are relatively rare, but are allowed (and actually necessary for correctness) in problem domains where modular operations are used (such as encryption algorithms, large integer implementations, hash functions, etc.).
Var hashValue: Int {//✅ / GOOD. Return foo.hashValue &+ 31 * (bar.hashValue &+ 31& * baz.hashValue)} return foo.hashValue &+ 31 * (bar.hashValue &+ 31& * baz.hashValue)}Copy the code
Var hashValue: Int {//⛔️ // Incorrect. Return foo.hashValue + 31 * (bar.hashValue + 31 * baz.hashvalue)} return foo.hashvalue + 31 * baz.hashvalue)}Copy the code
Mask operations are also allowed in performance-sensitive code, if you can be sure that the values are not overrunning (or that overrunning is not a problem). In this case, you should use comments to explain why it is important to use mask operations. Also, consider adding debugging preconditions to check these assumptions without affecting the performance of the optimized build.
Define a new operator (Operators)
When used unwisely, custom operators can significantly reduce code readability because they often lack the historical background of the more general operators compiled into the standard library.
In general, you should avoid custom operators. However, operators are allowed when they have a clear and unambiguous meaning in the problem domain and their use significantly improves the readability of the code compared to function calls. For example, since * is the only multiplication operator defined by Swift (excluding the mask version), the mathematical matrix library can define other operators to support other operations, such as cross and dot products.
An example of this is customizing the <~~ and ~~> operators to decode and encode JSON data. These operators are not native to the problem domain of working with JSON data, and even experienced Swift engineers cannot understand the purpose of the code without consulting the relevant documentation.
If you must use third-party code that provides apis through custom operators, and the value of that code is unquestionable, then it is highly recommended that you consider implementing a wrapper class that defines more readable methods, delegating methods to those custom operators. This significantly slows down the learning curve for new colleagues and other code reviewers.
Overloading Existing Operators
You can overload an operator when your use of it is semantically equivalent to its use in the standard library. Examples of permitted use are implementations that satisfy the operator requirements of Equatable and Hashable, or defining a new matrix type that supports arithmetic operations.
If you want to overload an operator to give it a new meaning other than its natural meaning, follow the instructions in defining the new operator to determine what is allowed. That is, if the new meaning has been established in the problem domain and the use of the operator improves readability compared to other syntactic constructs, then it is allowed.
An example of disallowing remeaning of operators is overloading * and + to set up a temporary regular expression API. This API does not provide a significant code readability benefit compared to simply representing the entire regular expression as a string.
Documentation Comments
A common format
Each line of a document comment begins with three slashes (///). Javadoc style comment blocks are not allowed (/**… * /)
//✅ // Returns the numeric value of the given digit;; performer as a Unicode scalar. /// /// - Parameters: /// - digit: The Unicode scalar whose numeric value should be returned. /// - radix: The radix, between 2 and 36, used to compute the numeric value. /// - Returns: The numeric value of the scalar. func numericValue(of digit: UnicodeScalar, radix: Int = 10) -> Int { // ... }Copy the code
//⛔️ /** * Returns the numeric value of the given digit; performers as a Unicode scalar. ** -parameters: * -digit: The Unicode scalar whose numeric value should be returned. * - radix: The radix, between 2 and 36, used to compute the numeric value. * - Returns: The numeric value of the scalar. */ func numericValue(of digit: UnicodeScalar, radix: Int = 10) -> Int { // ... } /** Returns the numeric value of the given digit represented as a Unicode scalar. - Parameters: - digit: The Unicode scalar whose numeric value should be returned. - radix: The radix, between 2 and 36, used to compute the numeric value. - Returns: The numeric value of the scalar. */ func numericValue(of digit: UnicodeScalar, radix: Int = 10) -> Int { // ... }Copy the code
Single-sentence Summary
Documentation comments begin with a short one-sentence summary describing the declaration. (This sentence can be broken down into multiple lines, but if it is broken down into too many lines, the author should consider whether the abstract can be further simplified and the details put in a new paragraph.)
If you need more detail than the summary description, you can add a new paragraph (separated by a blank line) at the end.
A one-sentence summary does not have to be a complete sentence; Method summaries, for example, are usually phrasal verbs that omit the subject (the method) because the subject is obvious and redundant. Similarly, an attribute summary is usually a noun phrase that omits the subject of the attribute. In all cases, however, they begin with a period (.). At the end.
/// The background color of the view.
var backgroundColor: UIColor
/// Returns the sum of the numbers in the given array.
///
/// - Parameter numbers: The numbers to sum.
/// - Returns: The sum of the numbers.
func sum(_ numbers: [Int]) *->* Int {
// ...
}
Copy the code
//⛔️
/// This property is the background color of the view.
var backgroundColor: UIColor
/// This method returns the sum of the numbers in the given array.
///
/// - Parameter numbers: The numbers to sum.
/// - Returns: The sum of the numbers.
func sum(_ numbers: [Int]) *->* Int {
// ...
}
Copy the code
Parameter, Returns, Throws the Parameter
Use Parameter(s), Returns, and Throws tags and the order to clearly record parameters, return values, and error Throws. Empty descriptions do not appear. When a line does not fit a description, successive lines are indented 2 Spaces from the beginning of the hyphen.
The recommended way to write a document comment in Xcode is to place the cursor over the declaration and press Command + Option + /. This will automatically generate the correct format, just continue typing at the placeholder.
The Parameter(s) and Returns tags can be omitted when a one-sentence summary fully states the meaning of the parameters and return values, and adding dependent tags only duplicates the existing description. Parameter(s), Returns, Throws the contents after the tag end with a period, even if they are not complete sentences but phrases.
If there is only one Parameter, use the singular single-line Parameter tag. When there are multiple Parameters, use the form of a grouped complex number of Parameters, each labeled with its name, as an item in a nested list.
//✅ /// return the output generated by executing a command. /// /// -parameter command: The command to execute in the shell environment. /// - Returns: A string containing the contents of the invoked process's /// standard output. func execute(command: String) -> String { // ... } /// Returns the output generated by executing a command with the given string /// used as standard input. /// /// - Parameters: /// - command: The command to execute in the shell environment. /// - stdin: The string to use as standard input. /// - Returns: A string containing the contents of the invoked process's /// standard output. func execute(command: String, stdin: String) -> String { // ... }Copy the code
The following examples are incorrect because they use the plural form for a single argument and the singular form for multiple arguments.
//⛔️ /// return the output generated by executing a command. /// /// - Parameters: /// - command: The command to execute in the shell environment. /// - Returns: A string containing the contents of the invoked process's /// standard output. func execute(command: String) -> String { // ... } /// Returns the output generated by executing a command with the given string /// used as standard input. /// /// - Parameter command: The command to execute in the shell environment. /// - Parameter stdin: The string to use as standard input. /// - Returns: A string containing the contents of the invoked process's /// standard output. func execute(command: String, stdin: String) -> String { // ... }Copy the code
Apple ‘s Markup Format
Using Apple’s markup format for rich formatting of documents is highly recommended. These tags help distinguish symbolic references (such as parameter names) from descriptive text in comments, and can be rendered by Xcode and other document generation tools. Here are some common instructions:
• Paragraphs are separated by three blank lines starting with slashes (///) • * A pair of single asterisks * and _ a pair of single underscores _ wrap around a piece of italic/slanted text • ** A pair of double asterisks ** and __ a pair of double underscores __ wrap around a piece of bold text • Symbol names and line code are placed in 'backquotes'. • Multiple lines of code (such as the example code) are preceded by three back quotes to form a code blockCopy the code
Where do I need documentation
At a minimum, each declaration of open and public, and each member of the declaration of open and public, should provide documentation comments, with the following exceptions:
• Each individual case of an enum does not need to provide comments if the name already describes itself. However, in cases with associated values, if the value is not obvious, you need to document what it means. • Documentation comments do not have to be present on declarations that override supertype declarations or implement a protocol requirement, or that provide a default implementation of the protocol requirement in an extension. An overridden declaration can be documented as a new behavior that describes the declaration it overrides. In any case, documentation comments for an override declaration should not be a mere copy of the overridden declaration. • Documented comments on test classes and test methods do not always appear. But they are useful for functional test classes and helper classes/helper methods shared by multiple tests. • Documentation comments are not always present in the extension declaration (i.e. the extension itself). You can add one if it helps clarify the purpose of the extension, but avoid meaningless and misleading instructions. In the following example, the comment simply repeats what is obvious in the code:Copy the code
In the following example, the comment simply repeats what is obvious in the code:
// Add 'Equatable' conformance. //⛔️ extension MyType: Equatable {//... }Copy the code
The next example is more subtle, but it is an example of a non-scalable document because extensions or conformace can be updated in the future. Consider that the type may be set to Comparable at writing time in order to sort values, but that is not the only possible use of this conformance, and client code may use it for other purposes in the future.
/// Make 'Candidate' comparable so that they can be sorted. //⛔️ extension Candidate: comparable {//... }Copy the code
In general, if you find yourself writing documentation that simply repeats the obvious information from the source code and describes it in terms like “represent,” then it’s ok to leave these comments alone. However, it is inappropriate to cite this exception to justify ignoring relevant information that a typical reader might need to know. For example, for an attribute named canonicalName, if a typical reader might not know what The term “canonical name” means in context, then don’t omit its documentation (on The grounds that “it will only say ///The canonical name”). Use documentation as an opportunity to define the term.
Original English link