Skip to content

wanteddev/swift-style-guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

61 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Wanted Lab Swift Style Guide

λͺ©ν‘œ

이 μŠ€νƒ€μΌ κ°€μ΄λ“œλ₯Ό μ€€μˆ˜ν•¨μœΌλ‘œμ¨

  • 보닀 μ‰½κ²Œ 읽고 μ΅μˆ™ν•˜μ§€ μ•Šμ€ μ½”λ“œλ₯Ό 이해할 수 μžˆλ„λ‘ ν•œλ‹€.
  • μ½”λ“œλ₯Ό 보닀 μ‰½κ²Œ μœ μ§€ 관리
  • κ°„λ‹¨ν•œ ν”„λ‘œκ·Έλž˜λ¨Έ 였λ₯˜ κ°μ†Œ
  • μ½”λ”© μ‹œ 인지 λΆ€ν•˜ κ°μ†Œ

간결함이 주된 λͺ©ν‘œκ°€ μ•„λ‹ˆλΌλŠ” 점에 μœ μ˜ν•œλ‹€.
μ½”λ“œλŠ” λ‹€λ₯Έ 쒋은 μ½”λ“œ ν’ˆμ§ˆ(가독성, λ‹¨μˆœμ„±, λͺ…ν™•μ„± λ“±)이 λ™μΌν•˜κ²Œ μœ μ§€λ˜κ±°λ‚˜ κ°œμ„ λ  κ²½μš°μ—λ§Œ 보닀 κ°„κ²°ν•˜κ²Œ λ§Œλ“€μ–΄μ•Ό ν•œλ‹€.

Guiding Tenets

  • λ³Έ κ°€μ΄λ“œμ— μ—†λŠ” κ°€μ΄λ“œλΌμΈμ€ μ•„λž˜λ₯Ό λ”°λ₯Έλ‹€.
  • λͺ¨λ“  κ·œμΉ™μ„ κ΅¬μ²΄ν™”ν•˜κΈ° μœ„ν•΄ λ…Έλ ₯ν•œλ‹€.
  • μ˜ˆμ™Έμ‚¬ν•­μ€ 거의 두지 μ•Šμ•„μ•Ό ν•˜κ³ , μžˆλ”λΌλ„ 정당성이 λ†’μ•„μ•Όν•œλ‹€.
  • master λΈŒλžœμΉ˜μ—μ„œ feature 브랜치λ₯Ό λ§Œλ“€μ–΄ κ°€μ΄λ“œλ₯Ό μˆ˜μ •ν•˜κ³ , master 브랜치둜 PR을 보내 리뷰λ₯Ό ν†΅κ³Όν•˜λ©΄ λ°˜μ˜μ‹œν‚¨λ‹€.

Table of Contents

  1. Code Layout
    1. Basic Code Layout
    2. Functions
    3. Operators
  2. Naming
  3. Style
    1. Basic Style
    2. Closures
    3. Properties
    4. Optionals
    5. Comments
  4. Patterns
    1. Basic Patterns
    2. Objects
    3. Types
  5. File Organization
  6. References

Code Layout

Basic Code Layout

ν•œ 쀄은 μ΅œλŒ€ 110자λ₯Ό λ„˜μ§€ μ•Šμ•„μ•Όν•œλ‹€.

Script:Xcode SwiftLint:line_length SwiftFormat: wrap

λ“€μ—¬μ“°κΈ°μ—λŠ” 4개의 spaceλ₯Ό μ‚¬μš©ν•œλ‹€.

Script:Xcode

Tip: 일뢀 μ½”λ“œ λ˜λŠ” λͺ¨λ‘ 선택(Command-A)ν•œ λ‹€μŒ Control-I(λ˜λŠ” νŽΈμ§‘κΈ° β–Έ Structure β–Έ Re-Indent)λ₯Ό μ„ νƒν•˜μ—¬ λ“€μ—¬μ“°κΈ°λ₯Ό λ‹€μ‹œ ν•  수 μžˆλ‹€.

쀄 λμ—λŠ” 곡백을 μ œκ±°ν•΄μ•Όν•œλ‹€.

Script:Xcode SwiftLint: trailing_whitespace SwiftFormat: trailingSpace

MARK ꡬ문은 μœ νš¨ν•œ ν˜•μ‹μ„ λ”°λ₯΄κ³  μœ„μ™€ μ•„λž˜μ—λŠ” 곡백을 μΆ”κ°€ν•œλ‹€.

SwiftLint: mark SwiftFormat: blankLinesAroundMark SwiftFormat: todos

예λ₯Ό λ“€μ–΄ β€˜// MARK: …’ λ˜λŠ” β€˜// MARK: - …’ ν˜•μ‹μ„ μ‚¬μš©ν•œλ‹€.

쒋은 예:

// MARK: Layout

override func layoutSubviews() {
 // doSomething()
}

// MARK: Actions

override func menuButtonDidTap() {
 // doSomething()
}

μ‹λ³„μž λ°”λ‘œ 뒀에 콜둠 : 을 λ°°μΉ˜ν•˜κ³  κ·Έ 뒀에 곡백을 λ„£λŠ”λ‹€.

SwiftLint: colon

쒋은 예:

var something: Double = 0
var dict = [KeyType: ValueType]()

class MyClass: SuperClass {
  // ...
}

λ‚˜μœ 예:

var something : Double = 0
var dict = [KeyType:ValueType]()
var dict = [KeyType : ValueType]()

class MyClass : SuperClass {
  // ...
}

가독성을 μœ„ν•΄ 리턴 ν™”μ‚΄ν‘œ μ–‘μͺ½μ— 곡백을 λ‘”λ‹€.

SwiftLint: return_arrow_whitespace

쒋은 예:

func doSomething() -> String {
  // ...
}
func doSomething(completion: () -> Void) {
  // ...
}

λ‚˜μœ 예:

func doSomething()->String {
  // ...
}
func doSomething(completion: ()->Void) {
  // ...
}

TODO와 FIXMEλŠ” Warning을 λ°œμƒμ‹œμΌœμ•Ό ν•œλ‹€.

μ£Όμ„μ²˜λ¦¬ μ§€μ‹œμ–΄λ₯Ό μ œμ™Έν•œ TODO: , FIXME: 의 ν˜•μ‹μ„ μ€€μˆ˜ν•˜μ—¬ μž‘μ„±ν•œλ‹€.
단, #warning() ν‚€μ›Œλ“œ μ•ˆμ— μž‘μ„±ν•˜μ—¬ κ°•μ œλ‘œ Warning을 λ°œμƒμ‹œν‚¨λ‹€.

μ™œ?

TODO와 FIXMEλ₯Ό μΆ”μ ν•˜κ³  κ΄€λ¦¬ν•˜μ—¬μ•Ό ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.

쒋은 예:

func emptyFunction() {
   #warning("TODO: μΆ”κ°€ 둜직 μž‘μ„± μ˜ˆμ •μž…λ‹ˆλ‹€")
}
 
#warning("FIXME: νŒŒλΌλ―Έν„° λͺ… ꡐ체 μ˜ˆμ •μž…λ‹ˆλ‹€")
func doSomething(complexParameterName: String) {
   // ...
}

λ‚˜μœ 예:

func emptyFunction() {
  // TODO: μΆ”κ°€ 둜직 μž‘μ„± μ˜ˆμ •μž…λ‹ˆλ‹€
}
  
// νŒŒλΌλ―Έν„° λͺ… ꡐ체 μ˜ˆμ •μž…λ‹ˆλ‹€
func doSomething(complexParameterName: String) {
  // ...
}   

Functions

λ©”μ„œλ“œ κ°„ 빈 쀄이 μ •ν™•νžˆ ν•˜λ‚˜ μžˆμ–΄μ•Όν•œλ‹€.

λ©”μ„œλ“œ 및 기타(if/else/switch/while/guard-else λ“±) μ€‘κ΄„ν˜ΈλŠ” 항상 λ¬Έμž₯κ³Ό λ™μΌν•œ λΌμΈμ—μ„œ μ—΄λ Έμ§€λ§Œ μƒˆλ‘œμš΄ λΌμΈμ—μ„œ λ‹«νžŒλ‹€.

SwiftFormat: braces SwiftFormat: elseOnSameLine

쒋은 예:

if user.isHappy {
 // Do something
} else {
 // Do something else
}

guard let urlString = urlString, let url = URL(string: urlString) else {
    completion(nil, nil)
    return
}

λ‚˜μœ 예:

if user.isHappy
{
  // Do something
}
else {
 // Do something else
}

μ˜ˆμ™Έ: return, return nil 처럼 κ°„λ‹¨ν•˜κ²Œ guard 둜 νƒˆμΆœν•˜λŠ” 경우 ν•œ μ€„λ‘œ μž‘μ„±ν•œλ‹€. 단, { } μ•ˆμͺ½μ— 곡백을 μ€€λ‹€.
SwiftFormat: spaceInsideBraces

쒋은 예:

guard let self = self else { return false }

λ‚˜μœ 예:

guard let self = self else {return false}

guard let self = self else {
  return false
}

λ©”μ„œλ“œ λ‚΄μ˜ 곡백은 κΈ°λŠ₯을 뢄리해야 ν•˜μ§€λ§Œ μ„Ήμ…˜μ΄ λ„ˆλ¬΄ 많으면 μ’…μ’… μ—¬λŸ¬ λ©”μ„œλ“œλ‘œ λ¦¬νŒ©ν† λ§ν•΄μ•Όν•œλ‹€.

ν•¨μˆ˜ μ •μ˜ μ‹œ λ§€κ°œλ³€μˆ˜λ‚˜ 호좜 μ‹œ μΈμˆ˜λŠ” 같은 쀄에 λ†“κ±°λ‚˜, 쀄 λ‹Ή ν•˜λ‚˜λ§Œ μžˆκ²Œν•œλ‹€. μ—¬λŸ¬ μ€„λ‘œ λ§Œλ“ λ‹€λ©΄, 각각 μƒˆ 쀄에 놓고 λ“€μ—¬μ“°κΈ°λ₯Ό μΆ”κ°€ν•œλ‹€.

SwiftLint: multiline_arguments SwiftLint: multiline_parameters SwiftFormat: wraparguments SwiftFormat: wrap SwiftFormat: trailingClosures

쒋은 예:

func reticulateSplines(
  spline: [Double],
  adjustmentFactor: Double,
  translateConstant: Int,
  comment: String
) -> Bool {
  // reticulate code goes here
}

let actionSheet = UIActionSheet(
  title: "정말 계정을 μ‚­μ œν•˜μ‹€ κ±΄κ°€μš”?",
  delegate: self,
  cancelButtonTitle: "μ·¨μ†Œ",
  destructiveButtonTitle: "μ‚­μ œν•΄μ£Όμ„Έμš”"
)

ν•¨μˆ˜ λ§€κ°œλ³€μˆ˜μ— closure κ°€ 2개 이상 μ‘΄μž¬ν•˜λŠ” 경우 λ‚΄λ €μ“°κΈ°λ₯Ό ν•œλ‹€.

SwiftLint: multiple_closures_with_trailing_closure

쒋은 예:

UIView.animate(
  withDuration: 0.25,
  animations: {
    // doSomething()
  },
  completion: { finished in
    // doSomething()
 }
)

single-line closureλŠ” 각 μ€‘κ΄„ν˜Έ 내뢀에 곡간이 μžˆμ–΄μ•Ό ν•œλ‹€.

SwiftLint: closure_spacing

쒋은 예:

let evenSquares = numbers.filter { $0 % 2 == 0 }.map { $0 * $0 }

λ‚˜μœ 예:

let evenSquares = numbers.filter {$0 % 2 == 0}.map {  $0 * $0  }

Operators

infix μ—°μ‚°μžλŠ” μ–‘μͺ½μ— ν•˜λ‚˜μ˜ 곡간이 μžˆμ–΄μ•Ό ν•œλ‹€.

SwiftLint: operator_usage_whitespace
이 κ·œμΉ™μ€ λ²”μœ„ μ—°μ‚°μžμ—λŠ” μ μš©λ˜μ§€ μ•ŠλŠ”λ‹€(예: 1...3) 및 postfix λ˜λŠ” 접두사 μ—°μ‚°μž (예: guest? , -1).

μ™œ?

λ§Žμ€ μ—°μ‚°μžκ°€ μžˆλŠ” λ¬Έμž₯을 μ‹œκ°μ μœΌλ‘œ κ·Έλ£Ήν™”ν•˜κΈ° μœ„ν•΄ 곡백의 폭을 λ‹€μ–‘ν•˜κ²Œ ν•˜κΈ° λ³΄λ‹€λŠ” κ΄„ν˜Έλ₯Ό μ„ ν˜Έν•œλ‹€.

쒋은 예:

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)

λ‚˜μœ 예:

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

Tip: 이 슀크립트λ₯Ό μ‹€ν–‰ν•˜μ—¬ Xcodeμ—μ„œ 일뢀 섀정을 ν™œμ„±ν™”ν•  수 μžˆλ‹€. 예: "Run Script" build phase의 일뢀가 되게 ν•˜λ©΄λœλ‹€.

⬆ back to top

Naming

λͺ…λͺ…은 기본적으둜 Swift API Design Guidelines의 κ°€μ΄λ“œλ₯Ό λ”°λ₯Έλ‹€. μœ„ λ¬Έμ„œμ˜ λ‚΄μš©μ„ 여기에 정리할 수 도 있고, μƒˆλ‘œμš΄ κ°€μ΄λ“œλΌμΈμ„ μΆ”κ°€ν•  μˆ˜λ„ μžˆλ‹€.

νƒ€μž…κ³Ό ν”„λ‘œν† μ½œ 이름은 PascalCaseλ₯Ό μ‚¬μš©ν•˜κ³ , κ·Έ μ™Έμ—λŠ” lowerCamelCase λ₯Ό μ‚¬μš©ν•œλ‹€.

SwiftLint: type_name SwiftLint: identifier_name

쒋은 예:

protocol SpaceThing {
 // ...
}

class SpaceFleet: SpaceThing {

 enum Formation {
   // ...
 }

 class Spaceship {
   // ...
 }

 var ships: [Spaceship] = []
 static let worldName: String = "Earth"

 func addShip(_ ship: Spaceship) {
   // ...
 }
}

let myFleet = SpaceFleet()

μ˜ˆμ™Έ: λ™μΌν•œ μ΄λ¦„μ˜ μ†μ„±μ΄λ‚˜ λ©”μ„œλ“œκ°€ 더 높은 μ•‘μ„ΈμŠ€ μˆ˜μ€€μ„ 가진 경우, private 속성에 밑쀄 접두사λ₯Ό 뢙일 수 μžˆλ‹€.

μ™œ?

μ†μ„±μ΄λ‚˜ λ©”μ„œλ“œλ₯Ό backingν•˜λŠ” 것이 μ„€λͺ…적인 이름을 μ‚¬μš©ν•˜λŠ” 것 보닀 더 읽기 μ‰¬μšΈ 수 μžˆλ‹€. 예λ₯Ό λ“€μ–΄

쒋은 예:

  • Type erasure
public final class AnyRequester<ModelType>: Requester {

 public init<T: Requester>(_ requester: T) where T.ModelType == ModelType {
   _executeRequest = requester.executeRequest
 }

 @discardableResult
 public func executeRequest(
   _ request: URLRequest,
   onSuccess: @escaping (ModelType, Bool) -> Void,
   onFailure: @escaping (Error) -> Void) -> URLSessionCancellable
 {
   return _executeRequest(request, session, parser, onSuccess, onFailure)
 }

 private let _executeRequest: (
   URLRequest,
   @escaping (ModelType, Bool) -> Void,
   @escaping (NSError) -> Void) -> URLSessionCancellable

}
  • 더 ꡬ체적인 νƒ€μž…μ„ μ‚¬μš©ν•˜μ—¬ 덜 ꡬ체적인 νƒ€μž…μ„ backingν•œλ‹€.
final class ExperiencesViewController: UIViewController {
 // We can't name this view since UIViewController has a view: UIView property.
 private lazy var _view = CustomView()

 loadView() {
   self.view = _view
 }
}

boolean νƒ€μž…μ€ isSpaceship, hasSpacesuit 같은 이름을 뢙인닀.

이것은 그듀이 λ‹€λ₯Έ νƒ€μž…μ΄ μ•„λ‹Œ booleanμ΄λΌλŠ” 것을 λΆ„λͺ…νžˆ ν•œλ‹€.

μ•½μ–΄λ‘œ μ‹œμž‘ν•˜λŠ” 경우 μ†Œλ¬Έμžλ‘œ ν‘œκΈ°ν•˜κ³ , κ·Έ μ™Έμ˜ κ²½μš°μ—λŠ” 항상 λŒ€λ¬Έμžλ‘œ ν‘œκΈ°ν•œλ‹€.

쒋은 예:

class URLValidator {

  func isValidURL(_ url: URL) -> Bool {
    // ...
  }

  func isProfileURL(_ url: URL, for userID: String) -> Bool {
    // ...
  }
}

let urlValidator = URLValidator()
let isProfile = urlValidator.isProfileURL(urlToTest, userID: idOfUser)

λ‚˜μœ 예:

class UrlValidator {

  func isValidUrl(_ URL: URL) -> Bool {
    // ...
  }

  func isProfileUrl(_ URL: URL, for userId: String) -> Bool {
    // ...
  }
}

let URLValidator = UrlValidator()
let isProfile = URLValidator.isProfileUrl(URLToTest, userId: IDOfUser)

이름은 κ°€μž₯ 일반적인 뢀뢄을 λ¨Όμ € μ“°κ³  κ°€μž₯ ꡬ체적인 뢀뢄은 λ§ˆμ§€λ§‰μ— 써야 ν•œλ‹€.

"κ°€μž₯ 일반적인"의 μ˜λ―ΈλŠ” 상황에 따라 λ‹€λ₯΄μ§€λ§Œ λŒ€λž΅μ μœΌλ‘œ "μ°ΎλŠ” ν•­λͺ©μ— λŒ€ν•œ 검색 λ²”μœ„λ₯Ό μ’νžˆλŠ” 데 κ°€μž₯ 도움이 λ˜λŠ”" 것을 μ˜λ―Έν•΄μ•Ό ν•œλ‹€. κ°€μž₯ μ€‘μš”ν•œ 것은 일관성을 μ§€ν‚€λŠ” 것이닀.

쒋은 예:

let titleMarginRight: CGFloat
let titleMarginLeft: CGFloat
let bodyMarginRight: CGFloat
let bodyMarginLeft: CGFloat

λ‚˜μœ 예:

let rightTitleMargin: CGFloat
let leftTitleMargin: CGFloat
let bodyRightMargin: CGFloat
let bodyLeftMargin: CGFloat

이름이 λͺ¨ν˜Έν•  경우 이름 νƒ€μž…μ— λŒ€ν•œ 힌트λ₯Ό ν¬ν•¨ν•œλ‹€.

쒋은 예:

let titleText: String
let cancelButton: UIButton

λ‚˜μœ 예:

let title: String
let cancel: UIButton

이벀트 처리 ν•¨μˆ˜λŠ” κ³Όκ±°ν˜•μœΌλ‘œ 이름 μ§“λŠ”λ‹€.

μ£Όμ–΄κ°€ λͺ…ν™•ν•˜λ‹€λ©΄ μƒλž΅ν•  수 μžˆλ‹€.

쒋은 예:

class ExperiencesViewController {

  private func didTapBookButton() {
    // ...
  }

  private func modelDidChange() {
    // ...
  }
}

λ‚˜μœ 예:

class ExperiencesViewController {

  private func handleBookButtonTap() {
    // ...
  }

  private func modelChanged() {
    // ...
  }
}

Object-C μŠ€νƒ€μΌμ˜ μ•½μ–΄ 접두사λ₯Ό ν”Όν•œλ‹€.

μ™œ?

이름간 μΆ©λŒμ„ ν”Όν•˜κΈ° μœ„ν•œ λ°©μ‹μ΄μ—ˆλŠ”λ° Swiftμ—μ„œ 더 이상 ν•„μš”ν•˜μ§€ μ•Šλ‹€. μŠ€μœ„ν”„νŠΈμ˜ νƒ€μž…μ€ ν•΄λ‹Ή νƒ€μž…μ„ ν¬ν•¨ν•˜λŠ” λͺ¨λ“ˆμ— μ˜ν•΄ μžλ™μœΌλ‘œ λ„€μž„μŠ€νŽ˜μ΄μŠ€κ°€ μ§€μ •λ˜λ©°, NS와 같은 클래슀 접두사λ₯Ό μΆ”κ°€ν•΄μ„œλŠ” μ•ˆ λœλ‹€. μ„œλ‘œ λ‹€λ₯Έ λͺ¨λ“ˆμ˜ 두 이름이 μΆ©λŒν•˜λŠ” 경우 μœ ν˜• 이름과 λͺ¨λ“ˆ 이름을 μ ‘λ‘μ‚¬λ‘œ μ—°κ²°ν•˜μ—¬ λͺ¨ν˜Έν•œ 정보λ₯Ό ν•΄μ œν•  수 μžˆλ‹€. 단, ν˜Όλ™ κ°€λŠ₯성이 μžˆμ„ λ•Œλ§Œ λͺ¨λ“ˆ 이름을 μ§€μ •ν•œλ‹€.

쒋은 예:

class Account {
  // ...
}

λ‚˜μœ 예:

class AIRAccount {
  // ...
}

Delegate methodλ₯Ό λ§Œλ“€ λ•Œ 이름 μ—†λŠ” 첫 번째 맀개 λ³€μˆ˜κ°€ delegate sourceκ°€ λ˜μ–΄μ•Ό ν•œλ‹€.

μ™œ?

쒋은 예:

func namePickerView(_ namePickerView: NamePickerView, didSelectName name: String)
func namePickerViewShouldReload(_ namePickerView: NamePickerView) -> Bool

λ‚˜μœ 예:

func didSelectName(namePicker: NamePickerViewController, name: String)
func namePickerShouldReload() -> Bool

ν•¨μˆ˜ 이름 μ•žμ—λŠ” λ˜λ„λ‘μ΄λ©΄ get을 뢙이지 μ•ŠλŠ”λ‹€.

쒋은 예:

func name(for user: User) -> String?

λ‚˜μœ 예:

func getName(for user: User) -> String?

λ·°(UIView 및 ν•˜μœ„ νƒ€μž…)νƒ€μž… λ³€μˆ˜μ˜ 이름을 지을 λ•Œ νƒ€μž… 이름을 뒀에 λΆ™μ—¬μ€€λ‹€.

νƒ€μž… 이름을 쀄이지 μ•ŠλŠ”λ‹€. UICollectionViewCell, UITableViewCell 의 κ²½μš°λŠ” λ„ˆλ¬΄ κΈΈκΈ° λ•Œλ¬Έμ— Cell만 ν™œμš©ν•œλ‹€.

μ™œ?

μ–΄λ–€ κΈ°λŠ₯의 뷰인지 λ°”λ‘œ μ•Œ 수 μžˆμ–΄μ„œ 가독성이 쒋아진닀.

쒋은 예:

@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var moreButton: UIButton!
@IBOutlet weak var collectionView: UICollectionView!
@IBOutlet weak var bannerCell: UITableViewCell!

λ‚˜μœ 예:

@IBOutlet weak var title: UILabel!
@IBOutlet weak var moreBtn: UIButton!
@IBOutlet weak var collection: UICollectionView!
@IBOutlet weak var bannerTableViewCell: UITableViewCell!

ν…ŒμŠ€νŠΈ 이름을 ν•œκΈ€λ‘œ μž‘μ„±ν•˜λŠ” 것을 ν—ˆμš©ν•œλ‹€.

μ™œ?

더 λΉ λ₯΄κ²Œ 이름을 μ§€μ„μˆ˜ 있고, ν…ŒμŠ€νŠΈλ₯Ό 이해할 수 μžˆλ‹€. ν•œκΈ€λ‘œ μž‘μ„±ν•˜λ©΄ μ˜¬λ°”λ₯Έ ν…ŒμŠ€νŠΈμΈμ§€ νŒλ‹¨ν•˜κΈ°κ°€ 더 쉽닀.

⬆ back to top

Style

Basic Style

μ‰½κ²Œ νƒ€μž…μ΄ 좔둠될 수 μžˆλ‹€λ©΄ νƒ€μž…μ„ ν¬ν•¨ν•˜μ§€ μ•ŠλŠ”λ‹€.

SwiftLint: redundant_type_annotation

쒋은 예:

let host = Host()
let selector = #selector(viewDidLoad)
view.backgroundColor = .red
let toView = context.view(forKey: .to)
let view = UIView(frame: .zero)

λ‚˜μœ 예:

let host: Host = Host()
let selector = #selector(ViewController.viewDidLoad)
view.backgroundColor = UIColor.red
let toView = context.view(forKey: UITransitionContextViewKey.to)
let view = UIView(frame: CGRect.zero)

언어에 μ˜ν•΄ μš”κ΅¬λ˜κ±°λ‚˜ λͺ¨ν˜Έν•¨μ„ ν”Όν•˜κΈ° μœ„ν•œ κ²½μš°κ°€ μ•„λ‹ˆλΌλ©΄ selfλ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠλŠ”λ‹€.

SwiftFormat: redundantSelf

쒋은 예:

final class Listing {

  init(capacity: Int, allowsPets: Bool) {
    self.capacity = capacity
    isFamilyFriendly = !allowsPets
  }

  private let isFamilyFriendly: Bool
  private var capacity: Int

  private func increaseCapacity(by amount: Int) {
    capacity += amount
    save()
  }
}

λ‚˜μœ 예:

final class Listing {

  init(capacity: Int, allowsPets: Bool) {
    self.capacity = capacity
    self.isFamilyFriendly = !allowsPets // `self.` not required here
  }

  private let isFamilyFriendly: Bool
  private var capacity: Int

  private func increaseCapacity(by amount: Int) {
    self.capacity += amount
    self.save()
  }
}

closure μ—μ„œ self λ₯Ό self 둜 λ°”μΈλ”©ν•œλ‹€.

SwiftFormat: strongifiedSelf

쒋은 예:

class MyClass {

  func request(completion: () -> Void) {
    API.request() { [weak self] response in
      guard let self = self else { return }
      // Do work
      completion()
    }
  }
}

λ‚˜μœ 예:

class MyClass {

  func request(completion: () -> Void) {
    API.request() { [weak self] response in
      guard let strongSelf = self else { return }
      // Do work
      completion()
    }
  }
}

tuple λ©€λ²„μ˜ 이름을 μ§€μ •ν•˜μ—¬ λ”μš± λͺ…ν™•ν•˜κ²Œ ν‘œμ‹œν•œλ‹€.

SwiftLint: large_tuple
κ²½ν—˜μƒ 3개 μ΄μƒμ˜ ν•„λ“œκ°€ μžˆλ‹€λ©΄, μ•„λ§ˆ structλ₯Ό μ‚¬μš©ν•΄μ•Ό ν•  것이닀.

쒋은 예:

func whatever() -> (x: Int, y: Int) {
 return (x: 4, y: 4)
}

let coord = whatever()
coord.x
coord.y

λ‚˜μœ 예:

 func whatever() -> (Int, Int) {
 return (4, 4)
}
let thing = whatever()
print(thing.0)

λΆˆν•„μš”ν•œ κ΄„ν˜ΈλŠ” μƒλž΅ν•œλ‹€.

SwiftLint: control_statement SwiftLint: unneeded_parentheses_in_closure_argument SwiftFormat: redundantParens

쒋은 예:

if userCount > 0 { ... }
switch someValue { ... }
let evens = userCounts.filter { number in number % 2 == 0 }
let squares = userCounts.map { $0 * $0 }

λ‚˜μœ 예:

if (userCount > 0) { ... }
switch (someValue) { ... }
let evens = userCounts.filter { (number) in number % 2 == 0 }
let squares = userCounts.map() { $0 * $0 }

NSRange λ“±μ˜ 경우 Make() ν•¨μˆ˜ λŒ€μ‹  μƒμ„±μžλ₯Ό μ‚¬μš©ν•œλ‹€.

SwiftLint: legacy_constructor

쒋은 예:

let range = NSRange(location: 10, length: 5)

λ‚˜μœ 예:

let range = NSMakeRange(10, 5)

String은 +λ₯Ό μ‚¬μš©ν•˜μ—¬ μ—°μ‚°ν•˜μ§€ μ•ŠλŠ”λ‹€.

μ™œ?

컴파일 μ‹œκ°„μ— 영ν–₯을 μ£ΌλŠ” μ£Όμš” μ›μΈμ΄λ―€λ‘œ μ§€μ–‘ν•œλ‹€.

쒋은 예:

let firstName = "κΉ€"
let secondName = "ν‹°λ“œ"
let wholeName = "\(firstName)\(secondName)"

λ‚˜μœ 예:

let firstName = "κΉ€"
let secondName = "ν‹°λ“œ"
let wholeName = firstName+secondName

Closures

λ§€κ°œλ³€μˆ˜μ™€ 리턴 νƒ€μž…μ΄ μ—†λŠ” closure μ •μ˜μ‹œμ—λŠ” () -> Voidλ₯Ό μ‚¬μš©ν•œλ‹€.

SwiftLint: empty_parameters SwiftLint: void_return SwiftFormat: void

μ™œ?

가독성이 ν–₯μƒλœλ‹€.

쒋은 예:

let completionBlock: (() -> Void)?

λ‚˜μœ 예:

let completionBlock: (() -> ())?
let completionBlock: ((Void) -> (Void))?

μ‚¬μš©λ˜μ§€ μ•Šμ€ closure λ§€κ°œλ³€μˆ˜μ˜ 이름을 밑쀄(_)둜 μ§€μ •ν•œλ‹€.

SwiftLint: unused_closure_parameter

μ™œ?

μ–΄λ–€ λ§€κ°œλ³€μˆ˜κ°€ μ‚¬μš©λ˜κ³  μ–΄λ–€ λ§€κ°œλ³€μˆ˜κ°€ μ‚¬μš©λ˜μ§€ μ•ŠλŠ”μ§€ λͺ…ν™•νžˆ ν•¨μœΌλ‘œμ¨ closureλ₯Ό 읽을 λ•Œ ν•„μš”ν•œ 인지 μ˜€λ²„ν—€λ“œκ°€ κ°μ†Œν•œλ‹€.

쒋은 예:

someAsyncThing() { _, _, argument3 in
  print(argument3)
}

λ‚˜μœ 예:

someAsyncThing() { argument1, argument2, argument3 in
  print(argument3)
}

closure λ§€κ°œλ³€μˆ˜κ°€ λ§ˆμ§€λ§‰ 끝에 ν•˜λ‚˜ μžˆλ‹€λ©΄ κ°€λŠ₯ν•œ ν•œ trailing closure ꡬ문을 μ‚¬μš©ν•œλ‹€.

SwiftFormat: trailingClosures
λ‘˜ 이상이라면 ν‰λ²”ν•˜κ²Œ μ‚¬μš©ν•œλ‹€.

쒋은 예:

UIView.animate(withDuration: 1.0) {
    self.myView.alpha = 0
}

UIView.animate(withDuration: 1.0, animations: {
 self.myView.alpha = 0
}, completion: { finished in
 self.myView.removeFromSuperview()
})

λ‚˜μœ 예:

UIView.animate(withDuration: 1.0, animations: {
 self.myView.alpha = 0
})

UIView.animate(withDuration: 1.0, animations: {
 self.myView.alpha = 0
}) { f in
 self.myView.removeFromSuperview()
}

Properties

μ—°μ‚° 속성이 μ½κΈ°μ „μš©μ΄λΌλ©΄ get μ ˆμ„ μƒλž΅ν•œλ‹€.

SwiftLint: implicit_getter SwiftFormat: redundantGet

쒋은 예:

var diameter: Double {
 return radius * 2
}

λ‚˜μœ 예:

var diameter: Double {
 get {
   return radius * 2
 }
}

Array<T>와 Dictionary<T: U> λ³΄λ‹€λŠ” [T], [T: U]λ₯Ό μ‚¬μš©ν•œλ‹€.

SwiftLint: syntactic_sugar SwiftFormat: typeSugar

쒋은 예:

var messages: [String]?
var names: [Int: String]?

λ‚˜μœ 예:

var messages: Array<String>?
var names: Dictionary<Int, String>?

Optionals

? λŠ” regular optionals 을 μ˜λ―Έν•œλ‹€.
! λŠ” implicitly unwrapped optionals (μ•”λ¬΅μ μœΌλ‘œ 포μž₯이 ν’€λ¦¬λŠ” μ˜΅μ…”λ„)을 μ˜λ―Έν•œλ‹€. κ°€μ΄λ“œμ—μ„œλŠ” ? 와 ! 둜 λŒ€μ²΄ν•˜μ—¬ ν‘œν˜„ν•œλ‹€.

κ°€λŠ₯ν•œ ν•œ ? 둜 μ„ μ–Έν•œλ‹€. μ‚¬μš©μ „ λ°˜λ“œμ‹œ μ΄ˆκΈ°ν™”λ˜λŠ” κ²½μš°μ—λ§Œ ! 둜 μ„ μ–Έν•œλ‹€.

μ™œ?

! λŠ” λŸ°νƒ€μž„μ‹œ ν¬λž˜μ‹œμ˜ 원인이 될 수 μžˆλ‹€.

  • κ·ΈλŸ¬λ‚˜, λΌμ΄ν”„νƒ€μž„μ΄ UI λΌμ΄ν”„μ‚¬μ΄ν΄μ•ˆμ— μžˆλŠ” μ‚¬μš©μž μΈν„°νŽ˜μ΄μŠ€ κ°μ²΄λŠ” ! λ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€. μ™œλƒν•˜λ©΄ 그것듀은 no-nil이 보μž₯되기 λ•Œλ¬Έμ΄λ‹€.

    • XIB νŒŒμΌμ΄λ‚˜ μŠ€ν† λ¦¬λ³΄λ“œμ˜ 객체에 μ—°κ²°λœ @IBOutlet 속성, μ™ΈλΆ€μ—μ„œ μ£Όμž…λ˜λŠ” 속성 λ“± κ·ΈλŸ¬ν•œ 속성을 ? 둜 λ§Œλ“œλŠ” 것은 μ‚¬μš©μžκ°€ 포μž₯을 ν’€κΈ°μ—λŠ” λ„ˆλ¬΄ λ§Žμ€ 뢀담을 쀄 수 μžˆλ‹€.
  • λ˜ν•œ ! λŠ” λ‹¨μœ„ν…ŒμŠ€νŠΈμ—μ„œ ν—ˆμš©λœλ‹€. μ΄λŠ” μœ„μ˜ UI 개체 μ‹œλ‚˜λ¦¬μ˜€μ™€ μœ μ‚¬ν•œ 이유 λ•Œλ¬Έμ΄λ‹€.

    • ν…ŒμŠ€νŠΈ fixture의 수λͺ…은 μ’…μ’… ν…ŒμŠ€νŠΈμ˜ μ΄ˆκΈ°ν™”ν•¨μˆ˜κ°€ μ•„λ‹ˆλΌ ν…ŒμŠ€νŠΈμ˜ setUp() λ©”μ„œλ“œμ—μ„œ μ‹œμž‘ν•˜μ—¬ 각 ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν•˜κΈ° 전에 μž¬μ„€μ •ν•  수 μžˆλ‹€.

? λ‚˜ ! λ³€μˆ˜μ˜ 값은 κ°€λŠ₯ν•œ ν•œ μ˜΅μ…”λ„ λ°”μΈλ”©μœΌλ‘œ μ–»λŠ”λ‹€.

μ™œ?

! λŠ” λŸ°νƒ€μž„μ‹œ ν¬λž˜μ‹œμ˜ 원인이 될 수 μžˆλ‹€.

? λ‚˜ ! λ³€μˆ˜λ‘œ λ©”μ„œλ“œ ν˜ΈμΆœν• λ•ŒλŠ” μ˜΅μ…”λ„ 체이닝을 μ‚¬μš©ν•œλ‹€.

쒋은 예:

textContainer?.textLabel?.setNeedsDisplay()

값을 μ‚¬μš©ν•  ν•„μš”κ°€ μ—†λ‹€λ©΄ μ˜΅μ…”λ„ 바인딩을 μ‚¬μš©ν•˜μ§€ μ•Šκ³  nil을 μ²΄ν¬ν•œλ‹€.

SwiftLint: unused_optional_binding

μ™œ?

μ˜΅μ…”λ„ 바인딩을 μ‚¬μš©ν•˜λŠ” λŒ€μ‹  nil을 μ²΄ν¬ν•˜λ©΄ κ·Έ μ‹€ν–‰λ¬Έμ˜ μ˜λ„κ°€ 무엇인지 μ¦‰μ‹œ μ•Œ 수 μžˆλ‹€.

쒋은 예:

var thing: Thing?

if nil != thing {
  doThing()
}

λ‚˜μœ 예:

var thing: Thing?

if let _ = thing {
  doThing()
}

Comments

주석은 기본적으둜 Swift API Design Guidelines 의 κ°€μ΄λ“œλ₯Ό λ”°λ₯Έλ‹€. μœ„ λ¬Έμ„œμ˜ λ‚΄μš©μ„ 여기에 정리할 수 도 있고, μƒˆλ‘œμš΄ κ°€μ΄λ“œλΌμΈμ„ μΆ”κ°€ν•  μˆ˜λ„ μžˆλ‹€.

⬆ back to top

Patterns

Basic Patterns

κ°•μ œ μ–Έλž©ν•‘ μ˜΅μ…˜μ„ μ‚¬μš©ν•˜μ§€ μ•Šκ³  κ°€λŠ₯ν•˜λ©΄ init νƒ€μž„μ— 속성을 μ΄ˆκΈ°ν™”ν•˜λŠ” 것을 μ„ ν˜Έν•œλ‹€.

SwiftLint: implicitly_unwrapped_optional
λ‘λ“œλŸ¬μ§„ μ˜ˆμ™ΈλŠ” UIViewController의 view 속성이닀.

쒋은 예:

class MyClass: NSObject {

  init() {
    someValue = 0
    super.init()
  }

  var someValue: Int
}

λ‚˜μœ 예:

class MyClass: NSObject {

  init() {
    super.init()
    someValue = 5
  }

  var someValue: Int!
}

init()μ—μ„œ 의미 μžˆλŠ” μž‘μ—…μ΄λ‚˜ time-intensive μž‘μ—…μ„ μˆ˜ν–‰ν•˜λŠ” 것을 ν”Όν•œλ‹€.

λ°μ΄ν„°λ² μ΄μŠ€ μ—°κ²° μ—΄κΈ°, λ„€νŠΈμ›Œν¬ μš”μ²­, λ””μŠ€ν¬μ—μ„œ λŒ€λŸ‰μ˜ 데이터 읽기 λ“±μ˜ μž‘μ—…μ„ μˆ˜ν–‰ν•˜μ§€ μ•ŠλŠ”λ‹€. 객체λ₯Ό μ‚¬μš©ν•  μ€€λΉ„κ°€ 되기 전에 μ΄λŸ¬ν•œ μž‘μ—…μ„ μˆ˜ν–‰ν•΄μ•Ό ν•˜λŠ” 경우 start() λ©”μ„œλ“œκ³Ό 같은 것을 λ§Œλ“ λ‹€.

λ³΅μž‘ν•œ 속성 observersλ₯Ό λ©”μ†Œλ“œλ‘œ μΆ”μΆœν•œλ‹€.

μ™œ?

μ΄λŠ” 쀑첩성을 κ°μ†Œμ‹œν‚€κ³ , 속성 μ„ μ–ΈμœΌλ‘œ λΆ€ν„° μ‚¬μ΄λ“œμ΄νŽ™νŠΈλ₯Ό 뢄리해내고, oldValue와 같이 μ•”λ¬΅μ μœΌλ‘œ μ „λ‹¬λ˜λŠ” λ§€κ°œλ³€μˆ˜λ₯Ό λͺ…μ‹œμ μœΌλ‘œ μ‚¬μš©ν•˜κ²Œ λ§Œλ“ λ‹€.

쒋은 예:

class TextField {
  var text: String? {
    didSet { textDidUpdate(from: oldValue) }
  }

  private func textDidUpdate(from oldValue: String?) {
    guard oldValue != text else {
      return
    }

    // Do a bunch of text-related side-effects.
  }
}

λ‚˜μœ 예:

class TextField {
  var text: String? {
    didSet {
      guard oldValue != text else {
        return
      }

      // Do a bunch of text-related side-effects.
    }
  }
}

λ³΅μž‘ν•œ callback block을 λ©”μ„œλ“œλ‘œ μΆ”μΆœν•œλ‹€.

μ™œ?

이것은 weak-selfκ°€ μ•ΌκΈ°ν•˜λŠ” λ³΅μž‘μ„±μ„ 막고 쀑첩성을 κ°μ†Œμ‹œν‚¨λ‹€.

쒋은 예:

class MyClass {

  func request(completion: () -> Void) {
    API.request() { [weak self] response in
      guard let self = self else { return }
      self.doSomething(with: self.property, response: response)
      completion()
    }
  }

  func doSomething(with nonOptionalParameter: SomeClass, response: SomeResponseClass) {
    // Processing and side effects
  }
}

λ‚˜μœ 예:

class MyClass {

  func request(completion: () -> Void) {
    API.request() { [weak self] response in
      if let self = self {
        // Processing and side effects
      }
      completion()
    }
  }
}

scope μ‹œμž‘ 뢀뢄에 guard μ‚¬μš©μ„ μ„ ν˜Έν•œλ‹€.

μ™œ?

λͺ¨λ“  guard 문을 상단에 λͺ¨μ•„λ†“λŠ” 것이 λΉ„μ¦ˆλ‹ˆμŠ€ 둜직과 μ„žμΌλ•Œ code block에 λŒ€ν•΄ μΆ”λ‘ ν•˜λŠ” 것이 더 쉽닀.

if λ¬Έ λ³΄λ‹€λŠ” guard 문을 μ‚¬μš©ν•˜μ—¬ 쀑첩을 μ΅œμ†Œν™”ν•œλ‹€.

μ™œ?

νƒˆμΆœ μ‘°κ±΄μ—μ„œ throwsλ‚˜ return λ¬Έ 등을 μ‹€ν–‰ν•˜κ±°λ‚˜ μ˜΅μ…”λ„ 바인딩을 ν•΄μ•Όν•  λ•Œ guard 문을 μ‚¬μš©ν•˜λ©΄ 쀑첩도λ₯Ό μ€„μ—¬μ„œ 가독성과 μœ μ§€λ³΄μˆ˜μ„±μ„ 높일 수 μžˆλ‹€.

쒋은 예:

func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
 guard let context = context else {
   throw FFTError.noContext
 }
 guard let inputData = inputData else {
   throw FFTError.noInputData
 }

 // use context and input to compute the frequencies
 return frequencies
}

guard
 let number1 = number1,
 let number2 = number2,
 let number3 = number3
 else {
   fatalError("impossible")
}
// do something with numbers

λ‚˜μœ 예:

func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
 if let context = context {
   if let inputData = inputData {
     // use context and input to compute the frequencies

     return frequencies
   } else {
     throw FFTError.noInputData
   }
 } else {
   throw FFTError.noContext
 }
}

if let number1 = number1 {
 if let number2 = number2 {
   if let number3 = number3 {
     // do something with numbers
   } else {
     fatalError("impossible")
   }
 } else {
   fatalError("impossible")
 }
} else {
 fatalError("impossible")
}

μ—¬λŸ¬ μ’…λ£Œμ§€μ μ—μ„œ 정리 μ½”λ“œκ°€ ν•„μš”ν•œ 경우 defer block을 μ‚¬μš©ν•˜λŠ” 것을 κ³ λ €ν•œλ‹€.

μ ‘κ·Ό μ œμ–΄λŠ” κ°€λŠ₯ν•œ ν•œ μ—„κ²©ν•œ μˆ˜μ€€μ΄μ–΄μ•Ό ν•œλ‹€.

그런 행동이 ν•„μš”ν•˜μ§€ μ•ŠλŠ” ν•œ openλ³΄λ‹€λŠ” public, fileprivateλ³΄λ‹€λŠ” private μͺ½μ„ μ„ ν˜Έν•œλ‹€.

μ ‘κ·Ό μ œμ–΄ μ§€μ •μžκ°€ 맨 μ•žμ— λ†“μ΄κ²Œ ν•œλ‹€.

SwiftFormat: specifiers

쒋은 예:

class TimeMachine {
  private dynamic lazy var fluxCapacitor = FluxCapacitor()
}

λ‚˜μœ 예:

class TimeMachine {
  lazy dynamic private var fluxCapacitor = FluxCapacitor()
}

μ˜ˆμ™Έ: static μ§€μ •μžλ‚˜ @IBAction, @IBOutlet, @discardableResult 같은 attributes

μ΅œμƒμœ„ μˆ˜μ€€ νƒ€μž…, ν•¨μˆ˜, λ³€μˆ˜μ— λͺ…μ‹œμ μœΌλ‘œ μ ‘κ·Ό μ œμ–΄λ₯Ό μ§€μ •ν•œλ‹€.

SwiftFormat: redundantExtensionACL
ν•˜μ§€λ§Œ, νƒ€μž… μ •μ˜ λ‚΄μ—μ„œλŠ” 같은 μˆ˜μ€€μ˜ μ ‘κ·Όμ œμ–΄λ₯Ό μƒλž΅ν•œλ‹€.

μ™œ?

λͺ…μ‹œμ μœΌλ‘œ ν‘œμ‹œν•˜κ²Œ 되면 항상 μ‹ μ€‘ν•˜κ²Œ κ²°μ •ν•˜κ²Œ λœλ‹€. μ •μ˜ λ‚΄μ—μ„œ λ™μΌν•œ μ ‘κ·Ό μ œμ–΄ μ§€μ •μžλ₯Ό μž¬μ‚¬μš©ν•˜λŠ” 것은 쀑볡이며, 일반적으둜 기본값이 ν•©λ‹Ήν•˜λ‹€.

쒋은 예:

puplic struct Book {}
internal apiKey : String
private func change(options: [Option]) {}

public class ImageDownloader {
  var links = ImageLinks()
  private var queue = OperationQueue()
}

λ‚˜μœ 예:

struct Book {}
apiKey : String
func change(options: [Option]) {}

public class ImageDownloader {
  public var links = ImageLinks()
  private var queue = OperationQueue()
}

μ ‘κ·Όμ œμ–΄λŠ” extension뿐만 μ•„λ‹ˆλΌ method, property에도 각각 μ„€μ •ν•œλ‹€.

쒋은 예:

private extension ViewController {
    private func setupViews() {}
}

λ‚˜μœ 예:

private extension ViewController {
    func setupViews() {}
}

κ°€λŠ₯ν•œ ν•œ μ „μ—­ ν•¨μˆ˜λ₯Ό μ •μ˜ν•˜μ§€ μ•ŠλŠ”λ‹€.

νƒ€μž… μ •μ˜μ•ˆμ—μ„œ λ©”μ„œλ“œλ₯Ό μ •μ˜ν•˜λŠ” 것을 μ„ ν˜Έν•œλ‹€.

μ™œ?

이것은 가독성에 도움이 λœλ‹€. 더 빨리 찾을 수 μžˆλ‹€. μ „μ—­ ν•¨μˆ˜λŠ” νŠΉμ • νƒ€μž…μ΄λ‚˜ μΈμŠ€ν„΄μŠ€μ™€ 관련이 없을 λ•Œ κ°€μž₯ μ μ ˆν•˜λ‹€.

쒋은 예:

class Person {
  var bornAt: TimeInterval

  var age: Int {
    // ...
  }

  func jump() {
    // ...
  }
}

λ‚˜μœ 예:

func age(of person, bornAt timeInterval) -> Int {
  // ...
}

func jump(person: Person) {
  // ...
}

μƒμˆ˜κ°€ fileprivate, private인 경우 파일의 μ΅œμƒμœ„ λ ˆλ²¨μ— λ†“λŠ” 것을 μ„ ν˜Έν•œλ‹€.

public λ˜λŠ” internal인 경우 namespacing 을 μœ„ν•΄ static μ†μ„±μœΌλ‘œ μ •μ˜ν•œλ‹€.

쒋은 예:

private let privateValue = "secret"

public class MyClass {

 public static let publicValue = "something"

 func doSomething() {
   print(privateValue)
   print(MyClass.publicValue)
 }
}

public λ˜λŠ” internal μƒμˆ˜ 및 ν•¨μˆ˜λ₯Ό λ„€μž„μŠ€νŽ˜μ΄μŠ€λ‘œ λ¬Άκ³  싢을 λ•ŒλŠ” case μ—†λŠ” enum을 μ‚¬μš©ν•œλ‹€.

SwiftLint: convenience_type
λ„€μž„μŠ€νŽ˜μ΄μŠ€ 없이 μ „μ—­ μƒμˆ˜μ™€ ν•¨μˆ˜λ₯Ό λ§Œλ“€μ§€ μ•ŠλŠ”λ‹€. λͺ…λ£Œν•¨μ„ μœ„ν•΄μ„œλΌλ©΄ λ„€μž„μŠ€νŽ˜μ΄μŠ€λ₯Ό μ œν•œμ—†μ΄ μ€‘μ²©ν•œλ‹€.

μ™œ?

case μ—†λŠ” enum은 μΈμŠ€ν„΄μŠ€λ‘œ λ§Œλ“€ 수 μ—†κΈ° λ•Œλ¬Έμ— μˆœμˆ˜ν•œ λ„€μž„μŠ€νŽ˜μ΄μŠ€λ‘œ 잘 λ§žλŠ”λ‹€.

쒋은 예:

enum Environment {

 enum Earth {
   static let gravity = 9.8
 }

 enum Moon {
   static let gravity = 1.6
 }
}

μ™ΈλΆ€ μ†ŒμŠ€λ‘œ λΆ€ν„° λ§€ν•‘λ˜λŠ” κ²½μš°κ°€ μ•„λ‹ˆλΌλ©΄, Swift의 μžλ™μœΌλ‘œ λ§€κ²¨μ§€λŠ” enum 값을 κ·ΈλŒ€λ‘œ μ‚¬μš©ν•œλ‹€.

값을 λͺ…μ‹œμ μœΌλ‘œ ν• λ‹Ήν•œλ‹€λ©΄ κ·Έ 이유λ₯Ό μ„€λͺ…ν•˜λŠ” commentλ₯Ό μΆ”κ°€ν•œλ‹€.

μ™œ?

μ‚¬μš©μž 였λ₯˜λ₯Ό μ΅œμ†Œν™”ν•˜κ³  가독성을 ν–₯μƒμ‹œν‚€λ©° μ½”λ“œλ₯Ό 더 빨리 μ“°λ €λ©΄ Swift의 μžλ™ enum 값을 μ‚¬μš©ν•œλ‹€. κ·ΈλŸ¬λ‚˜ 값이 μ™ΈλΆ€ μ†ŒμŠ€μ— λ§€ν•‘λ˜κ±°λ‚˜(예: λ„€νŠΈμ›Œν¬ μš”μ²­μ—μ„œ 제곡됨) λ°”μ΄λ„ˆλ¦¬ 전역에 걸쳐 사라지지 μ•Šκ³  μœ μ§€λ˜λŠ” 경우, λͺ…μ‹œμ μœΌλ‘œ 값을 μ •μ˜ν•˜κ³  이 값이 λ§€ν•‘λ˜λŠ” λŒ€μƒμ„ λ¬Έμ„œν™”ν•œλ‹€. μ΄λ ‡κ²Œν•˜λ©΄ λˆ„κ΅°κ°€κ°€ 쀑간에 μƒˆλ‘œμš΄ 값을 μΆ”κ°€ν•œλ‹€κ³ ν•΄λ„ κΈ°μ‘΄ 것을 κΉ¨λœ¨λ¦¬μ§€ μ•Šκ²Œ λœλ‹€.

쒋은 예:

enum ErrorType: String {
  case error
  case warning
}

/// 이것은 logging serviceμ—μ„œ μ‚¬μš©λœλ‹€. λͺ…μ‹œμ μΈ 값은 λ°”μ΄λ„ˆλ¦¬ 전역에 걸쳐 일관성을 보μž₯ν•œλ‹€.
// swiftlint:disable redundant_string_enum_value
enum UserType: String {
  case owner = "owner"
  case manager = "manager"
  case member = "member"
}

// swiftlint:enable redundant_string_enum_value
enum Planet: Int {
  case mercury
  case venus
  case earth
  case mars
  case jupiter
  case saturn
  case uranus
  case neptune
}

/// μ΄λŸ¬ν•œ 값은 μ„œλ²„μ—μ„œ μ œκ³΅ν•˜λ―€λ‘œ, 여기에 λͺ…μ‹œμ μœΌλ‘œ λ§€μΉ­λ˜λŠ” 값을 μ„€μ •ν•œλ‹€.
enum ErrorCode: Int {
  case notEnoughMemory = 0
  case invalidResource = 1
  case timeOut = 2
}

λ‚˜μœ 예:

enum ErrorType: String {
  case error = "error"
  case warning = "warning"
}

enum UserType: String {
  case owner
  case manager
  case member
}

enum Planet: Int {
  case mercury = 0
  case venus = 1
  case earth = 2
  case mars = 3
  case jupiter = 4
  case saturn = 5
  case uranus = 6
  case neptune = 7
}

enum ErrorCode: Int {
  case notEnoughMemory
  case invalidResource
  case timeOut
}

μ˜ˆμ™Έ: Codable을 μ€€μˆ˜ν•˜λŠ” νƒ€μž…μ˜ 경우 λ‚΄λΆ€μ˜ enum caseμ—μ„œλŠ” λͺ…μ‹œμ μΈ 값을 μ •μ˜ν•œλ‹€

κ°€λŠ₯ν•œ ν•œ immutable 값을 μ„ ν˜Έν•œλ‹€.

μƒˆ μ»¬λ ‰μ…˜μ— μΆ”κ°€ν•˜λŠ” λŒ€μ‹  map κ³Ό compactMap 을 μ‚¬μš©ν•œλ‹€. λ³€κ²½ κ°€λŠ₯ν•œ μ»¬λ ‰μ…˜μ—μ„œ μš”μ†Œλ₯Ό μ œκ±°ν•˜λŠ” λŒ€μ‹  filterλ₯Ό μ‚¬μš©ν•œλ‹€.

μ™œ?

mutable λ³€μˆ˜λŠ” λ³΅μž‘μ„±μ„ μ¦κ°€μ‹œν‚€λ―€λ‘œ κ°€λŠ₯ν•œ ν•œ λ²”μœ„λ₯Ό μ’νžˆλ„λ‘ ν•œλ‹€.

쒋은 예:

let results = input.map { transform($0) }

let results = input.compactMap { transformThatReturnsAnOptional($0) }

λ‚˜μœ 예:

var results = [SomeType]()
for element in input {
  let result = transform(element)
  results.append(result)
}

var results = [SomeType]()
for element in input {
  if let result = transformThatReturnsAnOptional(element) {
    results.append(result)
  }
}

Type methodλŠ” 기본적으둜 static 으둜 μ •μ˜ν•œλ‹€.

λ©”μ†Œλ“œλ₯Ό μž¬μ •μ˜ν•΄μ•Όν•˜λŠ” κ²½μš°μ—λŠ” class ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•œλ‹€.

쒋은 예:

class Fruit {
  static func eatFruits(_ fruits: [Fruit]) { ... }
}

λ‚˜μœ 예:

class Fruit {
  class func eatFruits(_ fruits: [Fruit]) { ... }
}

ν΄λž˜μŠ€λŠ” 기본적으둜 final둜 μ •μ˜ν•œλ‹€.

클래슀λ₯Ό μž¬μ •μ˜ν•΄μ•Όν•˜λŠ” κ²½μš°μ—λŠ” final ν‚€μ›Œλ“œλ₯Ό μƒλž΅ν•œλ‹€.

μ™œ?

ν˜ΈμΆœν•  class κ΅¬ν˜„μ²΄λ₯Ό μ„ νƒν•˜λŠ” 과정은 λŸ°νƒ€μž„ λ‹¨κ³„μ—μ„œ μˆ˜ν–‰λ˜λ©°, μ΄λŠ” dynamic dispatch둜 μ•Œλ €μ Έμžˆλ‹€. λŸ°νƒ€μž„ μ˜€λ²„ν—€λ“œμ˜ 일정뢀뢄은 클래슀 상속을 μ‚¬μš©ν•˜λŠ” 것과 관련이 μžˆλ‹€. final ν‚€μ›Œλ“œλŠ” λ©”μ„œλ“œλ‚˜ ν•¨μˆ˜μ˜ 경우 μ˜€λ²„λΌμ΄λ“œ ν•  수 μ—†κ²Œ ν•˜κ³ , ν΄λž˜μŠ€λŠ” μ„œλΈŒν΄λž˜μ‹± ν•  수 μ—†κ²Œ ν•œλ‹€. 이 ν‚€μ›Œλ“œλŠ” λŸ°νƒ€μž„μ—μ„œ λ©”μ„œλ“œλ‚˜ 속성을 직접 ν˜ΈμΆœν•  수 있게 해쀄 것이며, μ•½κ°„μ˜ μ„±λŠ₯ ν–₯상을 κ°€μ Έμ˜¨λ‹€. μŠ€μœ„ν”„νŠΈ 4 ν”„λ‘œν† μ½œμ§€ν–₯ ν”„λ‘œκ·Έλž˜λ° 3/e μ—μ„œ μš”μ•½ 인용

쒋은 예:

final class SettingsRepository {
 // ...
}

λ‚˜μœ 예:

class SettingsRepository {
 // ...
}

switch λ¬Έμ—μ„œ enumκ°’ 에 λŒ€ν•΄ 가급적이면 default μΌ€μ΄μŠ€λ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠλŠ”λ‹€.

μ™œ?

λͺ¨λ“  caseλ₯Ό μ—΄κ±°ν•˜λŠ” 것은 κ°œλ°œμžμ™€ κ²€ν† μžκ°€ μƒˆλ‘œμš΄ caseκ°€ 좔가될 λ•Œ λͺ¨λ“  switch λ¬Έμž₯의 정확성을 κ³ λ €ν•˜λ„λ‘ ν•΄μ€€λ‹€.

쒋은 예:

switch anEnum {
case .a:
  // Do something
case .b, .c:
  // Do something else.
}

λ‚˜μœ 예:

switch anEnum {
case .a:
  // Do something
default:
  // Do something else.
}

switch λ¬Έμ—μ„œ case λ‚΄ μ²˜λ¦¬ν•΄μ•Ό ν•  μ½”λ“œκ°€ μžˆλŠ” κ²½μš°μ—λŠ” break μ‚¬μš©ν•˜μ§€ μ•ŠλŠ”λ‹€.

μ™œ?

case λ‚΄ μ²˜λ¦¬ν•΄μ•Ό ν•  μ½”λ“œκ°€ μžˆλŠ” 경우, case 내에 μž‘μ„±λœ breakλŠ” μ»΄νŒŒμΌλŸ¬μ—κ²Œ 일만 μΆ”κ°€ μ‹œν‚€κ³ , μ‹€ν–‰λ˜λŠ” μ˜λ―ΈλŠ” μ—†λŠ” μ½”λ“œκ°€ λ˜μ–΄λ²„λ¦°λ‹€.

쒋은 예:

switch anEnum {
case .a:
  // Do something
case .b, .c:
  // Do something else.
}

λ‚˜μœ 예:

switch anEnum {
case .a:
  // Do something
  break
default:
  // Do something
  break
}

μ–Έμ–΄μ—μ„œ ν•„μš”ν•˜μ§€ μ•Šμ€ 경우 return ν‚€μ›Œλ“œλ₯Ό μƒλž΅ν•œλ‹€.

SwiftLint: implicit_return SwiftFormat: redundantReturn

쒋은 예:

["1", "2", "3"].compactMap { Int($0) }

var size: CGSize {
  CGSize(
    width: 100.0,
    height: 100.0)
}

func makeInfoAlert(message: String) -> UIAlertController {
  UIAlertController(
    title: "ℹ️ Info",
    message: message,
    preferredStyle: .alert)
}

λ‚˜μœ 예:

["1", "2", "3"].compactMap { return Int($0) }

var size: CGSize {
  return CGSize(
    width: 100.0,
    height: 100.0)
}

func makeInfoAlert(message: String) -> UIAlertController {
  return UIAlertController(
    title: "ℹ️ Info",
    message: message,
    preferredStyle: .alert)
}

ν…ŒμŠ€νŠΈμ½”λ“œλŠ” Given-When-Then νŒ¨ν„΄μ„ λ”°λ₯Έλ‹€.

λ§ˆν‹΄νŒŒμšΈλŸ¬μ˜ GivenWhenThen κ΅¬κΈ€λ§μœΌλ‘œ μ—¬λŸ¬ λ²ˆμ—­λ³Έμ„ 찾을 수 μžˆμŠ΅λ‹ˆλ‹€.

return λ˜λŠ” κ°’λ§Œ ν•„μš”ν•œ λ©”μ†Œλ“œλŠ” Computed property(μ—°μ‚° ν”„λ‘œνΌν‹°)둜 μ •μ˜ν•œλ‹€.

쒋은 예:

var isMember: Bool {
    MemberManager.shared.isMember
}

λ‚˜μœ 예:

func isMember() -> Bool {
    MemberManager.shared.isMember
}

Objects

객체의 public interfaceλ₯Ό μ •μ˜ν•  λ•Œ λ”°λ₯΄λŠ” κ°€μ΄λ“œλΌμΈ

λ””λ―Έν„° 법칙을 λ”°λ₯Έλ‹€.

μ™œ?

Law of Demeter. λ‚―μ„ μžμ—κ²Œ λ§ν•˜μ§€ 말고, 였직 μΈμ ‘ν•œ μ΄μ›ƒν•˜κ³ λ§Œ λ§ν•˜λΌ.

λ””λ―Έν„° 법칙을 λ”°λ₯΄λŠ” μ½”λ“œλŠ” λ©”μ‹œμ§€ μ „μ†‘μžκ°€ μˆ˜μ‹ μžμ˜ λ‚΄λΆ€ κ΅¬ν˜„μ— κ²°ν•©λ˜μ§€ μ•ŠλŠ”λ‹€.
λ””λ―Έν„° 법칙을 μœ„λ°˜ν•œ μ½”λ“œλ₯Ό μˆ˜μ •ν•˜λŠ” 일반적인 방법은 묻지 말고 μ‹œμΌœλΌλ₯Ό λ”°λ₯΄λŠ” 것이닀. κ°μ²΄μ—κ²Œ λ‚΄λΆ€ ꡬ쑰λ₯Ό λ¬»λŠ” λŒ€μ‹  직접 μžμ‹ μ˜ μ±…μž„μ„ μˆ˜ν–‰ν•˜λ„λ‘ μ‹œν‚€λŠ” 것이닀. 이 두 μŠ€νƒ€μΌμ„ λ”°λ₯΄λ©΄ μžμ—°μŠ€λŸ½κ²Œ 자율적인 객체둜 κ΅¬μ„±λœ μœ μ—°ν•œ ν˜‘λ ₯을 μ–»κ²Œ λœλ‹€.

쒋은 예:

public class ReservationAgency {
  func func reserve(screening: Screening, customer: Customer, audienceCount: Int) -> Reservation  {
    let fee = screening.calculateFee(audienceCount)
    Reservation(customer, screening, fee, audienceCount);
  }
}

λ‚˜μœ 예:

public class ReservationAgency {
  func reserve(screening: Screening, customer: Customer, audienceCount: Int) -> Reservation {
    let movie: Movie = screening.movie
    var fee: Int = 0

    if movie.isDiscountable {
      let discountAmount = movie.discountAmount
      fee = (movie.fee - discountAmount) * audienceCount
    } else {
      fee = movie.fee * audienceCount
    }

    return Reservation(customer, screening, fee, audienceCount);
  }
}

μ˜ˆμ™Έ: self μ†μ„±μ˜ μ»¬λ ‰μ…˜ μš”μ†Œμ—λŠ” λ©”μ‹œμ§€λ₯Ό 전솑해도 μ’‹λ‹€.

μ˜ˆμ™Έ: 디미터법칙은 ν•˜λ‚˜μ˜ 점을 κ°•μ œν•˜λŠ” κ·œμΉ™μ΄ μ•„λ‹ˆλ‹€

μ•„λž˜ μ½”λ“œλŠ” 디미터법칙을 μœ„λ°°ν•˜μ§€ μ•ŠλŠ”λ‹€. 객체의 내뢀에 λŒ€ν•œ μ–΄λ–€ λ‚΄μš©λ„ 묻지 μ•ŠλŠ”λ‹€.

 [1, 10, 99, 101, 1000].filter{ $0 > 100 }.map{ $0 * 10 }.first

객체의 λ‚΄λΆ€ ꡬ쑰가 λ…ΈμΆœλ˜μ§€ μ•ŠλŠ”λ‹€λ©΄ 법칙을 μ€€μˆ˜ν•œ 것이닀.

객체의 μƒνƒœλŠ” 숨기고 ν–‰λ™λ§Œ 외뢀에 κ³΅κ°œν•œλ‹€.

속성은 private으둜 λ§Œλ“€μ–΄μ„œ 직접 μ ‘κ·Όν•˜μ§€ μ•Šκ³ , λ©”μ„œλ“œλ§Œ μ‚¬μš©ν•œλ‹€.

μ™œ?

묻지 말고 μ‹œμΌœλΌ (Tell, Don't Ask) 원칙. 객체의 μƒνƒœμ— κ΄€ν•΄ 묻지 μ•Šκ³  μ›ν•˜λŠ” 것을 μ‹œν‚¨λ‹€.

μ΄λ ‡κ²Œ ν•˜λ©΄ 객체에 λŒ€ν•΄μ„œ μ•Œμ•„μ•Όν•  정보λ₯Ό μ΅œμ†Œν™”ν•  수 μžˆλ‹€. 그리고, μžμ—°μŠ€λŸ½κ²Œ κ΄€λ ¨ 정보λ₯Ό κ°€μž₯ 잘 μ•Œκ³  μžˆλŠ” κ°μ²΄μ—κ²Œ μ±…μž„μ„ ν• λ‹Ήν•  수 μžˆλ‹€. μ™ΈλΆ€μ—μ„œ 객체의 μƒνƒœλ₯Ό 기반으둜 결정을 λ‚΄λ¦¬κ²Œ 되면 μΊ‘μŠν™”λ₯Ό μœ„λ°˜ν•˜κ²Œ λœλ‹€.

μ˜ˆμ™Έ:

  1. λ¬»λŠ” 것 μ΄μ™Έμ—λŠ” λ‹€λ₯Έ 방법이 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” κ²½μš°λ„ μžˆλ‹€. κ°μ²΄μ—κ²Œ λ¬»μ§€μ•Šκ³  직접 κ·Έ 일을 ν•˜λ„λ‘ ν•˜λŠ” 경우 μ‘μ§‘λ„λŠ” λ†’μ•„μ§ˆ 수 μžˆμ§€λ§Œ, λ³Έμ§ˆμ μ΄μ§€ μ•ŠλŠ” μ±…μž„μ„ λ– μ•ˆκ²Œ λ˜μ–΄ 객체간 결합도가 λ†’μ•„μ§ˆμˆ˜ μžˆλ‹€.
  2. λ¬ΌμœΌλ €λŠ” 객체가 μ •λ§λ‘œ 데이터인 κ²½μš°λ„ μžˆλ‹€.
  3. 자료ꡬ쑰라면 λ‹Ήμ—°νžˆ λ‚΄λΆ€λ₯Ό λ…ΈμΆœν•΄μ•Όν•˜λ―€λ‘œ λ””λ―Έν„° 법칙을 μ μš©ν•  ν•„μš”κ°€ μ—†λ‹€.

속성에 직접 μ ‘κ·Όν•΄μ•Όν•œλ‹€λ©΄ readonly둜 λ§Œλ“€μ–΄μ„œ μ™ΈλΆ€μ—μ„œ 직접 μˆ˜μ •ν•  수 μ—†κ²Œ ν•œλ‹€. 속성값 μˆ˜μ •μ΄ ν•„μš”ν•˜λ©΄ λ©”μ„œλ“œλ₯Ό ν†΅ν•΄μ„œλ§Œ κ°€λŠ₯ν•˜κ²Œν•œλ‹€.

쒋은 예:

public struct Movie {
    private(set) isDiscountable: Bool
    private(set) discountAmount: Int
    private(set) fee: Int
}

λ‚˜μœ 예:

public struct Movie2 {
  var isDiscountable: Bool
  var discountAmount: Int
  var fee: Int
}

λ©”μ„œλ“œμ˜ 이름은 'μ–΄λ–»κ²Œ'κ°€ μ•„λ‹ˆλΌ ν΄λΌμ΄μ–ΈνŠΈκ°€ '무엇'을 μ›ν•˜λŠ”μ§€λ₯Ό λ“œλŸ¬λ‚΄λ„λ‘ μ§“λŠ”λ‹€.

μ™œ?
  • 'μ–΄λ–»κ²Œ'λŠ” λ‚΄λΆ€ κ΅¬ν˜„μ„ μ„€λͺ…ν•˜λ©°, 섀계 μ‹œμ μ— λ‚΄λΆ€ κ΅¬ν˜„μ— λŒ€ν•΄ κ³ λ―Όν•  수 밖에 μ—†λ‹€. 결과와 λͺ©μ λ§Œμ„ ν¬ν•¨ν•˜λ„λ‘ νƒ€μž…κ³Ό μ˜€νΌλ ˆμ΄μ…˜μ˜ 이름을 λΆ€μ—¬ν•œλ‹€.
  • κ°€μž₯ 좔상적인 이름을 뢙인닀. Tip: 맀우 λ‹€λ₯Έ λ‘λ²ˆ μ§Έ κ΅¬ν˜„μ„ μƒμƒν•œλ‹€. 그리고, ν•΄λ‹Ή λ©”μ„œλ“œμ— λ™μΌν•œ 이름을 뢙인닀고 μƒμƒν•œλ‹€. κ°€μž₯ 좔상적인 이름을 λ©”μ„œλ“œμ— λΆ™μ΄κ²Œ 될 것이닀.

쒋은 예:

public class TicketSeller {
  private var ticketOffice: TicketOffice

  func sell(to audience: Audience) {
    audience.buyTicket(from:ticketOffice.ticket)
  }
}

public class PeriodCondition {
  func isSatisfied(_ screening: Screening) { ... }
}

λ‚˜μœ 예:

public class TicketSeller {
  private var ticketOffice: TicketOffice

  func setTicket(to audience: Audience) {
    audience.setTicket(from:ticketOffice.ticket)
  }
}

public class PeriodCondition {
  func isSatisfiedByPeriod(_ screening: Screening) { ... }
}

λͺ…λ Ήκ³Ό 쿼리λ₯Ό λΆ„λ¦¬ν•œλ‹€.

μ–΄λ–€ μ˜€νΌλ ˆμ΄μ…˜λ„ λͺ…령이 λ™μ‹œμ— 쿼리가 되게 ν•˜μ§€ μ•ŠλŠ”λ‹€.

μ™œ?

루틴은 μ–΄λ–€ 절차λ₯Ό λ¬Άμ–΄ 호좜 κ°€λŠ₯ν•˜λ„λ‘ 이름을 λΆ€μ—¬ν•œ κΈ°λŠ₯λͺ¨λ“ˆμ΄λ‹€. 루틴은 ν”„λ‘œμ‹œμ €μ™€ ν•¨μˆ˜λ‘œ λ‚˜λ‰œλ‹€. ν”„λ‘œμ‹œμ €λŠ” μ‚¬μ΄λ“œμ΄νŽ™νŠΈλ₯Ό λ°œμƒμ‹œν‚¬ 수 μžˆμ§€λ§Œ 값을 λ°˜ν™˜ν•  수 μ—†λ‹€. ν•¨μˆ˜λŠ” μ‚¬μ΄λ“œμ΄νŽ™νŠΈλ₯Ό λ°œμƒμ‹œν‚¬ 수 μ—†μ§€λ§Œ 값을 λ°˜ν™˜ν•  수 μ—†λ‹€. λͺ…λ Ή : 쿼리 = ν”„λ‘œμ‹œμ € : ν•¨μˆ˜

  • λΆ„λ¦¬λ‘œ μΈν•œ 긍정적인 효과
    • 쿼리의 μˆœμ„œλ₯Ό 자유둭게 λ³€κ²½ν•  수 μžˆλ‹€.
    • 객체지ν–₯ νŒ¨λŸ¬λ‹€μž„μ€ 객체의 μƒνƒœλ³€ν™”λΌλŠ” μ‚¬μ΄λ“œμ΄νŽ™νŠΈλ₯Ό 기반으둜 ν•˜κ³  μžˆλ‹€. λͺ…λ Ήκ³Ό 쿼리λ₯Ό λΆ„λ¦¬ν•˜λ©΄ 객체의 μ‚¬μ΄λ“œμ΄νŽ™νŠΈλ₯Ό μ œμ–΄ν•˜κΈ°κ°€ μˆ˜μ›”ν•΄μ§„λ‹€.
    • 가독성이 쒋아지고, 디버깅이 쉽고, μ‹€ν–‰ κ²°κ³Όλ₯Ό 예츑 κ°€λŠ₯ν•˜λ‹€.

ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ€ μ‚¬μ΄λ“œμ΄νŽ™νŠΈκ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” μˆ˜ν•™μ μΈ ν•¨μˆ˜λ₯Ό 기반으둜 ν•œλ‹€. κ·Έλž˜μ„œ, μ‹€ν–‰κ²°κ³Όλ₯Ό μ΄ν•΄ν•˜κ³  μ˜ˆμΈ‘ν•˜κΈ°κ°€ 더 쉽닀.

Types

νƒ€μž…μ˜ νŠΉμ„±μ„ κ³ λ €ν•˜μ—¬ class 와 structλ₯Ό μ‹ μ€‘ν•˜κ²Œ μ„ νƒν•œλ‹€.

μ™œ?

structλŠ” value semanticsλ₯Ό 가지고 μžˆλ‹€. 정체성(identity)이 μ—†λŠ” κ²ƒμ—λŠ” structλ₯Ό μ‚¬μš©ν•œλ‹€. [a, b, c]λ₯Ό ν¬ν•¨ν•˜λŠ” 배열은 [a, b, c]λ₯Ό ν¬ν•¨ν•˜λŠ” λ‹€λ₯Έ λ°°μ—΄κ³Ό μ‹€μ œλ‘œ λ™μΌν•˜λ©° μ™„μ „νžˆ κ΅ν™˜ν•  수 μžˆλ‹€. 첫 번째 배열을 μ‚¬μš©ν•˜λ“  두 번째 배열을 μ‚¬μš©ν•˜λ“  상관없닀. μ™œλƒν•˜λ©΄ 그것듀은 μ •ν™•νžˆ 같은 것을 λ‚˜νƒ€λ‚΄κΈ° λ•Œλ¬Έμ΄λ‹€. κ·Έλ ‡κΈ° λ•Œλ¬Έμ— 배열은 struct이닀.

classλŠ” reference semanticsλ₯Ό 가지고 μžˆλ‹€. 정체성(identity)μ΄λ‚˜ νŠΉμ •ν•œ 라이프 사이클이 μžˆλŠ” 것에 λŒ€ν•΄ classλ₯Ό μ΄μš©ν•œλ‹€. 두 μ‚¬λžŒ κ°μ²΄λŠ” μ„œλ‘œ λ‹€λ₯΄κΈ° λ•Œλ¬Έμ— μ‚¬λžŒμ„ class둜 λͺ¨ν˜•ν™” ν•  것이닀. 두 μ‚¬λžŒμ΄ 이름과 생년월일이 κ°™λ‹€κ³  ν•΄μ„œ 같은 μ‚¬λžŒμ΄ λ˜λŠ” 것은 μ•„λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ 1950λ…„ 3μ›” 3일의 λ‚ μ§œλŠ” 1950λ…„ 3μ›” 3일의 λ‹€λ₯Έ λ‚ μ§œ 객체와 κ°™κΈ° λ•Œλ¬Έμ— κ·Έ μ‚¬λžŒμ˜ 생년월일은 structκ°€ 될 것이닀. λ‚ μ§œ μžμ²΄λŠ” 정체성이 μ—†λ‹€.

⬆ back to top

File Organization

빈 쀄은 ν•œ μ€„λ‘œ μ œν•œν•œλ‹€.

SwiftLint: vertical_whitespace SwiftFormat: blankLinesBetweenScopes

νŒŒμΌμ„ 논리 그룹으둜 λ‚˜λˆ„κΈ° μœ„ν•΄ 높이가 λ‹€λ₯Έ 빈 μ€„λ³΄λ‹€λŠ” μ΄λŸ¬ν•œ ν¬λ§·νŒ… κ°€μ΄λ“œλΌμΈμ„ μ„ ν˜Έν•œλ‹€.

νŒŒμΌμ€ μƒˆλ‘œμš΄ μ€„λ‘œ λλ‚˜μ•Ό ν•œλ‹€.

SwiftLint: trailing_newline

extension을 μ‚¬μš©ν•˜μ—¬ μ½”λ“œλ₯Ό 논리적인 κΈ°λŠ₯ block으둜 λ‚˜λˆ„μ–΄μ§€λ„λ‘ κ΅¬μ„±ν•œλ‹€.

각 extension은 // MARK: - 주석을 달아 잘 정리해야 ν•œλ‹€.

λ³„λ„μ˜ extension으둜 protocol μ€€μˆ˜λ₯Ό μΆ”κ°€ν•œλ‹€.

μ™œ?

κ΄€λ ¨ λ©”μ„œλ“œκ°€ ν”„λ‘œν† μ½œκ³Ό ν•¨κ»˜ κ·Έλ£Ήν™”λ˜μ–΄ μœ μ§€λ˜λ©° κ΄€λ ¨ λ©”μ„œλ“œλ₯Ό μ°ΎκΈ°κ°€ 더 쉽고 μœ μ§€λ³΄μˆ˜κ°€ μš©μ΄ν•˜λ‹€.

쒋은 예:

class MyViewController: UIViewController {
 // class stuff here
}

// MARK: - UITableViewDataSource

extension MyViewController: UITableViewDataSource {
// table view data source methods
}

// MARK: - UIScrollViewDelegate

extension MyViewController: UIScrollViewDelegate {
 // scroll view delegate methods
}

λ‚˜μœ 예:

class MyViewController: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
  // all methods
}

import ν•˜λŠ” λͺ¨λ“ˆμ€ μ•ŒνŒŒλ²³ 순으둜 μ •λ ¬ν•œλ‹€.

SwiftFormat: sortedImports
λ‚΄μž₯ λͺ¨λ“ˆμ„ λ¨Όμ € 놓고, 빈 μ€„λ‘œ μ„Έμ»¨λ“œνŒŒν‹°λ₯Ό κ΅¬λΆ„ν•œλ‹€. 이후 μΆ”κ°€ 빈 μ€„λ‘œ μ„œλ“œνŒŒν‹°λ₯Ό ꡬ뢄할 수 μžˆλ‹€. header comment λ‹€μŒμ— ν•œ 쀄을 λ„μš°κ³  첫 import 문을 μ‹œμž‘ν•œλ‹€. μ΄μ™Έμ—λŠ” import λ¬Έ 사이에 빈 쀄을 μΆ”κ°€ν•˜μ§€ μ•ŠλŠ”λ‹€.

μ™œ?

standard organization 방법을 톡해 파일이 μ–΄λ–€ λͺ¨λ“ˆμ— μ˜μ‘΄ν•˜λŠ”μ§€λ₯Ό 보닀 μ‹ μ†ν•˜κ²Œ μ•Œμ•„λ‚Ό 수 μžˆλ‹€.

쒋은 예:

 //  Copyright Β© 2022 Wantedlab. All rights reserved.
 //

 import UIKit

 import SecondParty

 import SnapKit
 import React
 import Toaster

μ˜ˆμ™Έ: @testable은 일반적인 import λ¬Έ 뒀에 μœ„μΉ˜ν•˜κ³  빈 μ€„λ‘œ ꡬ뢄해야 ν•œλ‹€.

쒋은 예:

 //  Copyright Β© 2022 Wantedlab. All rights reserved.
 //

 import Nimble
 import Quick

 @testable import wanted

λ‚˜μœ 예:

//  Copyright Β© 2022 Wantedlab. All rights reserved.
//

import Nimble
@testable import wanted
import Quick

extension 파일 이름은 MyType+.swift 둜 λͺ…λͺ…ν•œλ‹€.

μ™œ?

+ λŠ” μ§§μ§€λ§Œ λͺ…ν™•ν•˜κ²Œ 의미λ₯Ό μ „λ‹¬ν•œλ‹€. ν™•μž₯λ˜λŠ” μ½”λ“œλ₯Ό 논리적인 κΈ°λŠ₯ block으둜 λ‚˜λˆŒ 수 있으며, 가독성과 μœ μ§€λ³΄μˆ˜μ„±μ΄ 쒋아진닀. μ°ΎλŠ” ν•­λͺ©μ— λŒ€ν•œ 검색 λ²”μœ„λ₯Ό μ’νžˆλŠ” 데 도움이 λœλ‹€.

쒋은 예:

```swift
AppDelegate+.swift
UIColor+.swift
UIImage+.swift
UIViewController+.swift
```

⬆ back to top

References

Airbnb Swift Style Guide

Swift API Design Guidelines

The Official raywenderlich.com Swift Style Guide

Google Swift Style Guide

StyleShare Swift Style Guide

였브젝트 by 쑰영호

SwiftLint Rules

SwiftFormater Rules

About

Wanted Lab's Swift Style Guide

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages