17 Swift Best Practices

17 Swift Best Practices

[[151452]]

Preface

This post is a compilation of notes I took while working on SwiftGraphics. Most of the suggestions are well thought out, but there are other similar solutions. So if other solutions make sense, they will be added.

This best practice does not impose or recommend a procedural, object-oriented, or functional style for using Swift. Rather, it is about a pragmatic approach. Some suggestions may focus on object-oriented or pragmatic solutions where necessary.

This article is primarily focused on the Swift language and the Swift standard library. Even so, if we can offer a unique Swift perspective and insight, we will still provide special advice on using Swift on Mac OS, iOS, WatchOS, and TV OS. And how to use Swift effectively with Xcode and LLDB will also be provided in the style of Hints & tips.

This process required a lot of hard work, and I am very grateful to those who contributed to this article.

Additionally, you can discuss this in the [Swift-Lang slack].

Notes for Contributors

Please make sure all examples are runnable first (some examples may not work correctly). This markdown can be converted to a Mac OS X playground.

Golden Rule

  • Generally speaking, Apple is right, follow the way Apple likes or demonstrates things. In any case, you should follow Apple's coding style as defined in their book "The Swift Programming Language". However, Apple is a large company, and we will see many differences in the sample code.

  • Never write code just to reduce the amount of code. Try to rely on Xcode's auto-completion, automatic suggestions, and copy and paste. A detailed code description style is very beneficial to other code maintainers. Even so, excessive verbosity will lose an important feature of Swift: type inference.

Best Practices

1. Naming

As in the Swift Programming Language, type names are named in upper camel case (for example: VehicleController).

Variables and constants are named using lowerCamelCase (for example: vehicleName).

You should use Swift templates to name your code rather than using the Objective-C class prefix style (unless connected to Objective-C).

Do not use any Hungarian notation names (e.g. k for constants, m for methods), use short names and use Xcode's Type Quick Help ( [[151453]] + click) to find out the type of the variable. Also, do not use SNAKE_CASE names.

The only special thing is the naming of enum values, which need to use upper camel case naming (this also follows Apple's Swift Programming Language style):

  1. enum Planet {
  2. case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
  3. }

Wherever possible, unnecessary shortening and abbreviation of names should be avoided, in the future you should be able to specify the type trait " ViewController " without any damage and without relying on Xcode's autocomplete functionality. Very common abbreviations such as URL are allowed. Abbreviations should be written in all uppercase ( URL ) or all lowercase ( url ). The same rules apply to types and variables. If url is a type, it should be uppercase, if it is a variable, it should be lowercase.

2. Annotations

Comments should not be used to invalidate code, commenting code invalidates it and affects the cleanliness of the code. If you want to remove code, but still want to keep it in case it is needed in the future, you should rely on git or the bug tracker.

3. Type Inference

Where possible, use Swift’s type inference to reduce redundant type information. For example, the correct way to write:

  1. var currentLocation = Location()

instead of:

  1. var currentLocation: Location = Location()

4. Self-inference

Let the compiler infer self wherever it is allowed. Explicit use of self should be used in init functions to set parameters and in non-escaping closures. For example:

  1. struct Example {
  2. let name: String
  3.      
  4. init(name: String) {
  5. self.name = name
  6. }
  7. }

5. Parameter list type inference

Specifying parameter types in a closure expression can make the code more verbose. Specify types only when necessary.

  1. let people = [
  2. ( "Mary" , 42 ),
  3. ( "Susan" , 27 ),
  4. ( "Charlie" , 18 ),
  5. ]
  6.  
  7. let strings = people.map() {
  8. (name: String, age: Int) -> String in
  9. return   "\(name) is \(age) years old"  
  10. }

If the compiler is able to infer the type, then the type definition should be removed.

  1. let strings = people.map() {
  2. (name, age) in
  3. return   "\(name) is \(age) years old"  
  4. }

Using ordered parameter numbers ("$0", "$1", "$2") can help reduce redundancy, as they often fully match the parameter list. Use numbered names only when the closure parameter names don't contain too much information (such as very simple maps and filters).

Apple can and will change the parameter types of Swift when converted from Objective-C frameworks. For example, options may be removed or become automatically expanded. We should intentionally specify your options and rely on Swift to infer the types to reduce the risk of your program breaking in this situation.

You should always specify return types sparingly. For example, this parameter list is clearly overly verbose:

  1. dispatch_async(queue) {
  2. () -> Void in
  3. print( "Fired." )
  4. }

6. Constants

When defining a type, constants should be declared as static in the type. For example:

  1. struct PhysicsModel {
  2. static var speedOfLightInAVacuum = 299_792_458
  3. }
  4.  
  5. class Spaceship {
  6. static let topSpeed ​​= PhysicsModel.speedOfLightInAVacuum
  7. var speed: Double
  8.      
  9. func fullSpeedAhead() {
  10. speed = Spaceship.topSpeed
  11. }
  12. }

Using static to modify constants allows them to be referenced without requiring an instantiation of the type.

Except for singletons, global constants should be avoided as much as possible.

7. Computed Properties

When you only need to inherit getter methods, return simple Computed properties. For example, you should do this:

  1. class Example {
  2. var age: UInt32 {
  3. return arc4random()
  4. }
  5. }

instead of:

  1. class Example {
  2. var age: UInt32 {
  3. get {
  4. return arc4random()
  5. }
  6. }
  7. }

If you add set or didSet to a property, you should explicitly provide a get method.

  1. class Person {
  2. var age: Int {
  3. get {
  4. return Int(arc4random())
  5. }
  6. set {
  7. print( "That's not your age." )
  8. }
  9. }
  10. }

8. Converting Instances

When creating code to convert from one type to another init() method:

  1. extension NSColor {
  2.  
  3. convenience init(_ mood: Mood) {
  4.  
  5. super .init(color: NSColor.blueColor)
  6.  
  7. }
  8.  
  9. }

In the Swift standard library, init methods are now the preferred way to convert instances of one type to another.

The "to" method is another reasonable technique (although you should follow Apple's guidance and use the init method):

  1. struct Mood {
  2. func toColor() -> NSColor {
  3. return NSColor.blueColor()
  4. }
  5. }

And you might be tempted to use a getter, for example:

  1. struct Mood {
  2. var color: NSColor {
  3. return NSColor.blueColor()
  4. }
  5. }

Getters are generally limited in that they should return a component of an acceptable type. For example, returning an instance of Circle is a good candidate for a getter, but converting a Circle to a CGPath is best done using the "to" function or the init() extension on CGPath.

9. Singletons

Singletons are simple to create in Swift:

  1. class ControversyManager {
  2. static let sharedInstance = ControversyManager()
  3. }

Swift's runtime ensures that singletons are created and accessed in a thread-safe manner.

Singletons should usually only need to access the static property "sharedInstance" unless you have a compelling reason to rename it. Be careful not to use static methods or global functions to access your singletons.

(Since singletons are so simple in Swift, and since constant naming is taking up so much of your time, you should have more time to complain about why singletons are an anti-pattern, but avoid spending so much time on it, and your peers will thank you.)

10. Use extensions to organize your code

Extensions should be used to organize code.

Secondary methods and properties of an instance should be moved to extensions. Note that not all property types currently support moving to extensions, and for best results you should use extensions within this limitation.

You should use extensions to help organize your instance definitions. A good example is a view controller that extends table view data source and delegate protocols. To minimize the code in the table view, consolidate the data source and delegate methods into extensions that adapt to the corresponding protocols.

In a single source file, add some definitions to extensions as you feel best organized. Don’t be afraid to add methods of the main class or methods pointing to method and property definitions in the struct to the extension. As long as everything is contained in one Swift file, it’s fine.

Conversely, main's instance definition should not refer to elements defined in extensions outside the scope of the main Swift file.

11. Chained Setters

For simple setter properties, do not use chained setters as a convenient alternative.

The right way:

  1. instance.foo = 42  
  2. instance.bar = "xyzzy"  

Wrong approach:

  1. instance.setFoo( 42 ).setBar( "xyzzy" )

Compared to chained setters, traditional setters are simpler and less formulaic.

12. Error Handling

Swift 2.0's do/try/catch mechanism is awesome.

13. Avoid using try!

Generally speaking, use the following syntax:

  1. do {
  2. try somethingThatMightThrow()
  3. }
  4. catch {
  5. fatalError( "Something bad happened." )
  6. }

Instead of:

  1. try ! somethingThatMightThrow()

Even though this form is quite verbose, it provides context for other developers to examine the code.

Until a more detailed error handling strategy is developed, it is fine to use try! as a temporary error handling. However, it is recommended that you check your code periodically to find any illegal try! that may escape your code check.

14. Avoid using try?

try? is used to "suppress" errors, and is only useful if you are sure that you don't care about the error that was generated. Generally speaking, you should catch errors and at least print them out.

15. Premature Return & Guards

When possible, use guard statements to handle premature returns or other exit situations (for example, fatal errors or thorough errors).

Correct way to write it:

  1. guard let safeValue = criticalValue else {
  2. fatalError( "criticalValue cannot be nil here" )
  3. }
  4. someNecessaryOperation(safeValue)

Wrong way of writing:

  1. if let safeValue = criticalValue {
  2. someNecessaryOperation(safeValue)
  3. } else {
  4. fatalError( "criticalValue cannot be nil here" )
  5. }

or:

  1. if criticalValue == nil {
  2. fatalError( "criticalValue cannot be nil here" )
  3. }
  4. someNecessaryOperation(criticalValue!)

This flattened code otherwise enters an if let block and exits prematurely near the relevant context, instead of entering the else block.

Even when you don’t capture a value ( guard let ), this pattern will force a premature exit during compilation. In the second if example, even though the code flattens out like the guard, a catastrophic error or other process that returns something that can’t be exited (or is an invalid state depending on the instance) will cause a crash. When a premature exit occurs, the guard statement will catch the error and remove it from the else block.

16. Early access control

Even if your code is not separated into independent modules, you should always think about access control. Marking a definition as private or internal acts as a lightweight documentation for the code. Everyone who reads the code will know that this element cannot be "touched". Conversely, making a definition public is equivalent to inviting other code to access this element. It is better to specify it explicitly rather than relying on Swift's default access control level. (internal)

If your codebase grows in the future, it may be broken up into submodules. Doing so will make it easier and faster to work with a codebase that is already decorated with access control information.

17. Restrictive Access Control

In general, when adding access control to your code, it's best to be exhaustive. Here, it makes more sense to use private than internal, and internal is definitely better than public. (Note: internal is the default).

It is easy to make the access control of code more open (along the lines of "private" to "internal" to "public") if necessary. Code with too open access control may not be appropriate for use by other code. Code with sufficiently restrictive access can detect inappropriate and incorrect usage and provide a better interface. An example is a type that publicly exposes an internal cache.

Moreover, restricting access to code limits the "exposed surface area" and allows code to be refactored with less impact on other code. Other techniques such as Protocol Driven Development can also play a similar role.

TODO Section

  • This is a list of headings for possible future expansion.

  • Protocols & Protocol Driven Development

  • Implicitly Unwrapped Optionals

  • Reference vs Value Types

  • Async Closures

  • unowned vs weak

  • Cocoa Delegates

  • Immutable Structs

  • Instance Initialisation

  • Logging & Printing

  • Computed Properties vs Functions

  • Value Types and Equality

<<:  Baidu, Alibaba, Tencent, which of the three giants is the most dangerous?

>>:  Apple kills ad blocking tool that dares to block Apple apps

Recommend

Things about memory optimization in Android - a record of image optimization

The customer service group was shouting: This use...

How to increase brand marketing exposure for free?

Everyone has put most of their time and energy in...

Beizhen SEO training: Daily data analysis of Guangzhou website SEO optimization

Guangzhou website SEO optimization is not just ab...

Since even light cannot escape from a black hole, why can it be "seen"?

Black holes have always been an incredible existe...

3000 words to explain the key points of private domain operation

As competition among domestic content platforms e...

Why do you feel marketing has become difficult?

Re-think: “ Marketing Jobs” This time, I hope to ...

How can brands come up with new ideas for Spring Festival marketing?

The Spring Festival marketing war is about to beg...

B station marketing promotion methods and strategies!

Brand marketing is a long-term thing. The communi...

Preliminary preparation for developing e-commerce WeChat applet

With the development of intelligence, mobile e-co...