└── 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 |
--------------------------------------------------------------------------------