7.2 KiB
File Organization
Convention - MARK Comments
The special // MARK: Some text
comment should be used to group related methods into sections (#pragma mark
in objc).
This menu can be revealed either by tapping the name of the current method in the "jump bar" at the top of your source editor or by pressing ^6
(you can rebind this to something more convenient). One great feature of this method list is that you can type to fuzzy filter the list and press [Return]
to jump to the selected method.
You may also use // MARK: - Some text
to insert a dividing line (such as you can see for "Notifications" and "UIScrollViewDelegate")
Xcode is flexible with whitespace here, but our rules are:
- There should be a single space between
//
andMARK
- There should be no space between
MARK
and:
- There should be a single space after the
:
- If using a line, it should be after the space from point #3 and should have a space afterwards if using text as well
Please attempt to follow these recommendations when marking groups of methods:
- When marking a section within a type declaration or extension, use
// MARK: Some label
(no hyphen). If the section is outside a type declaration, use// MARK: - Some label
(with a hypen). - When separating the methods within a
UIViewController
, attempt to use these standardized section names:- "Init" (grouping together
init()
anddeinit()
, if any) - "Setup"
- "Layout"
- "Notifications" and "Actions" (event handlers)
- "Helpers"
- "Init" (grouping together
- When marking a section of methods belonging to a superclass or a protocol, use the actual name of the superclass or protocol type instead of a loose translation
- ✅
// MARK: - UITableViewDelegate
- ❌
// MARK: - Table view delegate
- ✅
- In some cases it may make more sense for a method to be moved to a functional section instead of just being grouped based on class hierarchy
- for instance,
becomeFirstResponder
should belong to aUIResponder
mark section sizeThatFits:
should probably belong to aLayout
section rather thanUIView
since the functionality trumps where it was inherited from
- for instance,
Rationale
Having a consistent mechanism for grouping allows everyone on the team to nagivate the codebase with much greater ease and efficiency. Knowing how files for commong view controllers, for example, are laid out makes working on them much faster and simpler.
Example
// bad: does not use the name of the protocol as the name of the section
// MARK: - Table view delegate
...
// good: use the name of the protocol directly as the name of the section
// MARK: - UITableViewDelegate
...
// bad: using the same section for UITableViewDelegate and UITableViewDataSource protocols
// MARK: - UITableViewDelegate and UITableViewDataSource
public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
...
}
public func numberOfSections(in tableView: UITableView) -> Int {
...
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
...
}
// good: using different sections for UITableViewDelegate and UITableViewDataSource protocols
// MARK: UITableViewDelegate
public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
...
}
// MARK: UITableViewDataSource
public func numberOfSections(in tableView: UITableView) -> Int {
...
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
...
}
Convention - Ordering
Most "important" stuff at the top; and least "important" stuff at the bottom. The importance is usually determined by the access level; the less restrictive the access level, the more "important" it is.
Rationale
Ordering file contents by access level ensures that the entities with the least restrictive access level get the most visibility, which are likely the components that developers interact with the most.
Example
// public enums, classes, interfaces etc.
public class House {
// public functions
public init {
// ...
}
// public variables
public static let continent = "Westeros"
// internal functions
internal func addHouseMember() {
// ...
}
// internal variables
internal let sigil: Animal
// fileprivate functions
fileprivate func updateHouseAllegiance() {
// ...
}
// fileprivate variables
fileprivate var hasAllies: Bool
// private functions
private func reorganizeHouse() {
// ...
}
// private variables
private var size: Int = 0
}
// internal functions
internal func updateUnalliedHouses(_ houses: [House]) {
houses.forEach { house in
// since this top-level function is in the same file, it can access fileprivate funcs and vars
if !house.hasAllies {
house.updateHouseAllegiance()
}
}
}
// private extensions or constants
private maximumHouseSize: Int = 50
Convention - Constants
Use structs or nested structs to group constants when possible; the grouping should be based on logical use instead of structure or type of the constants.
Rationale
This reduces verbosity and provides structure.
Example
class FruitSellerView: UIView {
...
// bad: no separation between rotation and fade animation constants, long constant names are harder to read
private enum Constants {
static let rotationAnimationAngle: CGFloat = .pi / 45.0
static let rotationAnimationDelay: CGFloat = 0.15
static let rotationAnimationDuration: CGFloat = 0.3
static let rotationAnimationSpeed: CGFloat = 100
static let fadeAnimationDelay: CGFloat = 0.1
static let fadeAnimationDuration: CGFloat = 0.25
static let fadeAnimationSpeed: CGFloat = 200
}
// bad: the name "Strings" does not provide helpful about how the constants are used.
private enum Strings {
static let rotationAnimationIdentifier: String = "FruitSellerViewRotationAnimationIdentifier"
static let fadeAnimationIdentifier: String = "FruitSellerViewFadeAnimationIdentifier"
}
}
class FruitSellerView: UIView {
...
// good: split rotation and fade animation constants into different groups
private enum RotationAnimationConstants {
static let angle: CGFloat = .pi / 45.0
static let delay: CGFloat = 0.15
static let duration: CGFloat = 0.3
static let speed: CGFloat = 100
static let identifier: String = "FruitSellerViewRotationAnimationIdentifier"
}
private enum FadeAnimationConstants {
static let delay: CGFloat = 0.1
static let duration: CGFloat = 0.25
static let speed: CGFloat = 200
static let identifier: String = "FruitSellerViewFadeAnimationIdentifier"
}
}
Note: We use enum
rather than struct
as it can help reduce potential confusion. Running let x = RotatationAnimationConstants()
would be valid code if we were to use a struct
, for an enum
it will throw an error.