└── README.md /README.md: -------------------------------------------------------------------------------- 1 | Swift Coding Style Guide 2 | ============================ 3 | 4 | The coding standards adopted by adesso Turkey for the iOS platform. 5 | 6 | Table of Contents 7 | ----------------- 8 | 9 | - [DRY](#dry) 10 | - [Use Early Exit](#use-early-exit) 11 | - [Write Shy Code](#write-shy-code) 12 | - [Minimal Imports](#minimal-imports) 13 | - [Protocol Declarations](#protocol-declarations) 14 | - [Protocol Conformance](#protocol-conformance) 15 | - [Function Declarations](#function-declarations) 16 | - [Closure Expressions](#closure-expressions) 17 | - [Memory Management](#memory-management) 18 | - [If Let Shorthand](#if-let-shorthand) 19 | - [License](#license) 20 | 21 | ## DRY (Don't Repeat Yourself) 22 | 23 | The simple meaning of DRY is don’t write the same code repeatedly. 24 | 25 | * (link) 26 | Instead of preventing code repetition and calling the same function more than once, we should prefer the following method. 27 | 28 | **Preferred** 29 | ```swift 30 | let message = isPositionCorrect ? "Position Correct" : "Position InCorrect" 31 | updateUI(message, isPositionCorrect) 32 | ``` 33 | 34 | **Not Preffered** 35 | ```swift 36 | let isPositionCorrect = false 37 | if isPositionCorrect { 38 | updateUI("Position Correct", isPositionCorrect) 39 | } else { 40 | updateUI("Position InCorrect", isPositionCorrect) 41 | } 42 | ``` 43 | * (link) 44 | By creating an extension on ShowAlert protocol, all conforming types automatically gain showAlert() method implementation without any additional modification. 45 | 46 | **Preferred** 47 | ```swift 48 | protocol ShowingAlert { 49 | func showAlert() 50 | } 51 | 52 | extension ShowingAlert where Self: UIViewController { 53 | func showAlert() { 54 | // ... 55 | } 56 | } 57 | 58 | class LoginViewController: ShowingAlert { } 59 | class HomeViewController: ShowingAlert { } 60 | ``` 61 | 62 | **Not Preffered** 63 | ```swift 64 | class LoginViewController { 65 | func showAlert() { 66 | // ... 67 | } 68 | } 69 | 70 | class HomeViewController: ShowingAlert { 71 | func showAlert() { 72 | // ... 73 | } 74 | } 75 | ``` 76 | * (link) 77 | Extract code snippets with the same job into a single function. 78 | 79 | **Preferred** 80 | ```swift 81 | func sum(a: Int, b: Int) -> Int { return a + b } 82 | 83 | func calculateTwoProperties() { 84 | let result = sum(a: firstValue, b: secondValue) 85 | } 86 | ``` 87 | 88 | **Not Preffered** 89 | ```swift 90 | 91 | let firstValue: Int = 5 92 | let secondValue: Int = 12 93 | 94 | func calculateTwoProperties() { 95 | let result = firstValue + secondValue 96 | } 97 | ``` 98 | 99 | ## Use Early Exit 100 | 101 | * (link) 102 | 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. 103 | 104 | **Preferred** 105 | ```swift 106 | func function(items: [Int]) { 107 | guard !items.isEmpty else { return } 108 | for item in items { 109 | // do something 110 | } 111 | } 112 | ``` 113 | 114 | **Not Preffered** 115 | ```swift 116 | func function(items: [Int]) { 117 | if items.count > 0 { 118 | for item in items { 119 | // do something 120 | } 121 | } 122 | } 123 | ``` 124 | 125 | ## Write Shy(Law Of Demeter) Code 126 | 127 | * (link) 128 | 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. 129 | 130 | **Preferred** 131 | ```swift 132 | protocol PrefferedVMProtocol { 133 | func changeUserName(name: String) 134 | } 135 | 136 | struct User { 137 | var name: String? 138 | } 139 | 140 | class PrefferedVM: PrefferedVMProtocol { 141 | private var user: User 142 | 143 | init(user: User) { 144 | self.user = user 145 | } 146 | 147 | func changeUserName(name: String) { 148 | user.name = name 149 | } 150 | } 151 | 152 | let viewModel: PrefferedVMProtocol = PrefferedVM(user: User(name: "Test")) 153 | viewModel.changeUserName(name: "Preffered") 154 | ``` 155 | 156 | **Not Preffered** 157 | ```swift 158 | class NotPrefferedVM { 159 | var user: User 160 | 161 | init(user: User) { 162 | self.user = user 163 | } 164 | } 165 | 166 | let viewModel = NotPrefferedVM(user: User(name: "Test")) 167 | viewModel.user.name = "Not Preffered" 168 | ``` 169 | 170 | 171 | ## Minimal Imports 172 | 173 | * (link) 174 | Import only the modules a source file requires. 175 | 176 | **Preferred** 177 | ```swift 178 | import UIKit 179 | var textField: UITextField 180 | var numbers: [Int] 181 | ``` 182 | 183 | **Not Preffered** 184 | ```swift 185 | import UIKit 186 | import Foundation 187 | var textField: UITextField 188 | var numbers: [Int] 189 | ``` 190 | 191 | **Preffered** 192 | ```swift 193 | import Foundation 194 | var numbers: [Int] 195 | ``` 196 | 197 | **Not Preffered** 198 | ```swift 199 | import UIKit 200 | var numbers: [Int] 201 | ``` 202 | 203 | ## Protocol Declarations 204 | 205 | 206 | ## Protocol Conformance 207 | 208 | When adding protocol conformance to a model, separate each into an extension and use // MARK: - comment. 209 | 210 | * (link) 211 | 212 | **Preferred** 213 | ```swift 214 | class MyViewController: UIViewController { 215 | // class stuff 216 | } 217 | 218 | // MARK: - UITableViewDataSource 219 | extension MyViewController: UITableViewDataSource { 220 | // table view data source 221 | } 222 | 223 | // MARK: - UIScrollViewDelegate 224 | extension MyViewController: UIScrollViewDelegate { 225 | // scroll view delegate 226 | } 227 | ``` 228 | 229 | **Not Preferred** 230 | ```swift 231 | class MyViewController: UIViewController, UITableViewDataSource, UIScrollViewDelegate { 232 | // all 233 | } 234 | ``` 235 | 236 | ## Function Declarations 237 | 238 | * (link) 239 | Keep function declarations short, if long, then use a line break after each parameter. 240 | 241 | **Preferred** 242 | ```swift 243 | func calculateCost(quantity: Int, 244 | realPrice: Double, 245 | discountRate: Double, 246 | taxRate: Double, 247 | serviceCharge: Double) -> Double { 248 | ... 249 | } 250 | ``` 251 | 252 | **Not Preferred** 253 | ```swift 254 | func calculateCost(quantity: Int, realPrice: Double, discountRate: Double, taxRate: Double, serviceCharge: Double) -> Double { 255 | ... 256 | } 257 | ``` 258 | 259 | * (link) 260 | Invoke functions with a long declaration by using a line break for each parameter. 261 | 262 | **Preferred** 263 | ```swift 264 | let result = calculateCost(quantity: 5, 265 | realPrice: 100, 266 | discountRate: 25, 267 | taxRate: 10, 268 | serviceCharge: 5) 269 | ``` 270 | 271 | **Not Preferred** 272 | ```swift 273 | let result = calculateCost(quantity: 5, realPrice: 100, discountRate: 25, taxRate: 10, serviceCharge: 5) 274 | ``` 275 | 276 | **Not Preferred** 277 | ```swift 278 | let result = calculateCost( 279 | quantity: 5, 280 | realPrice: 100, 281 | discountRate: 25, 282 | taxRate: 10, 283 | serviceCharge: 5) 284 | ``` 285 | 286 | * (link) 287 | Use prepositions or assistive words in the parameter naming instead of function naming. 288 | 289 | **Preferred** 290 | ```swift 291 | func displayPopup(with message: String) { 292 | ... 293 | } 294 | ``` 295 | 296 | **Not Preferred** 297 | ```swift 298 | func displayPopupWith(message: String) { 299 | ... 300 | } 301 | ``` 302 | 303 | * (link) 304 | Avoid from `Void` return type. 305 | 306 | **Preferred** 307 | ```swift 308 | func someMethod() { 309 | ... 310 | } 311 | ``` 312 | 313 | **Not Preferred** 314 | ```swift 315 | func someMethod() -> Void { 316 | ... 317 | } 318 | ``` 319 | 320 | ## Closure Expressions 321 | 322 | * (link) 323 | Use an underscore (`_`) for the name of the unused closure parameter. 324 | 325 | **Preferred** 326 | ```swift 327 | // Only `data` parameter is used 328 | someCompletion { data, _, _ in 329 | handle(data) 330 | } 331 | ``` 332 | 333 | **Not Preferred** 334 | ```swift 335 | // `error` and `succeeded` parameters are unused 336 | someCompletion { data, error, succeeded in 337 | handle(data) 338 | } 339 | ``` 340 | 341 | * (link) 342 | Use trailing closure syntax when a function has only one closure parameter that is at the end of the argument list. 343 | 344 | **Preferred** 345 | ```swift 346 | someMethod(options: [.option1]) { result in 347 | print(result) 348 | } 349 | 350 | otherMethod(options: [.option1], 351 | action: { index in 352 | print(index) 353 | }, 354 | completion: { result in 355 | print(result) 356 | }) 357 | ``` 358 | 359 | **Not Preferred** 360 | ```swift 361 | someMethod(options: [.option1], completion: { result in 362 | print(result) 363 | }) 364 | 365 | otherMethod(options: [.option1], action: { index in 366 | print(index) 367 | }) { result in 368 | print(result) 369 | } 370 | ``` 371 | 372 | * (link) 373 | Use space or line break inside and outside of the closure (if necessary) to increase readability. 374 | 375 | **Preferred** 376 | ```swift 377 | let activeIndices = items.filter { $0.isActive }.map { $0.index } 378 | 379 | let activeIndices = items.filter({ $0.isActive }).map({ $0.index }) 380 | 381 | let activeIndices = items 382 | .filter { $0.isActive } 383 | .map { $0.index } 384 | 385 | let activeIndices = items 386 | .filter { 387 | $0.isActive 388 | } 389 | .map { 390 | $0.index 391 | } 392 | ``` 393 | 394 | **Not Preferred** 395 | ```swift 396 | let activeIndices = items.filter{$0.isActive}.map{$0.index} 397 | 398 | let activeIndices = items.filter( { $0.isActive } ).map( { $0.index } ) 399 | 400 | let activeIndices = items 401 | .filter{$0.isActive} 402 | .map{$0.index} 403 | 404 | let activeIndices = items 405 | .filter{ 406 | $0.isActive 407 | } 408 | .map{ 409 | $0.index 410 | } 411 | ``` 412 | 413 | * (link) 414 | Don't use empty parentheses `()` when single parameter of a function is closure. 415 | 416 | **Preferred** 417 | ```swift 418 | func someClosure { result in 419 | ... 420 | } 421 | ``` 422 | 423 | **Not Preferred** 424 | ```swift 425 | func someClosure() { result in 426 | ... 427 | } 428 | ``` 429 | 430 | * (link) 431 | Avoid from `Void` return type. 432 | 433 | **Preferred** 434 | ```swift 435 | func someClosure { result in 436 | ... 437 | } 438 | ``` 439 | 440 | **Not Preferred** 441 | ```swift 442 | func someClosure { result -> Void in 443 | ... 444 | } 445 | ``` 446 | 447 | ## Memory Management 448 | 449 | 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`). 450 | 451 | * (link) 452 | Always use `[weak self]` or `[unowned self]` with `guard let self = self else { return }`. 453 | 454 | **Preferred** 455 | ```swift 456 | someMethod { [weak self] someResult in 457 | guard let self else { return } // Check out 'If Let Shorthand' 458 | let result = self.updateResult(someResult) 459 | self.updateUI(with: result) 460 | } 461 | ``` 462 | 463 | **Not Preferred** 464 | ```swift 465 | // Deallocation of self might occur between `let result = self?.updateResult(someResult)` and `self?.updateUI(with: result)` 466 | someMethod { [weak self] someResult in 467 | let result = self?.updateResult(someResult) 468 | self?.updateUI(with: result) 469 | } 470 | ``` 471 | * (link) 472 | 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]`. 473 | 474 | 475 | **Preferred** 476 | ```swift 477 | someMethod { [weak self] someResult in 478 | guard let self else { return } // Check out 'If Let Shorthand' 479 | let result = self.updateResult(someResult) 480 | self.updateUI(with: result) 481 | } 482 | ``` 483 | 484 | **Not Preferred** 485 | ```swift 486 | // self may be deallocated inside closure 487 | someMethod { [unowned self] someResult in 488 | guard let self else { return } // Check out 'If Let Shorthand' 489 | let result = self.updateResult(someResult) 490 | self.updateUI(with: result) 491 | } 492 | ``` 493 | 494 | ## If Let Shorthand 495 | Since (Swift 5.7), we can simplfy the if-let & guard let blocks. 496 | 497 | **Preferred** 498 | ```swift 499 | if let value { 500 | print(value) 501 | } 502 | ``` 503 | 504 | **Not Preferred** 505 | ```swift 506 | if let value = value { 507 | print(value) 508 | } 509 | ``` 510 | 511 | **Preferred** 512 | ```swift 513 | someMethod { [weak self] someResult in 514 | guard let self else { return } 515 | let result = self.updateResult(someResult) 516 | self.updateUI(with: result) 517 | } 518 | ``` 519 | 520 | **Not Preferred** 521 | ```swift 522 | someMethod { [weak self] someResult in 523 | guard let self = self else { return } 524 | let result = self.updateResult(someResult) 525 | self.updateUI(with: result) 526 | } 527 | ``` 528 | ## License 529 | 530 | ``` 531 | Copyright 2020 adesso Turkey 532 | 533 | Licensed under the Apache License, Version 2.0 (the "License"); 534 | you may not use this file except in compliance with the License. 535 | You may obtain a copy of the License at 536 | 537 | http://www.apache.org/licenses/LICENSE-2.0 538 | 539 | Unless required by applicable law or agreed to in writing, software 540 | distributed under the License is distributed on an "AS IS" BASIS, 541 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 542 | See the License for the specific language governing permissions and 543 | limitations under the License. 544 | ``` 545 | 546 | [linkedin/jobs]: https://www.linkedin.com/company/adessoturkey/jobs/ 547 | --------------------------------------------------------------------------------