Swift Style Guide
Table of Contents
- Naming
- Swift Style Rules
- Swift Formatting Rules
- Modern Swift Features
- SwiftUI Guidelines
- Concurrency
- Error Handling
- Performance
- Accessibility
- Patterns
Naming
Apple’s API Style Guidelines
Apple’s official Swift naming and API design guidelines hosted on swift.org are considered part of this style guide and are followed as if they were repeated here in their entirety.
Identifiers
Typically, identifiers contain only 7-bit ASCII characters. Unicode identifiers have clear, legitimate meanings in the problem area of the code base (e.g., Greek characters representing mathematical concepts) and are acceptable if the team that owns the code understands them well.
/// Wrong
let 😎 = "😎"
/// Right
let nice = "😎"
Initializers
For clarity, the initializer argument that corresponds directly to the stored property has the same property and name. Use explicit self.
to clearly distinguish between assignments.
/// Wrong
public struct Company {
public let name: String
public let location: String
public init(name: String, location place: String) {
name = name
location = place
}
}
/// Right
public struct Company {
public let name: String
public let location: String
public init(name: String, location: String) {
self.name = name
self.location = location
}
}
Static and Class Properties
Static and class properties that return instances of the declaring type are not suffixed with the name of the type.
/// Wrong
public class UIColor {
public class var blackColor: UIColor {
// ...
}
}
/// Right
public class UIColor {
public class var black: UIColor {
// ...
}
}
Global Constants
Like other variables, the global constant is lowerCamelCase
.
/// Wrong
let VERSION = "1.0.0"
/// Right
let version = "1.0.0"
Swift Style Rules
Access Control
Omit the internal keyword when defining types, properties, or functions with an internal access control level.
/// Wrong
internal class Base {
internal init() {
// ...
}
internal func run() {
// ...
}
}
/// Right
class Base {
init() {
// ...
}
func run() {
// ...
}
}
Attribute Annotations
Place the properties of the function, type, and calculated properties on the line above the declaration.
/// Wrong
@objc func tapAction() {
// ...
}
@discardableResult func run() {
// ...
}
/// Right
@objc
func tapAction() {
// ...
}
@discardableResult
func run() {
// ...
}
Collection Types
Add a trailing comma on the last element of a multi-line array.
/// Wrong
let color: [Color] = [
.black,
.red,
.blue
]
/// Right
let color: [Color] = [
.black,
.red,
.blue,
]
There should be no spaces inside the backets of collection literals.
/// Wrong
let constraints = [ top, right, left, bottom ]
/// Right
let constraints = [top, right, left, bottom]
Types
Don’t include easily deducible types.
/// Wrong
let status: Bool = true
let statusCode: String = 400
/// Right
let status = true
let statusCode = 400
Prefer letting the type of a variable or property be inferred from the right-hand-side value rather than writing the type explicitly on the left-hand side.
/// Wrong
struct Command {
let bash: Alias = .init(executableURL: "/bin/bash", dashc: "-c")
func run() {
let command: Arguments = .init("ls")
bash.run(command)
}
}
/// Right
struct Command {
let bash = Alias(executableURL: "/bin/bash", dashc: "-c")
func run() {
let command = Arguments("ls")
bash.run(command)
}
}
Self Keyword
Don’t use self
unless it’s necessary for disambiguation.
/// Wrong
final class Server {
private let statusCode: Int32
private let result: Result
private var serverRequestSuccess: Bool
init(statusCode: Int32, result: Result) {
self.statusCode = statusCode
self.result = result
self.serverRequestSuccess = !result.success.isEmpty
}
}
/// Right
final class Server {
private let statusCode: Int32
private let result: Result
private var serverRequestSuccess: Bool
init(statusCode: Int32, result: Result) {
self.statusCode = statusCode
self.result = result
serverRequestSuccess = !result.success.isEmpty
}
}
Bind to self
when upgrading from a weak reference.
/// Wrong
final class AClass {
func run(completion: () -> Void) {
Server.run() { [weak self] result in
guard let strongSelf = self else { return }
// ...
completion()
}
}
}
/// Right
final class AClass {
func run(completion: () -> Void) {
Server.run() { [weak self] result in
guard let self else { return }
// ...
completion()
}
}
}
Space
Colons should always be followed by a space. but not preceded by a space.
/// Wrong
let token : Token = "#sidi3wjdia2sdpwd1"
/// Right
let token: Token = "#sidi3wjdia2sdpwd1"
Place a space on either side of a return arrow for readability.
/// Wrong
func request()->Result {
// ...
}
/// Right
func request() -> Result {
// ...
}
Parentheses
Omit unnecessary parentheses.
/// Wrong
if (count > 0) { // ... }
let event = allEvents.map() { // ... }
/// Right
if count > 0 { // ... }
let event = allEvents.map { // ... }
Enum Type
Omit enum associated values from case statements when all arguments are unlabeled.
/// Wrong
if case .response(_) = Response { // ... }
switch result {
case .success(_, _):
// ...
}
/// Right
if case .response = Response { // ... }
switch result {
case .success:
// ...
}
When destructuring an enum case or a tuple, place the let
keyword inline, adjacent to each individual property assignment.
/// Wrong
switch result {
case let .success(value):
// ...
case let .error(statusCode, description):
// ...
}
/// Right
switch result {
case .success(let value):
// ...
case .error(let statusCode, let description):
// ...
}
Declaration
Place simple attributes for stored properties on the same line as the rest of the declaration. Complex attributes with named arguments, or more than one unnamed argument, should be placed on the previous line.
/// Wrong
struct BaseView: View {
@StateObject
var object = ObservableObject()
@available(*, unavailable, message: "No longer in production") var oldView: some View {
// ...
}
}
/// Right
struct BaseView: View {
@StateObject var object = ObservableObject()
@available(*, unavailable, message: "No longer in production")
var oldView: some View {
// ...
}
}
Empty Space
Include a single space in an empty set of braces({ }
).
/// Wrong
extension UIView: Buildable {}
/// Right
extension UIView: Buildable { }
Functions
Omit Void
return types from function definitions.
/// Wrong
func run() -> Void {
// ...
}
/// Right
func run() {
// ...
}
Separate long function declarations with line breaks before each argument label, and before the return signature or any effects (async
, throws
).
class Company {
/// Wrong
func addMember(name: String, email: String, number: String) -> Member {
// ...
}
/// Wrong
func addMember(
name: String,
email: String,
number: String)
-> Member {
// ...
}
/// Right
func addMember(
name: String,
email: String,
number: String
)
-> Member
{
// ...
}
}
Long function invocations should also break on each argument.
/// Wrong
Company.addMember(name: "Zepa", email: "official@pelagornis.com", number: "010-1234-4567")
Company.addMember(
name: "Zepa",
email: "official@pelagornis.com",
number: "010-1234-4567"
)
/// Right
Company.addMember(
name: "Zepa",
email: "official@pelagornis.com",
number: "010-1234-4567")
Remove blank lines between chained functions.
/// Wrong
var item: [String] {
datas
.filter { $0.fetch() }
.map { $0.id }
}
/// Right
var item: [String] {
datas
.filter { $0.fetch() }
.map { $0.id }
}
Closure
Favor Void
return types over () in closure declarations.
/// Wrong
func run(completion: () -> ()) {
// ...
}
/// Right
func run(completion: () -> Void) {
// ...
}
Name unused closure parameters as underscores(_
).
/// Wrong
run() { environment, command in
Task.run(command)
}
/// Right
run() { _, command in
Task.run(command)
}
Omit Void
return types from closure expressions.
run() { command -> Void in
// ...
}
/// Right
run() { command in
// ...
}
Prefer trailing closure syntax for closure arguments with no parameter name.
// WRONG
member.map({ $0.id })
// RIGHT
member.map { $0.id }
Swift Formatting Rules
Indentation
Indent by 2 spaces at a time.
When writing a colon (:
), leave spaces only on the right side of the colon.
/// Wrong
let item:[Int:String]?
/// Right
let item: [Int: String]?
Maximum line length
Each line should have a maximum column width of 100 characters.
- Xcode’s
Prefeneces
->Text Editing
->Display
and set it to 100 characters.
Empty line
Make sure there are no spaces in the blank line, and all files must end in blank lines. The MARK syntax requires spaces below.
/// Wrong
// MARK: Property
let name: String = ""
/// Right
// MARK: Property
let name: String = ""
Semicolons
Semicolons (;) are not used, either to terminate or separate statements.
/// Wrong
func run() {
let company = "Pelagornis";
print("Hello \(company)");
}
/// Right
func run() {
let company = "Pelagornis";
print("Hello \(company)")
}
Import
The module import is sorted alphabetically, import the built-in framework first, separated by blank lines, and import the third-party framework.
import UIKit
import Builder
import Snapkit
Comment
Use ///
to leave comments used for documentation.
/// Banner image exposed at the top of the main screen
final class BannerView: UIView {
/// Image to float banner image View
var bannerImageView: ImageView!
}
Use // MARK:
to separate codes by type.
// MARK: Properties
let nameLabel: UILabel!
let dismissButton: UIButton!
// MARK: Initalizer
override init(frame: CGRect) {
// ...
}
// MARK: Actions
override func dismissButtonDidTap() {
// ...
}
Modern Swift Features
Result Builders
Use result builders for declarative APIs and DSLs.
/// Wrong
let view = VStack {
Text("Hello")
Text("World")
}
/// Right
@ViewBuilder
var content: some View {
Text("Hello")
Text("World")
}
Property Wrappers
Use property wrappers to encapsulate common patterns and reduce boilerplate.
/// Wrong
class ViewModel: ObservableObject {
private var _isLoading = false
var isLoading: Bool {
get { _isLoading }
set {
_isLoading = newValue
objectWillChange.send()
}
}
}
/// Right
class ViewModel: ObservableObject {
@Published var isLoading = false
}
Opaque Types
Use opaque return types (some
) for better API design and performance.
/// Wrong
func createView() -> AnyView {
AnyView(Text("Hello"))
}
/// Right
func createView() -> some View {
Text("Hello")
}
Key Paths
Use key paths for type-safe property references.
/// Wrong
let names = people.map { $0.name }
/// Right
let names = people.map(\.name)
Dynamic Member Lookup
Use @dynamicMemberLookup
sparingly and only when it significantly improves API ergonomics.
/// Wrong
@dynamicMemberLookup
struct Config {
subscript(dynamicMember key: String) -> String? {
return nil
}
}
/// Right
@dynamicMemberLookup
struct Config {
private let values: [String: String]
subscript(dynamicMember key: String) -> String? {
return values[key]
}
}
SwiftUI Guidelines
View Composition
Break down complex views into smaller, focused components.
/// Wrong
struct ContentView: View {
var body: some View {
VStack {
// 100+ lines of view code
}
}
}
/// Right
struct ContentView: View {
var body: some View {
VStack {
HeaderView()
ContentSection()
FooterView()
}
}
}
State Management
Use appropriate state management patterns for different data types.
/// Wrong
struct ContentView: View {
@State private var user: User?
@State private var isLoading = false
@State private var error: Error?
}
/// Right
struct ContentView: View {
@StateObject private var viewModel = UserViewModel()
}
View Modifiers
Create custom view modifiers for reusable styling.
/// Wrong
struct ContentView: View {
var body: some View {
Text("Hello")
.font(.title)
.foregroundColor(.blue)
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
}
}
/// Right
struct ContentView: View {
var body: some View {
Text("Hello")
.cardStyle()
}
}
extension View {
func cardStyle() -> some View {
self
.font(.title)
.foregroundColor(.blue)
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
}
}
Environment Values
Use environment values for dependency injection.
/// Wrong
struct ContentView: View {
let networkService: NetworkService
var body: some View {
// ...
}
}
/// Right
struct ContentView: View {
@Environment(\.networkService) private var networkService
var body: some View {
// ...
}
}
Concurrency
Async/Await
Prefer async/await over completion handlers for asynchronous operations.
/// Wrong
func fetchData(completion: @escaping (Result<Data, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(error))
} else if let data = data {
completion(.success(data))
}
}.resume()
}
/// Right
func fetchData() async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
Actors
Use actors for thread-safe data access.
/// Wrong
class Counter {
private var count = 0
private let queue = DispatchQueue(label: "counter.queue")
func increment() {
queue.async {
self.count += 1
}
}
}
/// Right
actor Counter {
private var count = 0
func increment() {
count += 1
}
func getCount() -> Int {
return count
}
}
Task Groups
Use task groups for concurrent operations.
/// Wrong
func fetchAllData() async throws -> [Data] {
let data1 = try await fetchData1()
let data2 = try await fetchData2()
let data3 = try await fetchData3()
return [data1, data2, data3]
}
/// Right
func fetchAllData() async throws -> [Data] {
try await withThrowingTaskGroup(of: Data.self) { group in
group.addTask { try await fetchData1() }
group.addTask { try await fetchData2() }
group.addTask { try await fetchData3() }
var results: [Data] = []
for try await data in group {
results.append(data)
}
return results
}
}
MainActor
Use @MainActor
for UI-related code.
/// Wrong
class ViewModel: ObservableObject {
@Published var data: [Item] = []
func loadData() {
Task {
let items = try await fetchItems()
DispatchQueue.main.async {
self.data = items
}
}
}
}
/// Right
@MainActor
class ViewModel: ObservableObject {
@Published var data: [Item] = []
func loadData() {
Task {
let items = try await fetchItems()
self.data = items
}
}
}
Error Handling
Structured Error Types
Define specific error types for better error handling.
/// Wrong
enum NetworkError: Error {
case error
}
/// Right
enum NetworkError: Error {
case invalidURL
case noData
case decodingFailed(Error)
case serverError(Int)
var localizedDescription: String {
switch self {
case .invalidURL:
return "Invalid URL"
case .noData:
return "No data received"
case .decodingFailed(let error):
return "Decoding failed: \(error.localizedDescription)"
case .serverError(let code):
return "Server error: \(code)"
}
}
}
Result Type
Use Result type for operations that can fail.
/// Wrong
func fetchUser(id: String, completion: @escaping (User?, Error?) -> Void) {
// ...
}
/// Right
func fetchUser(id: String) async -> Result<User, NetworkError> {
do {
let user = try await networkService.fetchUser(id: id)
return .success(user)
} catch {
return .failure(.decodingFailed(error))
}
}
Performance
Lazy Properties
Use lazy properties for expensive computations.
/// Wrong
class DataProcessor {
let processedData = expensiveComputation()
}
/// Right
class DataProcessor {
lazy var processedData = expensiveComputation()
}
Value Types
Prefer value types (structs, enums) over reference types when possible.
/// Wrong
class Point {
var x: Double
var y: Double
init(x: Double, y: Double) {
self.x = x
self.y = y
}
}
/// Right
struct Point {
let x: Double
let y: Double
}
Collection Performance
Use appropriate collection types for performance.
/// Wrong
var items: [String] = []
for i in 0..<1000 {
items.append("Item \(i)")
}
/// Right
let items = (0..<1000).map { "Item \($0)" }
Accessibility
Accessibility Labels
Provide meaningful accessibility labels for UI elements.
/// Wrong
Button("X") {
dismiss()
}
/// Right
Button("Close") {
dismiss()
}
.accessibilityLabel("Close dialog")
Accessibility Traits
Use appropriate accessibility traits.
/// Wrong
Text("Play")
.onTapGesture {
playMusic()
}
/// Right
Text("Play")
.onTapGesture {
playMusic()
}
.accessibilityAddTraits(.isButton)
Dynamic Type
Support Dynamic Type for better accessibility.
/// Wrong
Text("Hello")
.font(.system(size: 16))
/// Right
Text("Hello")
.font(.body)
Patterns
Unwrapping
Prefer to initialize properties at init time, if possible, without using the force unwrapping option.
/// Wrong
final class Company: NSObject {
var name: String!
init() {
super.init()
name = "Pelagornis"
}
}
/// Right
final class Company: NSObject {
var name: String
init() {
name = ""
super.init()
}
}
Dependency Injection
Use dependency injection for better testability and modularity.
/// Wrong
class UserService {
private let networkService = NetworkService()
func fetchUser() async throws -> User {
return try await networkService.fetchUser()
}
}
/// Right
class UserService {
private let networkService: NetworkService
init(networkService: NetworkService) {
self.networkService = networkService
}
func fetchUser() async throws -> User {
return try await networkService.fetchUser()
}
}
Protocol-Oriented Programming
Use protocols to define contracts and enable better testability.
/// Wrong
class DataManager {
func save(_ data: Data) {
// Direct file system access
}
}
/// Right
protocol DataStorable {
func save(_ data: Data) throws
}
class FileDataManager: DataStorable {
func save(_ data: Data) throws {
// File system implementation
}
}
class DatabaseDataManager: DataStorable {
func save(_ data: Data) throws {
// Database implementation
}
}
Optional Binding
guard else
is written on the same line if it does not impair readability or does not exceed 100 lines.
And Prefer to use guard
statements rather than if
statements to minimize overlap.
Defer
Consider using defer block
if you need a cleanup code at multiple end points.
Blank
Only one blank line is allowed
/// Wrong
func run() {
let name = ""
print(name)
}
/// Right
func run() {
let name = ""
print(name)
}
Spacing
Give a space around the curly brackets.
/// Wrong
value.filter{true}.map{$0}
/// Right
value.filter { true }.map { $0 }
Access Modifier
Access control should be as strict as possible, preferring public
to open
and private
to fileprivate
unless that level is required.
Global Function
It prefers to define methods in the type definition section, and does not define possible global functions
/// Wrong
func age(_ person: Person) {
// ...
}
/// Right
struct Person {
var age: Int {
// ...
}
}
Property
Extract complex property observers into methods. The purpose is to reduce overlap, to separate side effects from attribute declarations, and to make the use of implicitly forwarded parameters explicitly oldValue
.
/// WRONG
public class TextField {
public var text: String? {
didSet {
guard oldValue != text else {
return
}
}
}
}
/// RIGHT
public class TextField {
public var text: String? {
didSet { textDidUpdate(from: oldValue) }
}
private func textDidUpdate(from oldValue: String?) {
guard oldValue != text else {
return
}
}
}
Static Type
If a method needs to be redefined, the default type method should be static
because the author must choose a static
type instead of using the class
keyword.
/// Wrong
class Company {
class func checkStaff(_ member: Member, time: Date) {
// ...
}
}
/// Right
class Company {
static func checkStaff(_ member: Member, _ time: Date) {
// ...
}
}
Final Keyword
If inheritance is unnecessary, add the final
keyword to the default class.
/// Wrong
class Repository {
// ...
}
/// Right
final class Repository {
// ...
}
Immutable or Computed Static Properties
Prefer immutable or computed static
properties over mutable ones whenever possible. Use stored static let
properties or computed static var
properties over stored static var
properties whenever possible, as stored static var
properties are global mutable state.
/// Wrong
enum Color {
static var red = DynamicColor(...)
}
/// Right
enum Color {
static let red = DynamicColor(...)
}
/// Wrong
struct Timmer {
var count: Int
static var start = Timmer(count: 0)
}
/// Right
struct Timmer {
var count: Int
static var start: Timer {
Timmer(count: 0)
}
}
Enum Type
Preferred to list all cases for accuracy consideration rather than using default
cases when switching enumeration types.
/// Not Preferred
enum Color {
case red
case green
case blue
var rawValue: String {
switch self {
case .red:
return "Red"
case .green:
return "Green"
case .blue:
return "Blue"
default:
return "Not provided Color"
}
}
}
/// Preferred
enum Color {
case red
case green
case blue
var rawValue: String {
switch self {
case .red:
return "Red"
case .green:
return "Green"
case .blue:
return "Blue"
}
}
}
Return Keyword
Omit the unnecessary return
keyword.
/// Wrong
var size: CGSize {
return CGSize(
width: 100.0,
height: 100.0
)
}
/// Right
var size: CGSize {
CGSize(
width: 100.0,
height: 100.0
)
}
AnyObject Type
Use AnyObject
instead of class
in protocol definitions.
// WRONG
protocol Request: class {
// ...
}
// RIGHT
protocol Request: AnyObject {
// ...
}
Extension
Extension specifies access control for each declaration individually.
/// Wrong
public extension Company {
func addStaff(_ member: Member) {
// ...
}
}
/// Right
extension Company {
public func addStaff(_ member: Member) {
// ...
}
}
Logging
Prefer dedicated logging systems like os_log
or swift-log
over writing directly to standard out using print(…)
, debugPrint(…)
, or dump(…)
.
/// Wrong
print("User logged in: \(user.id)")
debugPrint("Network request failed: \(error)")
/// Right
import os.log
private let logger = Logger(subsystem: "com.app.myapp", category: "authentication")
logger.info("User logged in: \(user.id)")
logger.error("Network request failed: \(error.localizedDescription)")
Equatable Type
Prefer using the generated Equatable
implementation when comparing properties of all types.
Void Type
Avoid using ()
as a type and prefer to use Void
.
/// Wrong
let response: (Response<(), Error>) -> ()
/// Right
let response: (Response<Void, Error>) -> Void
Avoid using Void()
and prefer to use ()
let response: (Response<Void, Error>) -> Void
/// Wrong
response(.success(Void()))
/// Right
response(.success(()))