iOS 🍏

Hero image for iOS 🍏

In general, we follow the official Swift API Design Guidelines.

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/)

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";
    

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.

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.

  • Class or files naming for 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 project’s type. If we are working with Objective-C codebases. we will have prefixes. On the other hand, we will not prefixed classes expect 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

      NSArray <NSString *> *a
    

    Preferred

      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
    

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 vary when 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 tradeoff 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.