Swift 🏎

Hero image for Swift 🏎

Reference Resources

When it comes to Objective-C, most conventions are shared with Swift. But when there are differences, specific examples for Objective-C are added. For more generic conventions, we follow:

Code formatting

General

  • In average, row width should not exceed 120 characters. However, the main criteria is readability.
  • Do not align code as ASCII-art.
  • When using Objective-C, class names must be unique across an entire application. The convention is to use prefixes on all classes. For Swift, class prefixes are optional. It will depend on the project’s type. If we are working with an Objective-C codebase, we will use prefixes. Otherwise, we will not use prefixes classes expect for Error classes.
// Good for Objective-C
RPViewController, RPError

// Good for Swift
ViewController, Manager
  • Use proper data types. For non-integer types use decimal point explicitly:
// Bad
let width = 320

// Good
let width: CGFloat = 320.0
  • Do not keep outdated code in comments as it could confuse other team members and could cause less productivity. Source control is a way to go in this case 😌

Braces and Indents

  • Curly braces open on the same line with the method signature, closed on a new line.
// Bad
func viewDidLoad()
{
    super.viewDidLoad()
}

// Good
func viewDidLoad() {
    super.viewDidLoad()
}
  • Separate logical code blocks with 1 line wrap. Random number of spaces and line breaks is not allowed.

Declaration

Methods

  • Keep method signatures short and clear.
  • Do not keep empty methods, autogenerated methods, methods that only call super implementation.
  • Method must do exactly what is declared in the signature, avoid implicit side effects.
  • Mark methods that perform heavy operations with special words: load, fetch, create.
  • In average method body should not exceed the limit of 80 lines of code.

Properties

  • Prefer outlets as weak, but be aware of a case where strong reference is required. Suppose we have an outlet of NSLayoutConstraint in UITableView where we need to activate and deactivate it. It’s possible that, once deactivated, the outlet will be released from memory since there’s no any strong reference pointing to it.
  • Variables of Boolean type should have is, has prefixes.

Closures

  • Do not use words callback, block, closure as parts of the names. Give names which describe the task the closure performs or specify the moment of time it is called:
// Bad
callback, comparingBlock, sortingClosure, selectionAction, beginBlock

// Good
completion, comparator, sortCriteria, didSelectItem, willBeginParsing
  • Use typealias for reused closure types.
  • For better readability use Void instead of empty braces.
// Bad
var action: () -> ()

// Good
var action: () -> Void

Lightweight Generics

  • Objective-C type declarations using lightweight generic parameterization are imported by Swift with information about the type of their contents preserved
  • Do not have space between TYPE and GENERIC

Not preferred

// Bad
NSArray <NSString *> *a

// Good
NSArray<NSString *> *a

Reused resources

Formatter

  • Keep formatters in a separate file.
  • Do not use static formatters, they are not thread safe.
  • Do not create formatters in a cycle, they are heavy objects.
  • Use namespaces to group formatters.

Constants

  • Do not keep constants in a separate file.
  • Keep as little constants as possible.
  • Sets of settings keep in .plist files.

Colors

  • Give colors reasonable names.
  • Do not use word color in color names.
  • Try to keep the color palette as small as possible. Large set of used color usually caused by inconsistency in design specifications.
  • Expressive names help to manage and remember reused colors.
  • Avoid giving too specific names, make names general:
// Bad
var textFieldBorder: UIColor
var veryDarkGrey: UIColor

// Good
var ashGrey: UIColor
var mandyRed: UIColor

Localization

  • Localization is required regardless of the number of supported languages. Therefore adding an extra language won’t cause any difficulty in the future.
  • Do not use storyboard localization. Keep all strings in Localizable.strings file.
  • For localizable string keys use 3-level domain naming scheme: feature.use_case.description. Do not use capitals or camel case to avoid ambiguity.
// Bad
"Expiry date" = "Expiry date";
"OK" = "OK";

// Good
"booking.confirmation.expiry_date" = "Expiry date";
"general.button.ok" = "OK";

Project Structure

  • Organize .xcodeproj: Folder structure must reflect logical structure of the project. Do not put all view controllers into a separate folder and all models into another one. Keep close related objects together
  • Keep file structure in sync with .xcodeproj
  • One .m / .swift == One class. No extra classes allowed
  • Images and other resources (.plist) should be grouped (i.e. $SRCROOT/Resources/Images/Common/, $SRCROOT/Resource/Assets/)

Objects and Structures

  • Prefer Swift native types over NSObject.
  • Prefer value types to reference types for lightweight data types, or until you explicitly need reference semantics.
// Bad
final class Coordinate {
    var x: CGFloat
    var y: CGFloat
}

// Good
struct Coordinate {
    var x: CGFloat
    var y: CGFloat
}
  • Prefer let over var as it indicates directly that the property cannot be changed, and that makes it easier for other team members to understand the intention. Always use var only when it’s required to be changed. Otherwise, go for let.
  • Define classes final by default.
  • Expose only methods and variables that are meant to be exposed. Hide internal implementation by using private access specifier.
  • Keep life cycle methods such as deinit, viewDidLoad, viewWillAppear etc. at the top of the class body.
  • Prefer declaring init with deinit right below it if required.

Design Patterns

Many design patterns have distinctive declaration style. Being consistent in method naming helps other developers to understand your intentions, relations between objects even without reading the code itself.

Delegate, Data Source

  • Always add the sender to method signatures:
// Bad
func parserDidEndParsingNode(_ node: Node)

// Good
func parser(_ parser: Parser, didEndParsingNode node: Node)
  • Do not use verbs in past form. Use did + Verb in present form. The purpose of such style is avoiding exotic forms of irregular verbs.

Target-Action

  • Always add the sender to the method signatures.
  • Use did as a prefix to describe how the action is triggered.
// Bad
@objc func showCart(_ sender: Any?)

// Good
@objc func didSelectCart(_ sender: UIButton)

Listener

  • For methods which are called by NotificationCenter, always add notification parameter to the signature.
// Bad
func applicationDidEnterForeground()

// Good
func applicationDidEnterForeground(_ notification: Notification)

Code clarity & Best practices

Method and variable naming

Good code has high signal/noise ratio. To declare methods and variables, use as little information, as required to understand their responsibility.

A good rule of thumb is that, when declaring methods and variables that require a comment to explain what they are, it usually indicates that its name might not be clear enough. However, it could depend on the case and couldn’t apply to every case. Use it as an indicator. But, in the end, it leaves to the developer’s judgement upon the case.

Avoid large initialization

Initialize objects only with required parameters. Avoid large init methods. Do not init objects with delegate.

Avoid creating large protocols

Avoid creating large protocols. Each protocol must describe a single responsibility. Many small protocols is better than one large.

Naming classes should be straightforward

Avoid words with unclear meaning, such as Common, Utils, Helper, Custom. The name of a class must leave no questions about its task.

// Bad
DataManager, StringUtils, PriceHelper, CustomStepper

// Good
DataStorage, Formatter, PriceCalculator, StepperWithCount

Be wary of creating large structs

Generally, value types are better than reference types in term of performance as they get allocated in stack. So there’s no need for the reference counting overhead.

Keep in mind that, once struct contains at least 1 reference type, its advantage over using class will be nullified, or might even get worse, since struct is pass-by-value so its properties will need to be copied over and over when passing. Hence, reference counting will do the job more than just using class.

But, the trade-off of value semantic could be worth enough to ignore the performance gain sometimes depending on how big the struct is. In that case, COW (Copy-on-write) should be implemented.