Super comprehensive Swift coding specification (recommended)

  • 2020-05-19 06:02:11
  • OfStack

preface

Different developers have their own specifications for Swift code, and many may not have one at all. To ensure that the code in the same company and on the same project team is beautiful and accurate, write this guide to the Swift programming specification. The primary goal of this guide is to make the code compact, readable, and concise.

Code format

Indent with 4 Spaces

A maximum of 160 characters per line prevents 1 line from being too long (Xcode-) > Preferences- > Text Editing- > Page guide at column: set to 160
Make sure you have a blank line at the end of each file

Make sure that each line does not end with a blank character (Xcode-) > Preferences- > Text Editing- > Automatically trim trailing whitespace + Including whitespace-only lines)

You don't have to replace the opening curly bracket with the first line


class SomeClass {
 func someMethod() {
 if x == y {
  /* ... */
 } else if x == z {
  /* ... */
 } else {
  /* ... */
 }
 }
 /* ... */
}

Follow the comma with a space


let array = [1, 2, 3, 4, 5];

The 2 meta operator (+, ==, or > ), and no Spaces are required before and after the opening and closing brackets.


let value = 20 + (34 / 2) * 3
if 1 + 1 == 2 {
 //TODO
}
func pancake -> Pancake {
 /** do something **/
}

Follow Xcode's built-in indentation format, and Xcode's default format is recommended when a declared function needs to span multiple lines.


// Xcode Declare indentation for functions that span multiple lines 
func myFunctionWithManyParameters(parameterOne: String,
     parameterTwo: String,
     parameterThree: String) {
 // Xcode It will indent automatically 
 print("\(parameterOne) \(parameterTwo) \(parameterThree)")
}
// Xcode In view of the multiple lines  if  Indentation of statements 
if myFirstVariable > (mySecondVariable + myThirdVariable)
 && myFourthVariable == .SomeEnumValue {
 // Xcode It will indent automatically 
 print("Hello, World!")
}

When a function is called with multiple arguments, each argument has an additional line, one indent more than the function name.


functionWithArguments(
 firstArgument: "Hello, I am a string",
 secondArgument: resultFromSomeFunction()
 thirdArgument: someOtherLocalVariable)

When an array or dictionary that needs to be processed needs to be displayed on multiple lines, you need to do the same for [and] similar method body parentheses and method body closure.


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 and use local variables or other policies


//  recommended 
let firstCondition = x == firstReallyReallyLongPredicateFunction()
let secondCondition = y == secondReallyReallyLongPredicateFunction()
let thirdCondition = z == thirdReallyReallyLongPredicateFunction()
if firstCondition && secondCondition && thirdCondition {
 //  What are you doing? 
}
//  Is not recommended 
if x == firstReallyReallyLongPredicateFunction()
 && y == secondReallyReallyLongPredicateFunction()
 && z == thirdReallyReallyLongPredicateFunction() {
 //  What are you doing? 
}

named

Use the PASCAL spelling (aka great camel spelling with capital letters) for type naming (e.g. struct, enum, class, typedef, associatedtype, etc.).

Use the little camel spelling (lowercase) to name functions, methods, constants, parameters, etc.

Acronyms are generally all uppercase in naming. The exception is if the acronym is the beginning of a name, and the name requires a lowercase letter, in which case the acronym is all lowercase.


// "HTML"  Is the beginning of the variable name ,  All lowercase  "html"
let htmlBodyContent: String = "<p>Hello, World!</p>"
//  It is recommended to use  ID  Rather than  Id
let profileID: Int = 1
//  It is recommended to use  URLFinder  Rather than  UrlFinder
class URLFinder {
 /* ... */
}

Use the prefix k + camel nomenclature to name all non-singletons of static constants.


class ClassName {
 //  Primitive constants are used  k  As a prefix 
 static let kSomeConstantHeight: CGFloat = 80.0
 //  Non-primitive constants are also used  k  As a prefix 
 static let kDeleteButtonColor = UIColor.redColor()
 //  Do not use it for singletons k As a prefix 
 static let sharedInstance = MyClassName()
 /* ... */
}

The name should be descriptive and clear.


//  recommended 
class RoundAnimatingButton: UIButton { /* ... */ }
//  Is not recommended 
class CustomButton: UIButton { /* ... */ }

Don't abbreviate, abbreviate, or name a single letter.


let array = [1, 2, 3, 4, 5];
0

If the original name does not clearly indicate the type, include the type information in the property name.


//  recommended 
class ConnectionTableViewCell: UITableViewCell {
 let personImageView: UIImageView
 let animationDuration: NSTimeInterval
 //  As the property name firstName , is obviously a string type, so you don't have to include it in the name String
 let firstName: String
 //  It's not recommended ,  Here with  Controller  Instead of  ViewController  Can also. 
 let popupController: UIViewController
 let popupViewController: UIViewController
 //  If you need to use UIViewController Subclass, such as TableViewController, CollectionViewController, SplitViewController,  Etc., you need to label the name type in the name. 
 let popupTableViewController: UITableViewController
 //  When using outlets when ,  Make sure the annotation type is included in the name. 
 @IBOutlet weak var submitButton: UIButton!
 @IBOutlet weak var emailTextField: UITextField!
 @IBOutlet weak var nameLabel: UILabel!
}
//  Is not recommended 
class ConnectionTableViewCell: UITableViewCell {
 //  This is not  UIImage,  Should not have to Image  Name the ending. 
 //  It is recommended to use  personImageView
 let personImage: UIImageView
 //  This is not String , should be named as  textLabel
 let text: UILabel
 // animation  Unable to articulate the time interval 
 //  It is recommended to use  animationDuration  or  animationTimeInterval
 let animation: NSTimeInterval
 // transition  Can't articulate yes String
 //  It is recommended to use  transitionText  or  transitionString
 let transition: String
 //  This is a ViewController , not View
 let popupView: UIViewController
 //  Since abbreviations are not recommended, they are recommended here  ViewController replace  VC
 let popupVC: UIViewController
 //  Technically this variable is  UIViewController,  But you should express that this variable is TableViewController
 let popupViewController: UITableViewController
 //  In order to maintain 1 For uniqueness, 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!
 //  In the use of outlets  , the variable name should contain the type name. 
 //  It is recommended to use  firstNameLabel
 @IBOutlet weak var firstName: UILabel!
}

When naming function parameters, make sure the function understands the purpose of each parameter.

Code style.

comprehensive

Use let as much as possible and var as little as possible.
It is recommended to use the functions flatMap, filter, and reduce when you need to traverse one set to mutate into another.


//  recommended 
let stringOfInts = [1, 2, 3].flatMap { String($0) }
// ["1", "2", "3"]
//  Is not recommended 
var stringOfInts: [String] = []
for integer in [1, 2, 3] {
 stringOfInts.append(String(integer))
}

//  recommended 
let evenNumbers = [4, 8, 15, 16, 23, 42].filter { $0 % 2 == 0 }
// [4, 8, 16, 42]
//  Is not recommended 
var evenNumbers: [Int] = []
for integer in [4, 8, 15, 16, 23, 42] {
 if integer % 2 == 0 {
 evenNumbers(integer)
 }
}

If the type of a variable can be determined by judgment, it is not recommended to specify the type when declaring a variable.

If a function has multiple return values, it is recommended to use tuples instead of the inout parameter. If the tuple is used in more than one place, it is recommended to use typealias to define the tuple. If the tuple is returned with three or more elements, a struct or class is recommended.


let array = [1, 2, 3, 4, 5];
3

When using delegates and protocols, be careful to avoid circular references and basically use weak modifiers when defining properties.

You need to be careful to avoid circular references when using self in closures, which can be avoided by using capture lists.


let array = [1, 2, 3, 4, 5];
4

break is not explicitly used in the switch module.

Do not use parentheses when asserting process control.


let array = [1, 2, 3, 4, 5];
5

When writing enumerated types, try to abbreviate them.


let array = [1, 2, 3, 4, 5];
6

Do not use shorthand when using class methods, because class methods are not the same as enumerated types and cannot easily derive context.


//  recommended 
imageView.backgroundColor = UIColor.whiteColor()
//  Is not recommended 
imageView.backgroundColor = .whiteColor()

self is not recommended unless required.

When writing a method, you need to measure whether the method will be overwritten in the future. If it is not, please modify it with the final keyword so that the method is overwritten. Generally speaking, the final modifier can be used to optimize compilation speed, so use it when appropriate. It is important to note that the impact of using final in a publicly published code base is quite different from that of using final in a local project.

When using statements such as else, catch, etc. that follow the block keyword, make sure that the block and the keyword are in the same line.


if someBoolean {
 //  What do you want 
} else {
 //  What don't you want to do 
}
do {
 let fileContents = try readFile("filename.txt")
} catch {
 print(error)
}

Access control modifier

If you need to put the access modifier in the first position.


let array = [1, 2, 3, 4, 5];
9

The access modifier should not be separated from the first line, but should be kept on the same line as the object described by the access modifier.


//  recommended 
public class Pirate {
 /* ... */
}
//  Is not recommended 
public
class Pirate {
 /* ... */
}

The default access modifier is internal, which can be omitted without writing.

When a variable needs to be accessed by a unit test, it needs to be declared as type internal to use @testable import {ModuleName}. If a variable is actually of type private, and because the unit test needs to be declared as type internal, be sure to add the appropriate comment documentation to explain why. The -warning: markup syntax is recommended for adding comments here.


/**
  The variable is private  The name 
 - warning:  Is defined as  internal  Rather than  private  In order to  `@testable`.
 */
let pirateName = "LeChuck"

Custom operator

Custom operators are not recommended if you need to create functions instead.

Before you override the operator, consider carefully whether there is a good reason to create a new operator globally rather than using another policy.

You can override existing operators to support new types (specifically ==), but the new ones must retain the operator's original meaning, such as ==, which must be used to test for equality and return a Boolean value.

switch statements and enumerations

When using the swift statement, do not use default if the options are a finite set. Instead, put some unused options at the bottom and block them with the break keyword.

Because the switch option in swift contains break by default, there is no need to use the break keyword.

The case statement should be left aligned with the switch statement and above the standard default.

When an option is defined with an associated value, make sure that the associated value has 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, you can throw an error or an assertion like this.


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 1 can use implicit unpacking is in combination with @IBOutlets, using non-optional and regular optional types in other scenarios. Even if you are certain that some variables will never be used for nil in some scenarios, doing so will keep the 1 out and the program more robust.

Don't use as! And try!!! Unless you have to.

If you are not going to declare an optional type for a variable, but when you need to check if the value of the variable is nil, it is recommended to compare the current value directly with nil, rather than if let syntax. And nil comes after the first variable.


//  recommended 
if nil != someOptional {
 //  What are you going to do 
}
//  Is not recommended 
if let _ = someOptional {
 //  What are you going to do 
}

Do not use unowned, unowned and weak modification variables are basically equivalent and are both implicitly unpacked (unowned has a little performance optimization on reference counting), and since implicit unpacking is not recommended, unowned variables are also not recommended.


//  recommended 
weak var parentViewController: UIViewController?
//  Is not recommended 
weak var parentViewController: UIViewController!
unowned var parentViewController: UIViewController

guard let myVariable = myVariable else {
 return
}

agreement

When implementing protocols, there are two ways to organize your code:

Implement the split protocol and other code using //MAKR: annotations.

Use extension outside of the existing code in the class/structure, but in the same file.

Note that the code in extension cannot be subclassed, which also means that testing is difficult. If this is often the case, it is best to unify 1 with the first approach for code-1 conformance. Otherwise, with the second method, the code can be separated more clearly. Using the second method, using // MARK: still makes the code more readable at Xcode.

attribute

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 propert-related methods get {}, set {}, willSet, and didSet, be sure to indent the relevant code block.

While the old and new values in willSet/didSet and set can be customized, the default standard name newValue/oldValue is recommended.


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.


class MyTableViewCell: UITableViewCell {
 static let kReuseIdentifier = String(MyTableViewCell)
 static let kCellHeight: CGFloat = 80.0
}

Singleton properties can be declared by:


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 it is also allowed to explicitly declare the type. Sometimes the readability of the code is to add detailed information, and sometimes it is partially repetitive. Make your choice as you see fit, but keep the consistency between the two.


//  Omit type 
doSomethingWithClosure() { response in
 print(response)
}
//  Specify the type 
doSomethingWithClosure() { response: NSURLResponse in
 print(response)
}
// map  Use abbreviations for statements 
[1, 2, 3].flatMap { String($0) }

If you use capture lists or have specific non-Void return types, the parameter list should be in parentheses, otherwise the parentheses can be omitted.


//  Because you use capture lists, you cannot omit the parentheses. 
doSomethingWithClosure() { [weak self] (response: NSURLResponse) in
 self?.handleResponse(response)
}
//  Because of the return type, the parentheses cannot be omitted. 
doSomethingWithClosure() { (response: NSURLResponse) -> String in
 return String(response)
}

If the closure is a variable type, you don't need to put the value of the variable in parentheses unless you need to, such as if the variable type is optional (Optional?). , or the current closure is in another closure. Make sure all the arguments in the closure are enclosed in parentheses, so () means no arguments, and Void means no return value is required.


let completionBlock: (success: Bool) -> Void = {
 print("Success? \(success)")
}
let completionBlock: () -> Void = {
 print("Completed!")
}
let completionBlock: (() -> Void)? = nil

An array of

Basically don't access the contents of an array directly through subscripts, use.first or.last if possible, because these methods are unforced types that don't crash. It is recommended to use for item in items whenever possible instead of for i in 0.. n.

append() or appendContentsOf(), let myNewArray = [arr1, arr2].flatten(), not let arr1 + arr2, if you want to declare an array based on another array and keep it of an immutable type.

Error handling

Suppose a function myFunction has a return type declared as String, but there is always the possibility that the function will encounter error. One solution is to declare the return type as String? , return nil when an error is encountered.


func readFile(withFilename filename: String) -> String? {
 guard let file = openFile(filename) else {
  return nil
 }
 let fileContents = file.read()
 file.close()
 return fileContents
}
func printSomeFile() {
 let filename = "somefile.txt"
 guard let fileContents = readFile(filename) else {
  print(" Can't open  \(filename).")
  return
 }
 print(fileContents)
}

In fact, we should use try/catch in Swift if we anticipate the cause of failure.

The error object structure is defined 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) {
  self.file = file
  self.function = function
  self.line = line
  self.message = message
 }
}

Use cases:


func readFile(withFilename filename: String) throws -> String {
 guard let file = openFile(filename) else {
  throw Error(message:  "The name of the file that won't open  \(filename).")
 }
 let fileContents = file.read()
 file.close()
 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 an error is encountered in the process of acquiring remote data, nil is reasonable as the return result, that is, it is more reasonable to declare that returning optional types is more reasonable than error handling.

Overall, if a method is likely to fail and using an optional type as its return type results in error cause oblivion, consider throwing an error rather than eating it.

Use the guard statement

In general, we recommend an early return policy rather than nesting if statements. Use the guard statement to improve the readability of the code.


//  recommended 
func eatDoughnut(atIndex index: Int) {
 guard index >= 0 && index < doughnuts else {
  //  if  index  Return earlier than allowed. 
  return
 }
 let doughnut = doughnuts[index]
 eat(doughnut)
}
//  Is not recommended 
func eatDoughnuts(atIndex index: Int) {
 if index >= 0 && index < donuts.count {
  let doughnut = doughnuts[index]
  eat(doughnut)
 }
}

When parsing optional types, it is recommended to use the guard statement instead of the if statement because the guard statement reduces unnecessary nesting indentation.


//  recommended 
guard let monkeyIsland = monkeyIsland else {
 return
}
bookVacation(onIsland: monkeyIsland)
bragAboutVacation(onIsland: monkeyIsland)
//  Is not recommended 
if let monkeyIsland = monkeyIsland {
 bookVacation(onIsland: monkeyIsland)
 bragAboutVacation(onIsland: monkeyIsland)
}
//  ban 
if monkeyIsland == nil {
 return
}
bookVacation(onIsland: monkeyIsland!)
bragAboutVacation(onIsland: monkeyIsland!)

When parsing the optional type need to decide between if statements and guard statements do when the choice, the most important criterion is whether to make the code readability is stronger, will face more in practical projects, such as relying on two different Boolean value, complex logic statements involve multiple comparison, etc., in general, the consistency judgment according to you keep code readability and stronger, if you are not sure which one if statement which and guard more readable, it is recommended to use guard.


// if  Statements are more readable 
if operationFailed {
 return
}
// guard  The statement is more readable here 
guard isSuccessful else {
 return
}
//  The double negation is not easily understood  -  Don't do that 
guard !operationFailed else {
 return
}

If you need to choose between two states, it is recommended to use the if statement instead of the guard statement.


//  recommended 
if isFriendly {
 print(" hello ,  Friends from far away! ")
} else {
 print( "Poor boy, where did you get it? ")
}
//  Is not recommended 
guard isFriendly else {
 print(" Poor boy, where'd you get it? ")
 return
}
print(" hello ,  Friends from far away! ")

You should only use the guard statement if you exit the current context in the case of a failure. The following example explains that the if statement is sometimes more appropriate than the guard statement.


functionWithArguments(
 firstArgument: "Hello, I am a string",
 secondArgument: resultFromSomeFunction()
 thirdArgument: someOtherLocalVariable)
0

We often encounter unpacking multiple optional values using the guard statement. If all unpacking failures are 1 error handling, the unpacking can be combined into 1 (e.g. return, break, continue,throw, etc.).


functionWithArguments(
 firstArgument: "Hello, I am a string",
 secondArgument: resultFromSomeFunction()
 thirdArgument: someOtherLocalVariable)
1

Documentation/comments

The document

If a function than O (1) the complexity is high, you need to consider for the function to add comments, because the function signature (the method name and argument list) is not so clear, 1 here recommend more popular plug-in VVDocumenter. For whatever reason, if you have any novelty project is not easy to understand the code, all you need to add annotations, for complex class/structure/enumerated/agreement/attributes are need to add comments. All exposed functions/classes/variables/enums/protocols/properties/constants also need to be documented, especially if the function declarations (including the names and parameter lists) are not so clear.

After you have annotated the document, you should check that it is formatted correctly.

The rules for annotated documents are as follows:

Do not exceed 160 characters in a line (similar to the code length limit).

Use the modular format (/* /) even if the document comment is only 1 line long.

Do not use * to hold empty lines in the comment module.

Be sure to use the new parameter format instead of Use the new -:param: format. Also note that parameter is in lower case.

If you need to comment a method parameter/return value/throw exception, be sure to comment all of them, even if they look partially repetitive, otherwise the comments will look incomplete. Sometimes if only one parameter is worth adding a comment, you can highlight it in the method comment.

For responsible classes, you can add some appropriate examples when describing how the class is used. Note that the Swift annotation supports the MarkDown syntax.


functionWithArguments(
 firstArgument: "Hello, I am a string",
 secondArgument: resultFromSomeFunction()
 thirdArgument: someOtherLocalVariable)
2

When writing comments to a document, try to keep them concise.

Other annotation principles

// keep a space after it.

The comment must start on another line.

When using comment // MARK: -xoxo, the following line is left blank.


functionWithArguments(
 firstArgument: "Hello, I am a string",
 secondArgument: resultFromSomeFunction()
 thirdArgument: someOtherLocalVariable)
3

conclusion


Related articles: