Our overarching goals are clarity, consistency, readability, simplicity, and brevity, in that order.
Following this style guide should:
- Improves the readability.
- Make code easier to maintain.
Clarity is more important than brevity. Although Swift code can be compact, it is a non-goal to enable the smallest possible code with the fewest characters. Brevity in Swift code, where it occurs, is a side-effect of the strong type system and features that naturally reduce boilerplate. Note that brevity is not a primary goal. Code should be made more concise only if other good code qualities (such as readability, simplicity, and clarity) remain equal or are improved.
If you are having trouble describing your API’s functionality in simple terms, you may have designed the wrong API. -Apple
-
Static and Class Properties
Static and class properties that return instances of the declaring type are not suffixed with the name of the type.
// RIGHT public class UIColor { public class var red: UIColor { // ... } } // WRONG public class UIColor { public class var redColor: UIColor { // ... } }
-
Use PascalCase for type and protocol names, and lowerCamelCase for everything else.
This one not using on our project right now. But we should. There is a lot of struct and enum which are starts lowerCamelCase. If we add this thing, it will generate 150 error. FYI, folks.
protocol SpaceThing { // ... } class SpaceFleet: SpaceThing { enum Formation { // ... } class Spaceship { // ... } var ships: [Spaceship] = [] static let worldName: String = "Earth" func addShip(_ ship: Spaceship) { // ... } } let myFleet = SpaceFleet()
-
Name booleans like
isAddAddressVisible
,hasChangePermission
, etc. This makes it clear that they are booleans and not other types. -
Names should be written with their most general part first and their most specific part last. The meaning of "most general" depends on context, but should roughly mean "that which most helps you narrow down your search for the item you're looking for." Most importantly, be consistent with how you order the parts of your name.
// WRONG let radiusBorder: CGFloat let widthBorder: CGFloat let bodyRightMargin: CGFloat let bodyLeftMargin: CGFloat // RIGHT let borderRadius: CGFloat let borderRight: CGFloat let bodyMarginRight: CGFloat let bodyMarginLeft: CGFloat
-
Use
enum
s for organizing constants into namespaces.. Feel free to nest namespaces where it adds clarity. -
Include a hint about type in a name if it would otherwise be ambiguous.
// WRONG @IBOutlet private weak var icon: UIImageView! @IBOutlet private weak var rightArrow: UIImageView! @IBOutlet private weak var bottomLine: UIView! let cancel: UIButton // RIGHT @IBOutlet private weak var iconImageView: UIImageView! @IBOutlet private weak var rightArrowImageView: UIImageView! @IBOutlet private weak var bottomLineView: UIView!
-
Event-handling functions should be named like past-tense sentences. The subject can be omitted if it's not needed for clarity.
// WRONG class ExperiencesViewController { private func handleBookButtonTap() { // ... } private func tappedCampaign() { // ... } private func modelChanged() { // ... } } // RIGHT class ExperiencesViewController { private func campaignViewTapped() { // ... } }
-
Avoid Objective-C-style acronym prefixes. This is no longer needed to avoid naming conflicts in Swift.
// WRONG class AIRAccount { // ... } // RIGHT class Account { // ... }
-
Add localization parameters to domain based. When adding string key use below logic. And fileName must be end of the extension.
<ScreenName>.<ScreenComponent>.<ComponentPart>.<Type>
-
Add accessibilityIdentifiers as Localizable.
-
Delete all TODO comments.
-
Use fetch prefix instead of get for interactor functions.
-
Don't use get prefix on function or variable.
-
Remove line spaces for class's first and last lines.
-
Convert cells to viper module. at least move logic into presenter (Who touches it changes it)
-
On UnitTest every
it
method should restore changed data if test case makes changes on any data. -
Mark each extension. Also use
MARK: -
overMARK:
it will add a seperator on each extension.// MARK: - UrlOpenable public extension UrlOpenable { func canOpenUrl(_ url: URL) -> Bool { ... } func openUrl(_ url: URL) { ... } }
-
Line length rule. Some view or requests are too long. We can move some parameters to new line.
line_length: warning: 140 error: 160
-
Don't include types where they can be easily inferred.
// WRONG let host: Host = Host() // RIGHT let host = Host()
enum Direction { case left case right } func someDirection() -> Direction { // WRONG return Direction.left // RIGHT return .left }
-
Don't use
self
unless it's necessary for disambiguation or required by the language.final class Listing { init(capacity: Int, allowsPets: Bool) { // WRONG self.capacity = capacity self.isFamilyFriendly = !allowsPets // `self.` not required here // RIGHT self.capacity = capacity isFamilyFriendly = !allowsPets } private let isFamilyFriendly: Bool private var capacity: Int private func increaseCapacity(by amount: Int) { // WRONG self.capacity += amount // RIGHT capacity += amount // WRONG self.save() // RIGHT save() } }
-
Name members of tuples for extra clarity. Rule of thumb: if you've got more than 3 fields, you should probably be using a struct.
// WRONG func whatever() -> (Int, Int) { return (4, 4) } let thing = whatever() print(thing.0) // RIGHT func whatever() -> (x: Int, y: Int) { return (x: 4, y: 4) } // THIS IS ALSO OKAY func whatever2() -> (x: Int, y: Int) { let x = 4 let y = 4 return (x, y) } let coord = whatever() coord.x coord.y
-
Place the colon immediately after an identifier, followed by a space.
// WRONG var something : Double = 0 // RIGHT var something: Double = 0
// WRONG class MyClass : SuperClass { // ... } // RIGHT class MyClass: SuperClass { // ... }
// WRONG var dict = [KeyType:ValueType]() var dict = [KeyType : ValueType]() // RIGHT var dict = [KeyType: ValueType]()
-
Place a space on either side of a return arrow for readability.
// WRONG func doSomething()->String { // ... } // RIGHT func doSomething() -> String { // ... }
// WRONG func doSomething(completion: ()->Void) { // ... } // RIGHT func doSomething(completion: () -> Void) { // ... }
-
// WRONG if (userCount > 0) { ... } switch (someValue) { ... } let evens = userCounts.filter { (number) in number % 2 == 0 } let squares = userCounts.map() { $0 * $0 } // RIGHT if userCount > 0 { ... } switch someValue { ... } let evens = userCounts.filter { number in number % 2 == 0 } let squares = userCounts.map { $0 * $0 }
-
Omit enum associated values from case statements when all arguments are unlabeled.
// WRONG if case .done(_) = result { ... } switch animal { case .dog(_, _, _): ... } // RIGHT if case .done = result { ... } switch animal { case .dog: ... }
-
Use constructors instead of Make() functions for NSRange and others.
// WRONG let range = NSMakeRange(10, 5) // RIGHT let range = NSRange(location: 10, length: 5)
-
Omit unnecessary parameters.
// WRONG @IBAction func deleteAddressClicked(_ sender: UIButton) { presenter?.notifyDeleteAddressClicked() } // RIGHT @IBAction func deleteAddressClicked() { presenter?.notifyDeleteAddressClicked() }
-
Omit
Void
return types from function definitions.// WRONG func doSomething() -> Void { ... } // RIGHT func doSomething() { ... }
-
Mostly don't use ommited parameters on function. If parameter name contains on method name at it is clear to recognize. You can use.
// WRONG func addImage(_ imageUrl: String) { ... } func addImage(image: UIImage) { ... } // RIGHT func addImage(_ image: UIImage) { ... } // RIGHT func addImage(with image: UIImage) { ... }
-
Long function invocations should also break on each argument. Put the closing parenthesis on the last parameter of the invocation..
// WRONG universe.generateStars(at: location, count: 5, color: starColor, withAverageDistance: 4) // WRONG universe.generateStars( at: location, count: 5, color: starColor, withAverageDistance: 4 ) // WRONG universe.generateStars( at: location, count: 5, color: starColor, withAverageDistance: 4) // WRONG universe.generate( 5, .stars, at: location) // RIGHT universe.generateStars(at: location, count: 5, color: starColor, withAverageDistance: 4) // RIGHT universe.generate(5, .stars, at: location)
-
Favor
Void
return types over()
in closure declarations. If you must specify aVoid
return type in a function declaration, useVoid
rather than()
to improve readability.// WRONG func method(completion: () -> ()) { ... } // RIGHT func method(completion: () -> Void) { ... }
-
Name unused closure parameters as underscores (
_
).Naming unused closure parameters as underscores reduces the cognitive overhead required to read closures by making it obvious which parameters are used and which are unused.
// WRONG someAsyncThing() { argument1, argument2, argument3 in print(argument3) } // RIGHT someAsyncThing() { _, _, argument3 in print(argument3) }
-
Single-line closures should have a space inside each brace. Also if you can do it on one line, do it. When you doing that remember readability is our most important thing.
// WRONG let evenSquares = numbers.filter {$0 % 2 == 0}.map { $0 * $0 } // RIGHT let evenSquares = numbers.filter { $0 % 2 == 0 }.map { $0 * $0 }
-
Infix operators should have a single space on either side. Prefer parenthesis to visually group statements with many operators rather than varying widths of whitespace. This rule does not apply to range operators (e.g.
1...3
) and postfix or prefix operators (e.g.guest?
or-1
).// WRONG let capacity = 1+2 let capacity = currentCapacity ?? 0 let mask = (UIAccessibilityTraitButton|UIAccessibilityTraitSelected) let capacity=newCapacity let latitude = region.center.latitude - region.span.latitudeDelta/2.0 // RIGHT let capacity = 1 + 2 let capacity = currentCapacity ?? 0 let mask = (UIAccessibilityTraitButton | UIAccessibilityTraitSelected) let capacity = newCapacity let latitude = region.center.latitude - (region.span.latitudeDelta / 2.0)
-
Don't use chain delegate if you are not make any changes on every scope.
extension ReviewCell: ReviewContainerViewDelegate { func photosTapped(imageUrls: [String], currentImageIndex: Int) { delegate?.photosTapped(imageUrls: imageUrls, currentImageIndex: currentImageIndex) } } extension ReviewContainerViewPresenter: ReviewContainerViewPresenterInterface { func reviewPhotoTapped(imageUrls: [String], currentImageIndex: Int) { delegate?.photosTapped(imageUrls: imageUrls, currentImageIndex: currentImageIndex) } }
-
Don't use same delegate name when you use chain delegate. A unique method name helps engineers more quickly determine which modules depends on.
-
Prefer initializing properties at
init
time whenever possible, rather than using implicitly unwrapped optionals.// WRONG class MyClass: NSObject { init() { super.init() someValue = 5 } var someValue: Int! } // RIGHT class MyClass: NSObject { init() { someValue = 0 super.init() } var someValue: Int }
-
Prefer using
guard
at the beginning of a scope. -
Access control should be at the strictest level possible.
-
Prefer immutable values whenever possible. Use
map
andcompactMap
instead of appending to a new collection. Usefilter
instead of removing elements from a mutable collection.Mutable variables increase complexity, so try to keep them in as narrow a scope as possible.
// WRONG var results = [SomeType]() for element in input { let result = transform(element) results.append(result) } // RIGHT let results = input.map { transform($0) }
// WRONG var results = [SomeType]() for element in input { if let result = transformThatReturnsAnOptional(element) { results.append(result) } } // RIGHT let results = input.compactMap { transformThatReturnsAnOptional($0) }
-
Default classes to
final
. -
Dont use the
default
case whenswitch
ing over an enum.Enumerating every case requires developers and reviewers have to consider the correctness of every switch statement when new cases are added.
// WRONG switch anEnum { case .a: // Do something default: // Do something else. } // RIGHT switch anEnum { case .a: // Do something case .b, .c: // Do something else. }
Also if you can use ternary if use it on here.
let value = anEnum == .a ? XXX : xxx
-
Check for nil rather than using optional binding if you don't need to use the value.
-
Seperation of Concerns. Do not add everything to BaseProtocolInterface.
Add protocols with specific needs. With that, we can easily identify that interface what it can do? On Trendyol we are adding all common functions to Base...Interface. For example on our project most of ViewInterface can can openUrl but not all of them needs that ability.
public protocol UrlOpenable { func canOpenUrl(_ url: URL) -> Bool func openUrl(_ url: URL) } public extension UrlOpenable { func canOpenUrl(_ url: URL) -> Bool { ... } func openUrl(_ url: URL) { ... } }
public protocol NavigationBarConfigurable { func prepareBackButton(tintColor: Color) func prepareCrossButton(tintColor: Color, target: Any?, selector: Selector) func setNavigationBarHidden(_ shouldHide: Bool) } public extension NavigationBarConfigurable { func prepareBackButton(tintColor: Color) { ... } func prepareCrossButton(tintColor: Color, target: Any?, selector: Selector) { ... } func setNavigationBarHidden(_ shouldHide: Bool) { ... } }
protocol ProductDetailViewInterface: ViewInterface, UrlOpenable, NavigationBarConfigurable { .... }
-
Use ternary if only setting value or calling function.
Swift build times are slow mostly because of expensive type checking. By default Xcode doesn't show code that's slow to compile. You can instruct it to show slowly compiling functions and expressions, though by adding:
-Xfrontend -warn-long-function-bodies=100 (100 means 100ms here, you should experiment with this value depending on your computer speed and project) -Xfrontend -warn-long-expression-type-checking=100
// Build time: 239.0ms let labelNames = type == 0 ? (1...5).map{type0ToString($0)} : (0...2).map{type1ToString($0)} // Build time: 16.9ms var labelNames: [String] if type == 0 { labelNames = (1...5).map{type0ToString($0)} } else { labelNames = (0...2).map{type1ToString($0)} }
// Build time: 1034.0ms let moveValue = willShowKeyboard ? -(endKeyboardFrame.height/Constant.dividerNumberForKeyboardHeight + (view?.doneButtonHeight ?? 0)) : 0 // Build time: 103.0ms var moveValue: CGFloat = 0.0 if willShowKeyboard { moveValue = -(endKeyboardFrame.height/Constant.dividerNumberForKeyboardHeight) if let doneButtonHeight = view?.doneButtonHeight { moveValue += doneButtonHeight } }
-
Alphabetize module imports at the top of the file a single line below the last line of the header comments. Do not add additional line breaks between import statements.
-
On group name don't add full name to subgroups. Don't use Module suffix if it already inside on Module.
A standard organization method helps engineers more quickly determine which modules a file depends on.
// WRONG Modules > Account > BrandFollowingModule > Views > BrandFollowingTableViewHeader Modules > Account > AccountModule > AccountListFooterBanner Modules > Account > AccountSettingsModule //RIGHT Modules > Account > BrandFollowing > Views > TableViewHeader Modules > Account > Listing > FooterBanner Modules > Account > Settings
- *Documentation comments are written using the format where each line is preceded by a triple slash (///). Beacuse if you use that it will see on autocomplete dropdown view. Javadoc-style block comments (/ * ... /) are not permitted.