The coding standards adopted by adesso Turkey for the iOS platform.
- DRY
- Use Early Exit
- Write Shy Code
- Minimal Imports
- Protocol Declarations
- Protocol Conformance
- Function Declarations
- Closure Expressions
- Memory Management
- If Let Shorthand
- License
The simple meaning of DRY is don’t write the same code repeatedly.
- (link) Instead of preventing code repetition and calling the same function more than once, we should prefer the following method.
Preferred
let message = isPositionCorrect ? "Position Correct" : "Position InCorrect"
updateUI(message, isPositionCorrect)
Not Preffered
let isPositionCorrect = false
if isPositionCorrect {
updateUI("Position Correct", isPositionCorrect)
} else {
updateUI("Position InCorrect", isPositionCorrect)
}
- (link) By creating an extension on ShowAlert protocol, all conforming types automatically gain showAlert() method implementation without any additional modification.
Preferred
protocol ShowingAlert {
func showAlert()
}
extension ShowingAlert where Self: UIViewController {
func showAlert() {
// ...
}
}
class LoginViewController: ShowingAlert { }
class HomeViewController: ShowingAlert { }
Not Preffered
class LoginViewController {
func showAlert() {
// ...
}
}
class HomeViewController: ShowingAlert {
func showAlert() {
// ...
}
}
- (link) Extract code snippets with the same job into a single function.
Preferred
func sum(a: Int, b: Int) -> Int { return a + b }
func calculateTwoProperties() {
let result = sum(a: firstValue, b: secondValue)
}
Not Preffered
let firstValue: Int = 5
let secondValue: Int = 12
func calculateTwoProperties() {
let result = firstValue + secondValue
}
- (link) In functions that take parameters with early exit instead of wrapping the source code in an if loop statement, the condition that the loop is not executed should be added first, and if this is the case, it should return.
Preferred
func function(items: [Int]) {
guard !items.isEmpty else { return }
for item in items {
// do something
}
}
Not Preffered
func function(items: [Int]) {
if items.count > 0 {
for item in items {
// do something
}
}
}
- (link) Write shy code that makes objects loosely coupled. Write everything with the smallest scope possible and only increase the scope if it really needs to.
Preferred
protocol PrefferedVMProtocol {
func changeUserName(name: String)
}
struct User {
var name: String?
}
class PrefferedVM: PrefferedVMProtocol {
private var user: User
init(user: User) {
self.user = user
}
func changeUserName(name: String) {
user.name = name
}
}
let viewModel: PrefferedVMProtocol = PrefferedVM(user: User(name: "Test"))
viewModel.changeUserName(name: "Preffered")
Not Preffered
class NotPrefferedVM {
var user: User
init(user: User) {
self.user = user
}
}
let viewModel = NotPrefferedVM(user: User(name: "Test"))
viewModel.user.name = "Not Preffered"
- (link) Import only the modules a source file requires.
Preferred
import UIKit
var textField: UITextField
var numbers: [Int]
Not Preffered
import UIKit
import Foundation
var textField: UITextField
var numbers: [Int]
Preffered
import Foundation
var numbers: [Int]
Not Preffered
import UIKit
var numbers: [Int]
When adding protocol conformance to a model, separate each into an extension and use // MARK: - comment.
- (link)
Preferred
class MyViewController: UIViewController {
// class stuff
}
// MARK: - UITableViewDataSource
extension MyViewController: UITableViewDataSource {
// table view data source
}
// MARK: - UIScrollViewDelegate
extension MyViewController: UIScrollViewDelegate {
// scroll view delegate
}
Not Preferred
class MyViewController: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
// all
}
- (link) Keep function declarations short, if long, then use a line break after each parameter.
Preferred
func calculateCost(quantity: Int,
realPrice: Double,
discountRate: Double,
taxRate: Double,
serviceCharge: Double) -> Double {
...
}
Not Preferred
func calculateCost(quantity: Int, realPrice: Double, discountRate: Double, taxRate: Double, serviceCharge: Double) -> Double {
...
}
- (link) Invoke functions with a long declaration by using a line break for each parameter.
Preferred
let result = calculateCost(quantity: 5,
realPrice: 100,
discountRate: 25,
taxRate: 10,
serviceCharge: 5)
Not Preferred
let result = calculateCost(quantity: 5, realPrice: 100, discountRate: 25, taxRate: 10, serviceCharge: 5)
Not Preferred
let result = calculateCost(
quantity: 5,
realPrice: 100,
discountRate: 25,
taxRate: 10,
serviceCharge: 5)
- (link) Use prepositions or assistive words in the parameter naming instead of function naming.
Preferred
func displayPopup(with message: String) {
...
}
Not Preferred
func displayPopupWith(message: String) {
...
}
- (link)
Avoid from
Void
return type.
Preferred
func someMethod() {
...
}
Not Preferred
func someMethod() -> Void {
...
}
- (link)
Use an underscore (
_
) for the name of the unused closure parameter.
Preferred
// Only `data` parameter is used
someCompletion { data, _, _ in
handle(data)
}
Not Preferred
// `error` and `succeeded` parameters are unused
someCompletion { data, error, succeeded in
handle(data)
}
- (link) Use trailing closure syntax when a function has only one closure parameter that is at the end of the argument list.
Preferred
someMethod(options: [.option1]) { result in
print(result)
}
otherMethod(options: [.option1],
action: { index in
print(index)
},
completion: { result in
print(result)
})
Not Preferred
someMethod(options: [.option1], completion: { result in
print(result)
})
otherMethod(options: [.option1], action: { index in
print(index)
}) { result in
print(result)
}
- (link) Use space or line break inside and outside of the closure (if necessary) to increase readability.
Preferred
let activeIndices = items.filter { $0.isActive }.map { $0.index }
let activeIndices = items.filter({ $0.isActive }).map({ $0.index })
let activeIndices = items
.filter { $0.isActive }
.map { $0.index }
let activeIndices = items
.filter {
$0.isActive
}
.map {
$0.index
}
Not Preferred
let activeIndices = items.filter{$0.isActive}.map{$0.index}
let activeIndices = items.filter( { $0.isActive } ).map( { $0.index } )
let activeIndices = items
.filter{$0.isActive}
.map{$0.index}
let activeIndices = items
.filter{
$0.isActive
}
.map{
$0.index
}
- (link)
Don't use empty parentheses
()
when single parameter of a function is closure.
Preferred
func someClosure { result in
...
}
Not Preferred
func someClosure() { result in
...
}
- (link)
Avoid from
Void
return type.
Preferred
func someClosure { result in
...
}
Not Preferred
func someClosure { result -> Void in
...
}
A memory leak must not be created in the source code. Retain cycles should be prevented by using either weak
and unowned
references. In addition, value types (e.g., struct
, enum
) can be used instead of reference types (e.g., class
).
- (link)
Always use
[weak self]
or[unowned self]
withguard let self = self else { return }
.
Preferred
someMethod { [weak self] someResult in
guard let self else { return } // Check out 'If Let Shorthand'
let result = self.updateResult(someResult)
self.updateUI(with: result)
}
Not Preferred
// Deallocation of self might occur between `let result = self?.updateResult(someResult)` and `self?.updateUI(with: result)`
someMethod { [weak self] someResult in
let result = self?.updateResult(someResult)
self?.updateUI(with: result)
}
- (link)
Use
[unowned self]
where the object can not be nil and 100% sure that object's lifetime is less than or equal to self. However,[weak self]
should be preferred to[unowned self]
.
Preferred
someMethod { [weak self] someResult in
guard let self else { return } // Check out 'If Let Shorthand'
let result = self.updateResult(someResult)
self.updateUI(with: result)
}
Not Preferred
// self may be deallocated inside closure
someMethod { [unowned self] someResult in
guard let self else { return } // Check out 'If Let Shorthand'
let result = self.updateResult(someResult)
self.updateUI(with: result)
}
Since (Swift 5.7), we can simplfy the if-let & guard let blocks.
Preferred
if let value {
print(value)
}
Not Preferred
if let value = value {
print(value)
}
Preferred
someMethod { [weak self] someResult in
guard let self else { return }
let result = self.updateResult(someResult)
self.updateUI(with: result)
}
Not Preferred
someMethod { [weak self] someResult in
guard let self = self else { return }
let result = self.updateResult(someResult)
self.updateUI(with: result)
}
Copyright 2020 adesso Turkey
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.