Organization Within a File
June 4, 2021 ยท View on GitHub
How do we organize files?
A file should only contain one major type declaration. Other types are allowed in support of the main type that is represented by the file, which typically shares the name of the file, e.g. LoginViewController.swift would have a major type of LoginViewController.
Bad Code Example
In the example below, we have declared multiple top level enums and classes within one file. The class declarations should be split across multiple files and the enums should be encapsulated within their respective classes when it makes sense.
/// An enum to track whether or not the user is logged in or not
enum AuthState {
case loggedIn
case loggedOut
}
/// An enum to track where the user is in the signup / onboarding process
enum SignupState {
case onboardingComplete
case signupComplete
}
/// A UIViewController subclass that encapsulates functionality for a Login Screen
class LoginViewController: UIViewController {
/// ...
}
///A UIViewController subclass that encapsulates functionality for a Signup Screen
class SignupViewController: UIViewController {
/// ...
}
Good Code Example
In the example below, the file has only one major type declaration. The protocol and extension are supporting the major type of LoginViewController and therefore allowed to be in this file.
protocol LoginViewControllerDelegate {
/// ...
}
/// A UIViewController subclass that encapsulates functionality for a Login Screen
class LoginViewController: UIViewController {
/// ...
}
extension LoginViewController {
// MARK: - LoginViewController
}
Key Considerations
-
Files are organized in the following order:
- Default header created by Xcode
- Import statements
- Protocols that are associated primarily with the major type declaration of the file, each followed by a corresponding default protocol implementations, if applicable.
- The major type declaration of the file
- Nested type declarations
- Properties
- Inherited
- Protocol
IBOutlets- Open
- Public
- Internal
- Private
- Functions
- Inherited
- Protocol
- Open
- Public
- Internal
- Private
- Extension protocol conformances
- Private extensions of other types
-
Initializers, when implemented, should be the first declaration(s) in each group (inherited, protocol, open, etc.) of functions.
-
deinit, when implemented, should come directly after the last initializer. If no initializers exist,deinitshould come before all other function declarations. -
For
Codableconformance, it may be necessary to implement the special nested typeCodingKeys, which conforms toCodingKey. When present, this nested type should be declared after all other nested types. SinceCodingKeysandCodingKeyare not documented as part of theCodableprotocols, noMARKis necessary. -
Default protocol implementation extensions should never include additional methods or properties unless they are
privateto the extension and only used in the default implementation(s).
How do we use MARK?
Group and separate code using MARK. The grouping order for each section of properties and functions should be:
- Overridden declarations
- Declarations for protocol conformance
- Declarations being introduced in the major type of the file
Key Considerations
- We only use
MARKwhen a file has overrides or conformances. MARKseparates where things were originally declared.- Always use
MARK: -for grouping based on type.- The text should be the type you are grouping by.
- Use
MARK:for other groupings inside ofMARK: -, e.g.MARK: Helper Functions. - When adding a
MARKfor an extension, make sure it is inside the extension. - Default protocol implementation extensions do not require a
MARK.
Code Example
/// A view controller that displays an example.
class ExampleViewController: UIViewController {
// MARK: - UIViewController
override var extendedLayoutIncludesOpaqueBars: Bool {
set {
}
get {
return true
}
}
// MARK: - UITraitEnvironment
override var traitCollection: UITraitCollection {
return UITraitCollection(userInterfaceIdiom: .phone)
}
// MARK: - ExampleViewController
@IBOutlet private weak var exampleView: UIView!
private let urlSession: URLSession
// MARK: - UIViewController
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
}
// MARK: - NSCoding
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - UITraitEnvironment
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
view.backgroundColor = .red
}
// MARK: - ExampleViewController
/// Initializes the `ExampleViewController` with the required parameters.
///
/// - Parameter urlSession: The url session the controller should use.
init(urlSession: URLSession) {
self.urlSession = urlSession
super.init(nibName: nil, bundle: nil)
}
private func retrieveExample() {
// ...
}
}
extension ExampleViewController: UINavigationControllerDelegate {
// MARK: - UINavigationControllerDelegate
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
// ...
}
}