Preface
Regarding the relevant specifications of Swift's code, different developers have their own corresponding specifications, and many people may not have any specifications at all. In order to ensure that the code in the same company and the same project group is beautiful and consistent, here is a guide to Swift programming specifications. The primary goal of this guide is to make the code compact, readable and concise.
Code Format
Indent with four spaces
Each line has up to 160 characters, which can prevent one line from being too long (Xcode->Preferences->Text Editing->Page guide at column: set to 160)
Make sure there are blank lines at the end of each file
Make sure that every line does not end with a whitespace (Xcode->Preferences->Text Editing->Automatically trim trailing whitespace + Include whitespace-only lines)
No need to start a new line with the left brace
class SomeClass { func someMethod() { if x == y { /* ... */ } else if x == z { /* ... */ } else { /* ... */ } } /* ... */ }
Add spaces after commas
let array = [1, 2, 3, 4, 5];
The binary operator (+, ==, or >) needs to be added spaces before and after, and there is no space before the left and right brackets.
let value = 20 + (34 / 2) * 3 if 1 + 1 == 2 { //TODO } func pancake -> Pancake { /** do something **/ }
Adhering to Xcode's built-in indentation format, it is recommended to use Xcode's default format when a declared function needs to span multiple lines.
// Xcode declares indentation for cross-multi-line functionsfunc myFunctionWithManyParameters(parameterOne: String, parameterTwo: String, parameterThree: String) { // Xcode will be automatically indented print("\(parameterOne) \(parameterTwo) \(parameterThree)") } // Xcode is indented for multi-line if statementsif myFirstVariable > (mySecondVariable + myThirdVariable) && myFourthVariable == .SomeEnumValue { // Xcode will be automatically indented print("Hello, World!") }
When a function is called with multiple parameters, each parameter starts with a row, one more indent than the function name.
functionWithArguments( firstArgument: "Hello, I am a string", secondArgument: resultFromSomeFunction() thirdArgument: someOtherLocalVariable)
When encountering a large number of arrays or dictionary content that need to be processed and needs to be displayed in multiple lines, the brackets in the [and] method body and the closing in the method body must also be handled similarly.
functionWithBunchOfArguments( someStringArgument: "hello I am a string", someArrayArgument: [ "dadada daaaa daaaa dadada daaaa daaaa dadada daaaa daaaa", "string one is crazy - what is it thinking?" ], someDictionaryArgument: [ "dictionary key 1": "some value 1, but also some more text here", "dictionary key 2": "some value 2" ], someClosure: { parameter1 in print(parameter1) })
Try to avoid multi-line assertions, use local variables or other strategies
// recommendlet firstCondition = x == firstReallyReallyLongPredicateFunction() let secondCondition = y == secondReallyReallyLongPredicateFunction() let thirdCondition = z == thirdReallyReallyLongPredicateFunction() if firstCondition && secondCondition && thirdCondition { // What are you going to do} // Not recommendedif x == firstReallyReallyLongPredicateFunction() && y == secondReallyReallyLongPredicateFunction() && z == thirdReallyReallyLongPredicateFunction() { // What are you going to do}
name
Use Pascal spelling (also known as the lamka spelling, capitalized initials) to name types (such as struct, enum, class, typedef, associatedtype, etc.).
Use the spelling method of small camel (lowercase of the first letter) to name functions, methods, constant light, parameters, etc.
In naming, acronyms are generally in all capitals. The exception is if the acronym is the beginning of a naming, and this naming requires a lowercase letter as the beginning. In this case, the acronym is all lowercase.
// "HTML" is the beginning of the variable name, and requires all lowercase "html"let htmlBodyContent: String = "<p>Hello, World!</p>" // It is recommended to use ID instead of Idlet profileID: Int = 1 // It is recommended to use URLFinder instead of UrlFinderclass URLFinder { /* ... */ }
Use the prefix k + Camel nomenclature to name all non-singleton static constants.
class ClassName { // Primitive constants use k as prefix static let kSomeConstantHeight: CGFloat = 80.0 // Non-primitive constants are also prefixed static let kDeleteButtonColor = () // Do not use k as prefix for singleton static let sharedInstance = MyClassName() /* ... */ }
Naming should have descriptive clarity.
// recommendclass RoundAnimatingButton: UIButton { /* ... */ } // Not recommendedclass CustomButton: UIButton { /* ... */ }
Do not name abbreviations, abbreviations, or single letters.
// recommendclass RoundAnimatingButton: UIButton { let animationDuration: NSTimeInterval func startAnimating() { let firstSubview = } } // Not recommendedclass RoundAnimating: UIButton { let aniDur: NSTimeInterval func srtAnmating() { let v = } }
If the original naming cannot clearly indicate the type, the attribute naming must include type information.
// recommendclass ConnectionTableViewCell: UITableViewCell { let personImageView: UIImageView let animationDuration: NSTimeInterval // The firstName of the attribute name is obviously a string type, so you don't need to include String in the name let firstName: String // Although not recommended, it is also possible to use Controller instead of ViewController here. let popupController: UIViewController let popupViewController: UIViewController // If you need to use subclasses of UIViewController, such as TableViewController, CollectionViewController, SplitViewController, etc., you need to name the type in the naming. let popupTableViewController: UITableViewController // When using outlets, make sure to mark the type in the name. @IBOutlet weak var submitButton: UIButton! @IBOutlet weak var emailTextField: UITextField! @IBOutlet weak var nameLabel: UILabel! } // Not recommendedclass ConnectionTableViewCell: UITableViewCell { // This is not a UIImage and should not end with Image. // It is recommended to use personImageView let personImage: UIImageView // This is not a String, it should be named textLabel let text: UILabel // animation cannot clearly express the time interval // It is recommended to use animationDuration or animationTimeInterval let animation: NSTimeInterval // transition cannot clearly express it as a String // It is recommended to use transitionText or transitionString let transition: String // This is a ViewController, not a View let popupView: UIViewController // Since the abbreviation is not recommended, it is recommended to use ViewController to replace VC let popupVC: UIViewController // Technically speaking, this variable is UIViewController, but it should be expressed that this variable is TableViewController let popupViewController: UITableViewController // To maintain consistency, it is recommended to put the type at the end of the variable instead of the beginning, such as submitButton @IBOutlet weak var btnSubmit: UIButton! @IBOutlet weak var buttonSubmit: UIButton! // When using outlets, the variable name should contain the type name. // It is recommended to use firstNameLabel here @IBOutlet weak var firstName: UILabel! }
When naming a function parameter, make sure that the function can understand the purpose of each parameter.
Code Style
comprehensive
Use let as much as possible and use var less.
When it is necessary to traverse one set to form another set, it is recommended to use the functions flatMap, filter, and reduce.
// recommendlet stringOfInts = [1, 2, 3].flatMap { String($0) } // ["1", "2", "3"] // Not recommendedvar stringOfInts: [String] = [] for integer in [1, 2, 3] { (String(integer)) } // recommendlet evenNumbers = [4, 8, 15, 16, 23, 42].filter { $0 % 2 == 0 } // [4, 8, 16, 42] // Not recommendedvar evenNumbers: [Int] = [] for integer in [4, 8, 15, 16, 23, 42] { if integer % 2 == 0 { evenNumbers(integer) } }
If the variable type can be determined by judgment, it is not recommended to indicate the type when declaring a variable.
If a function has multiple return values, it is recommended to use tuple instead of the inout parameter. If this tuple is used in multiple places, it is recommended to use typealias to define this tuple. If the returned tuple has three or more elements, it is recommended to use a structure or class.
func pirateName() -> (firstName: String, lastName: String) { return ("Guybrush", "Threepwood") } let name = pirateName() let firstName = let lastName =
When using the protocol, be careful to avoid circular references, basically use weak modification when defining attributes.
When using self in closures, you need to be careful to avoid circular references. Using capture lists can avoid this.
functionWithClosure() { [weak self] (error) -> Void in // Plan 1 self?.doSomething() // Or plan 2 guard let strongSelf = self else { return } () }
Break is not explicitly used in the switch module.
Do not use brackets when asserting process control.
// recommendif x == y { /* ... */ } // Not recommendedif (x == y) { /* ... */ }
When writing enum types, try to abbreviate them as much as possible.
// recommend(url, type: .person) // Not recommended(url, type: )
When using class methods, there is no need to abbreviate it, because class methods and enum types are different, and context cannot be easily derived.
// recommend = () // Not recommended = .whiteColor()
It is not recommended to use self unless it is necessary.
When writing a method, you need to measure whether the method will be rewritten in the future. If not, please use the final keyword to modify it, so that the organization method will be rewritten. Generally speaking, the final modifier can optimize the compilation speed, so use it boldly when appropriate. It should be noted that the impact of using final in a publicly published code base vs. final in a local project is very different.
When using some statements such as else, catch, etc., follow the code block keywords, make sure that the code block and keyword are on the same line.
if someBoolean { // What do you want} else { // What do you don't want to do} do { let fileContents = try readFile("") } catch { print(error) }
Access control modifier
If you need to put the access modifier in the first position.
// recommendprivate static let kMyPrivateNumber: Int // Not recommendedstatic private let kMyPrivateNumber: Int
The access modifier should not start a single line, but should remain on the same line as the object described by the access modifier.
// recommendpublic class Pirate { /* ... */ } // Not recommendedpublic class Pirate { /* ... */ }
The default access modifier is internal and can be omitted without writing.
When a variable needs to be accessed by unit tests, it needs to be declared as internal type to use @testable import {ModuleName}. If a variable is actually private type and because the unit test needs to be declared as internal type, make sure to add the appropriate comment documentation to explain why this is done. Adding comments here is recommended to use - warning: marker syntax.
/** This variable is the private name - warning: Defined as internal instead of private for `@testable`. */ let pirateName = "LeChuck"
Custom Operators
Custom operators are not recommended, if you need to create functions instead.
Before rewriting operators, please consider carefully whether there is a good reason to create new operators globally, rather than using other strategies.
You can overload existing operators to support new types (especially ==), but the new definition must retain the original meaning of the operator, such as == must be used to test whether it is equal and return a boolean value.
Switch statements and enumerations
When using swift statements, if the options are limited collections, do not use default. Instead, put some unused options at the bottom and use the break keyword to prevent them from being executed.
The switch option in swift should contain break by default, so you don't need to use the break keyword.
The case statement should be left aligned with the switch statement and above the standard default.
When the defined options are associated with values, make sure that the associated values have the appropriate name, not just the type.
enum Problem { case attitude case hair case hunger(hungerLevel: Int) } func handleProblem(problem: Problem) { switch problem { case .attitude: print("At least I don't have a hair problem.") case .hair: print("Your barber didn't know when to stop.") case .hunger(let hungerLevel): print("The hunger level is \(hungerLevel).") } }
It is recommended to use fall through whenever possible.
If the default option should not be triggered, an error can be thrown or asserts similar practices.
func handleDigit(digit: Int) throws { case 0, 1, 2, 3, 4, 5, 6, 7, 8, 9: print("Yes, \(digit) is a digit!") default: throw Error(message: "The given number was not a digit.") }
Optional type
The only scenario where you use implicit unpacking optional is combining @IBOutlets, using non-optional and regular optional types in other scenarios. Even if you are sure that some variables will never be nil when used, this can maintain consistency and more robustness in the program.
Don't use as! and try! unless absolutely necessary.
If you do not intend to declare an optional type for a variable, but when you need to check whether the variable value is nil, it is recommended to use the current value and nil to directly compare it, rather than using the syntax of if let. And nil is in front of variables is in back.
// recommendif nil != someOptional { // what are you up to} // Not recommendedif let _ = someOptional { // what are you up to}
Don't use unowned, unowned and weak modify variables are basically equivalent, and they are both implicit unpacked (unowned has a little performance optimization on reference counting). Since implicit unpacking is not recommended, unowned variables are not recommended.
// recommendweak var parentViewController: UIViewController? // Not recommendedweak var parentViewController: UIViewController! unowned var parentViewController: UIViewController guard let myVariable = myVariable else { return }
protocol
There are two ways to organize your code when implementing the protocol:
Use the //MAKR: comment to implement the segmentation protocol and other code.
Use extension in the class/struct already has code but within the same file.
Please note that the code inside the extension cannot be rewritten by subclasses, which also means that the test is difficult to perform. If this happens frequently, it is best to use the first method in order to make code consistency. Otherwise, use the second method, which can be used to make the code segmentation clearer. When using the second method, use // MARK: It can still make the code more readable in Xcode.
property
For read-only properties, provide getter instead of get{}.
var computedProperty: String { if someBool { return "I'm a mighty pirate!" } return "I'm selling these fine leather jackets." }
For property-related methods get {}, set {}, willSet, and didSet, make sure to indent the relevant code block.
Although the old and new values in willSet/didSet and set can be customized, the default standard name is recommended, newValue/oldValue.
var computedProperty: String { get { if someBool { return "I'm a mighty pirate!" } return "I'm selling these fine leather jackets." } set { computedProperty = newValue } willSet { print("will set to \(newValue)") } didSet { print("did set from \(oldValue) to \(newValue)") } }
When creating constants, use the static keyword to modify it.
class MyTableViewCell: UITableViewCell { static let kReuseIdentifier = String(MyTableViewCell) static let kCellHeight: CGFloat = 80.0 }
Declaring singleton properties can be done in the following ways:
class PirateManager { static let sharedInstance = PirateManager() /* ... */ }
Closure
If the type of the parameter is obvious, you can omit the parameter type in the function name, but explicitly declaring the type is also allowed. The readability of the code is sometimes added with detailed information, and sometimes it is partially repeated. Make a choice based on your judgment, but keep consistency before and after.
// Omit the typedoSomethingWithClosure() { response in print(response) } //Specify the typedoSomethingWithClosure() { response: NSURLResponse in print(response) } // Map statement uses abbreviation[1, 2, 3].flatMap { String($0) }
If you use a snap list or there are specific non-Void return types, the parameter list should be in brackets, otherwise the brackets can be omitted.
// Because of using a snap list, brackets cannot be omitted.doSomethingWithClosure() { [weak self] (response: NSURLResponse) in self?.handleResponse(response) } // Because of the return type, brackets cannot be omitted.doSomethingWithClosure() { (response: NSURLResponse) -> String in return String(response) }
If the closure is a variable type, you do not need to put the variable value in parentheses unless required, such as the variable type is an optional type (Optional?), or the current closure is within another closure. Make sure that the parameters in the closure are placed in brackets, so () means there are no parameters, and Void means there is no need to return the value.
let completionBlock: (success: Bool) -> Void = { print("Success? \(success)") } let completionBlock: () -> Void = { print("Completed!") } let completionBlock: (() -> Void)? = nil
Array
Basically, don't directly access the array content through subscripts, if possible, use .first or .last because these methods are non-compulsive types and will not crash. It is recommended to use for item in items instead of for i in 0..n whenever possible.
Instead of adding new elements to the array using the += or + operators, use better performance.append() or appendContentsOf(). If you need to declare the array based on other arrays and keep it immutable, use let myNewArray = [arr1, arr2].flatten() instead of let myNewArray = arr1 + arr2 .
Error handling
Suppose a function myFunction return type is declared as String, but it is always possible that the function will encounter an error. One solution is to declare the return type as String?, and return nil when an error is encountered.
func readFile(withFilename filename: String) -> String? { guard let file = openFile(filename) else { return nil } let fileContents = () () return fileContents } func printSomeFile() { let filename = "" guard let fileContents = readFile(filename) else { print("Cannot open \(filename).") return } print(fileContents) }
In fact, if we predict the cause of the failure, we should use try/catch in Swift.
Definition Error object structure is as follows:
struct Error: ErrorType { public let file: StaticString public let function: StaticString public let line: UInt public let message: String public init(message: String, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) { = file = function = line = message } }
Use cases:
func readFile(withFilename filename: String) throws -> String { guard let file = openFile(filename) else { throw Error(message: “Unable to open file name \(filename).") } let fileContents = () () return fileContents } func printSomeFile() { do { let fileContents = try readFile(filename) print(fileContents) } catch { print(error) } }
In fact, there are still some scenarios in the project that are more suitable for declaring as optional types rather than error capture and processing. For example, when encountering errors in the process of obtaining remote data, nil is reasonable as the return result, that is, declaring that returning optional types is more reasonable than error processing.
Overall, if a method is likely to fail and using optional types as the return type will cause the error to be lost, consider throwing the error instead of eating it.
Use the guard statement
Overall, we recommend using a strategy that returns in advance rather than nesting if statements. Using guard statements can improve the readability of your code.
// recommendfunc eatDoughnut(atIndex index: Int) { guard index >= 0 && index < doughnuts else { // If index exceeds the allowable range, return in advance. return } let doughnut = doughnuts[index] eat(doughnut) } // Not recommendedfunc eatDoughnuts(atIndex index: Int) { if index >= 0 && index < { let doughnut = doughnuts[index] eat(doughnut) } }
When parsing optional types, it is recommended to use a guard statement instead of an if statement, because guard statements can reduce unnecessary nested indentation.
// recommendguard let monkeyIsland = monkeyIsland else { return } bookVacation(onIsland: monkeyIsland) bragAboutVacation(onIsland: monkeyIsland) // Not recommendedif let monkeyIsland = monkeyIsland { bookVacation(onIsland: monkeyIsland) bragAboutVacation(onIsland: monkeyIsland) } // prohibitif monkeyIsland == nil { return } bookVacation(onIsland: monkeyIsland!) bragAboutVacation(onIsland: monkeyIsland!)
When parsing optional types, you need to decide whether to choose between an if statement and a guard statement, the most important criterion for judgment is whether the code is more readable. In actual projects, you will face more scenarios, such as relying on 2 different Boolean values, complex logical statements will involve multiple comparisons, etc. Generally speaking, based on your judgment, the code remains consistent and readable. If you are not sure which one is more readable, the if statement or a guard statement is more readable, it is recommended to use guard.
// If statement is more readableif operationFailed { return } // Guard statement has better readability hereguard isSuccessful else { return } // Double denial is not easy to understand - don't do thisguard !operationFailed else { return }
If you need to choose between 2 states, it is recommended to use an if statement instead of a guard statement.
// recommendif isFriendly { print("Hello, a friend from afar!") } else { print(“Poor boy,Where did it come from?") } // Not recommendedguard isFriendly else { print("Where did the poor boy come from?") return } print("Hello, a friend from afar!")
You should only use the guard statement in a scenario where you exit the current context in a failed situation. The following example explains that if statements are sometimes more appropriate than guard statements - we have two unrelated conditions and should not block each other.
if let monkeyIsland = monkeyIsland { bookVacation(onIsland: monkeyIsland) } if let woodchuck = woodchuck where canChuckWood(woodchuck) { () }
We often encounter the use of guard statement to unpack multiple optional values. If all the unpacking errors are consistent, the unpacking can be combined together (such as return, break, continue, throw, etc.).
// Combine together because it may return immediatelyguard let thingOne = thingOne, let thingTwo = thingTwo, let thingThree = thingThree else { return } // statement used because each scene returns a different errorguard let thingOne = thingOne else { throw Error(message: "Unwrapping thingOne failed.") } guard let thingTwo = thingTwo else { throw Error(message: "Unwrapping thingTwo failed.") } guard let thingThree = thingThree else { throw Error(message: "Unwrapping thingThree failed.") }
Documentation/Comment
document
If a function is more complex than O(1), you need to consider adding comments to the function, because the function signature (method name and parameter list) is not so clear at a glance. Here we recommend the popular plugin VVDocumenter. For any reason, if there is any difficult-to-understand code, you need to add comments. For complex classes/structs/enumerations/protocols/properties, you need to add comments. All public functions/classes/variables/enumerations/protocols/attributes/constants also require documentation, especially when function declarations (including names and parameter lists) are not so clear.
After the comment document is complete, you should check that the format is correct.
The rules for commenting documents are as follows:
Do not exceed 160 characters per line (similar to the code length limit).
Even if the document comments only have one line, use modular format (/*/).
Do not use * to occupy spaces in the comment module.
Make sure to use the new - parameter format instead of using the new -:param: format, and note that parameter is lowercase.
If you need to add comments to the parameter/return value/throw an exception of a method, be sure to add comments to all, even if there will be partial repetitions, otherwise the comments will look incomplete. Sometimes if there is only one parameter worth adding comments, you can focus on the method comments.
For the responsible class, you can add some appropriate examples when describing the usage of the class. Please note that Swift annotations support the MarkDown syntax.
/** ## Feature List This class provides the next great functions, as follows: - Function 1 - Function 2 - Function 3 ## example Here is an example of a code block using four spaces as indentation. let myAwesomeThing = MyAwesomeClass() () ## warn Always pay attention to the following points when using 1. First point 2. Second point 3. The third point */ class MyAwesomeClass { /* ... */ }
When writing document notes, try to keep it concise.
Other Comment Principles
// Keep spaces behind.
The comment must start with a new line.
When using comments // MARK: - xoxo, the following line is left as a blank line.
class Pirate { // MARK: - Instance Properties private let pirateName: String // MARK: - Initialization init() { /* ... */ } }
Summarize
The above is the entire content of this article. I hope the content of this article will be of some help to your study or work. If you have any questions, you can leave a message to communicate. Thank you for your support.