Skip to content

🐢 WALWAL Code Convention

ir.__.si edited this page Jul 4, 2024 · 3 revisions

1️⃣ μ½”λ“œ λ ˆμ΄μ•„μ›ƒ

1. λ“€μ—¬μ“°κΈ° 및 띄어쓰기

  • λ“€μ—¬μ“°κΈ°μ—λŠ” νƒ­(tab) λŒ€μ‹  2개의 spaceλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.
  • 콜둠(:)을 μ“Έ λ•Œμ—λŠ” 콜둠의 였λ₯Έμͺ½μ—λ§Œ 곡백을 λ‘‘λ‹ˆλ‹€.

    let names: [String: String]?
  • μ—°μ‚°μž μ˜€λ²„λ‘œλ”© ν•¨μˆ˜ μ •μ˜μ—μ„œλŠ” μ—°μ‚°μžμ™€ κ΄„ν˜Έ 사이에 ν•œ μΉΈ λ„μ–΄μ”λ‹ˆλ‹€.

    func ** (lhs: Int, rhs: Int)

2. μ€„λ°”κΏˆ

  • νŒŒλΌλ―Έν„° 3개 이상일 λ•Œ, νŒŒλΌλ―Έν„° κΈ°μ€€μœΌλ‘œ μ€„λ°”κΏˆν•©λ‹ˆλ‹€.

    let actionSheet = UIActionSheet(
      title: "정말 계정을 μ‚­μ œν•˜μ‹€ κ±΄κ°€μš”?",
      delegate: self,
      cancelButtonTitle: "μ·¨μ†Œ",
      destructiveButtonTitle: "μ‚­μ œν•΄μ£Όμ„Έμš”"
    )
  • νŒŒλΌλ―Έν„°μ— ν΄λ‘œμ €κ°€ 2개 이상 μ‘΄μž¬ν•˜λŠ” κ²½μš°μ—λŠ” 무쑰건 λ‚΄λ €μ“°κΈ°ν•©λ‹ˆλ‹€.

    UIView.animate(
      withDuration: 0.25,
      animations: {
        // doSomething()
      },
      completion: { finished in
        // doSomething()
      }
    )
  • ν•¨μˆ˜λ₯Ό μž‘μ„±ν•  땐, 첫 번째 νŒŒλΌλ―Έν„°κ°€ μ€„λ°”κΏˆ 이후에 μœ„μΉ˜ν•˜λ„λ‘ ν•©λ‹ˆλ‹€.

    func animationController(
      forPresented presented: UIViewController,
      presenting: UIViewController,
      source: UIViewController
    ) -> UIViewControllerAnimatedTransitioning? {
      // doSomething()
    }
  • if let ꡬ문이 κΈΈ κ²½μš°μ—λŠ” μ€„λ°”κΏˆν•˜κ³  ν•œ μΉΈ λ“€μ—¬μ”λ‹ˆλ‹€.

    if let user = self.veryLongFunctionNameWhichReturnsOptionalUser(),
       let name = user.veryLongFunctionNameWhichReturnsOptionalName(),
      user.gender == .female {
      // ...
    }
  • guard let ꡬ문이 κΈΈ κ²½μš°μ—λŠ” μ€„λ°”κΏˆν•˜κ³  ν•œ μΉΈ λ“€μ—¬μ”λ‹ˆλ‹€.Β elseλŠ”Β λ‹¨μˆœ return 인 κ²½μš°μ—λŠ” ν•œ μ€„λ‘œ μ“°κ³  else ꡬ문 μ•ˆμ— λ™μž‘μ΄ μžˆλŠ” 경우 μ€„λ°”κΏˆν•©λ‹ˆλ‹€. (μ˜ˆμ‹œ μ°Έκ³ )

    guard let user = self.veryLongFunctionNameWhichReturnsOptionalUser(),
          let name = user.veryLongFunctionNameWhichReturnsOptionalName(),
          user.gender == .female else { return }
          
    guard let user = self.veryLongFunctionNameWhichReturnsOptionalUser(),
          let name = user.veryLongFunctionNameWhichReturnsOptionalName(),
          user.gender == .female else { 
    	      // ...
    	      return 
          }
  • return 전에 μ€„λ°”κΏˆν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

3. 빈 쀄

  • 빈 μ€„μ—λŠ” 곡백이 ν¬ν•¨λ˜μ§€ μ•Šλ„λ‘ ν•©λ‹ˆλ‹€.

  • λͺ¨λ“  νŒŒμΌμ€ 빈 μ€„λ‘œ λλ‚˜λ„λ‘ ν•©λ‹ˆλ‹€.

  • MARK ꡬ문 μœ„μ™€ μ•„λž˜μ—λŠ” 곡백이 ν•„μš”ν•©λ‹ˆλ‹€.

    // MARK: - Layout
    
    override func layoutSubviews() {
      // doSomething()
    }
    
    // MARK: - Actions
    
    override func menuButtonDidTap() {
      // doSomething()
    }

4. μž„ν¬νŠΈ

  • λ‚΄μž₯ ν”„λ ˆμž„μ›Œν¬λ₯Ό λ¨Όμ € μž„ν¬νŠΈν•˜κ³ , 빈 μ€„λ‘œ κ΅¬λΆ„ν•˜μ—¬ μ„œλ“œνŒŒν‹° ν”„λ ˆμž„μ›Œν¬λ₯Ό μž„ν¬νŠΈν•©λ‹ˆλ‹€.

    import UIKit
    
    import SwiftyColor
    import SwiftyImage
    import Then
    import URLNavigator

2️⃣ 넀이밍

1. ν΄λž˜μŠ€μ™€ ꡬ쑰체

  • ν΄λž˜μŠ€μ™€ ꡬ쑰체의 μ΄λ¦„μ—λŠ” UpperCamelCaseλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

    βœ… 쒋은 예:

    class SomeClass {
      // class definition goes here
    } 
    
    struct SomeStructure {
      // structure definition goes here
    }

    ❌ λ‚˜μœ 예:

    class someClass {
      // class definition goes here
    }
    
    struct someStructure {
      // structure definition goes here
    }

2. ν•¨μˆ˜

  • ν•¨μˆ˜ μ΄λ¦„μ—λŠ” lowerCamelCaseλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

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

  • ν•¨μˆ˜ 이름은 return 값을 λ³€μˆ˜μ²˜λŸΌ μ‚¬μš©ν•œλ‹€λ©΄, λͺ…μ‚¬λ‘œ μ‚¬μš©ν•΄λ„ 무관

    βœ… 쒋은 예:

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

    ❌ λ‚˜μœ 예:

    func getName(for user: User) -> String?
  • Action ν•¨μˆ˜μ˜ 넀이밍은 'μ£Όμ–΄ + 동사 + λͺ©μ μ–΄' ν˜•νƒœλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

    • Tap(λˆŒλ €λ‹€ λ—Œ)*은 UIControlEvents의 .touchUpInside에 λŒ€μ‘ν•˜κ³ , *Press(λˆ„λ¦„)*λŠ”Β .touchDown에 λŒ€μ‘ν•©λ‹ˆλ‹€.
      1. 동사 κ³Όκ±°ν˜•μœΌλ‘œ (ex. *confirmButtonTapped*)
      2. *will~*은 νŠΉμ • ν–‰μœ„κ°€ μΌμ–΄λ‚˜κΈ° 직전
      3. boolν˜•μ„ λ°˜ν™˜ν•˜λŠ” ν•¨μˆ˜λŠ” *is~*

    βœ… 쒋은 예:

    func backButtonDidTap() {
      // ...
    }

    ❌ λ‚˜μœ 예:

    func back() {
      // ...
    }
    
    func pressBack() {
      // ...
    }

3. λ³€μˆ˜

  • λ³€μˆ˜ μ΄λ¦„μ—λŠ” lowerCamelCaseλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

4. μƒμˆ˜

  • μƒμˆ˜ μ΄λ¦„μ—λŠ” lowerCamelCaseλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

    βœ… 쒋은 예:

    let maximumNumberOfLines = 3

    ❌ λ‚˜μœ 예:

    let MaximumNumberOfLines = 3
    let MAX_LINES = 3

5. μ—΄κ±°ν˜•

  • enum의 μ΄λ¦„μ—λŠ” UpperCamelCaseλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

  • enum의 각 caseμ—λŠ” lowerCamelCaseλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

  • case의 μ„ μ–ΈλΆ€λŠ” 라인별 1개의 caseλ₯Ό μž‘μ •ν•˜κ³ , case에 λŒ€ν•œ μ²˜λ¦¬κ°€ λ™μΌν•œ κ²½μš°μ—λŠ” ν•œ 쀄에 μ—¬λŸ¬ case에 λŒ€ν•œ 경우λ₯Ό 처리

    βœ… 쒋은 예:

    enum Result {
      
      case signUp(body: String)
      case signIn
      
      var httpMethod: {
    	switch self {
    	case .signUp, .signIn:
    	  return .POST
    	}
      }
    }

    ❌ λ‚˜μœ 예:

    enum Result {
      case .Success
      case .Failure
    }
    
    enum result {
      case .Success
      case .Failure
    }

6. ν”„λ‘œν† μ½œ

  • ν”„λ‘œν† μ½œμ˜ μ΄λ¦„μ—λŠ” UpperCamelCaseλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

  • κ΅¬μ‘°μ²΄λ‚˜ ν΄λž˜μŠ€μ—μ„œ ν”„λ‘œν† μ½œμ„ 채택할 λ•ŒλŠ” 콜둠과 λΉˆμΉΈμ„ λ„£μ–΄ κ΅¬λΆ„ν•˜μ—¬ λͺ…μ‹œν•©λ‹ˆλ‹€.

  • extension을 톡해 채택할 λ•Œλ„ λ™μΌν•˜κ²Œ μ μš©λ©λ‹ˆλ‹€.

  • protocol의 suffixλŠ” ~Protocol둜 ν†΅μΌν•©λ‹ˆλ‹€.

    βœ… 쒋은 예:

    protocol SomeProtocol {
      // protocol definition goes here
    }
    
    struct SomeStructure: SomeProtocol, AnotherProtocol {
      // structure definition goes here
    }
    
    class SomeClass: SomeSuperclass, SomeProtocol, AnotherProtocol {
      // class definition goes here
    }
    
    extension UIViewController: SomeProtocol, AnotherProtocol {
      // doSomething()
    }

7. μ•½μ–΄

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

    βœ… 쒋은 예:

    let userID: Int?
    let html: String?
    let websiteURL: URL?
    let urlString: String?    

    ❌ λ‚˜μœ 예:

    let userId: Int?
    let HTML: String?
    let websiteUrl: NSURL?
    let URLString: String?

8. Delegate

  • Delegate λ©”μ„œλ“œλŠ” ν”„λ‘œν† μ½œλͺ…μœΌλ‘œ λ„€μž„μŠ€νŽ˜μ΄μŠ€λ₯Ό κ΅¬λΆ„ν•©λ‹ˆλ‹€.

  • ex) UserCellDelegate β†’ λ‚΄λΆ€ ν•¨μˆ˜μ˜ prefixλŠ” UserCell

    βœ… 쒋은 예:

    protocol UserCellDelegate {
      func userCellDidSetProfileImage(_ cell: UserCell)
      func userCell(_ cell: UserCell, didTapFollowButtonWith user: User)
    }

    ❌ λ‚˜μœ 예:

    protocol UserCellDelegate {
      func didSetProfileImage()
      func followPressed(user: User)
    
      // `UserCell`μ΄λΌλŠ” ν΄λž˜μŠ€κ°€ μ‘΄μž¬ν•  경우 컴파일 μ—λŸ¬ λ°œμƒ
      func UserCell(_ cell: UserCell, didTapFollowButtonWith user: User)
    }

9. ν΄λ‘œμ €

  • νŒŒλΌλ―Έν„°μ™€ 리턴 νƒ€μž…μ΄ μ—†λŠ” Closure μ •μ˜μ‹œμ—λŠ”Β () -> Voidλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

    βœ… 쒋은 예:

    let completionBlock: (() -> Void)?

    ❌ λ‚˜μœ 예:

    let completionBlock: (() -> ())?let completionBlock: ((Void) -> (Void))?
  • Closure μ •μ˜μ‹œ νŒŒλΌλ―Έν„°μ—λŠ” κ΄„ν˜Έλ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. Rx λ‚΄λΆ€ ν΄λ‘œμ €μ—μ„œλ„ μ μš©λ©λ‹ˆλ‹€.

    βœ… 쒋은 예:

    { operation, responseObject in
      // doSomething()
    }

    ❌ λ‚˜μœ 예:

    { (operation, responseObject) in
      // doSomething()
    }
  • Closure μ •μ˜μ‹œ κ°€λŠ₯ν•œ 경우 νƒ€μž… μ •μ˜λ₯Ό μƒλž΅ν•©λ‹ˆλ‹€.

    βœ… 쒋은 예:

    ...,
    completion: { finished in
      // doSomething()
    }

    ❌ λ‚˜μœ 예:

    ...,
    completion: { (finished: Bool) -> Void in
      // doSomething()
    }
  • Closure ν˜ΈμΆœμ‹œ λ˜λ‹€λ₯Έ μœ μΌν•œ Closureλ₯Ό λ§ˆμ§€λ§‰ νŒŒλΌλ―Έν„°λ‘œ λ°›λŠ” 경우, νŒŒλΌλ―Έν„° 이름을 μƒλž΅ν•©λ‹ˆλ‹€.

    βœ… 쒋은 예:

    UIView.animate(withDuration: 0.5) {
      // doSomething()
    }

    ❌ λ‚˜μœ 예:

    UIView.animate(withDuration: 0.5, animations: { () -> Void in
      // doSomething()
    })

3️⃣ ν΄λž˜μŠ€μ™€ ꡬ쑰체

  • ꡬ쑰체λ₯Ό 생성할 λ•Œμ—λŠ” Swift ꡬ쑰체 μƒμ„±μžλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

    βœ… 쒋은 예:

    let frame = CGRect(x: 0, y: 0, width: 100, height: 100)

    ❌ λ‚˜μœ 예:

    let frame = CGRectMake(0, 0, 100, 100)

1. νƒ€μž…

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

    βœ… 쒋은 예:

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

    ❌ λ‚˜μœ 예:

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

2. 주석

  • ///λ₯Ό μ‚¬μš©ν•΄μ„œ λ¬Έμ„œν™”μ— μ‚¬μš©λ˜λŠ” 주석을 λ‚¨κΉλ‹ˆλ‹€.

    /// μ‚¬μš©μž ν”„λ‘œν•„μ„ κ·Έλ €μ£ΌλŠ” λ·°
    class ProfileView: UIView {
    
      /// μ‚¬μš©μž λ‹‰λ„€μž„μ„ κ·Έλ €μ£ΌλŠ” 라벨
      var nameLabel: UILabel!
    }
  • // MARK: λ₯Ό μ‚¬μš©ν•΄μ„œ μ—°κ΄€λœ μ½”λ“œλ₯Ό κ΅¬λΆ„μ§“μŠ΅λ‹ˆλ‹€.
    Objective-Cμ—μ„œ μ œκ³΅ν•˜λŠ”Β #pragma mark와 같은 κΈ°λŠ₯으둜, μ—°κ΄€λœ μ½”λ“œμ™€ 그렇지 μ•Šμ€ μ½”λ“œλ₯Ό ꡬ뢄할 λ•Œ μ‚¬μš©ν•©λ‹ˆλ‹€.

    // MARK: - Init
    
    override init(frame: CGRect) {
      // doSomething()
    }
    
    deinit {
      // doSomething()
    }
    
    // MARK: - Layout
    
    override func layoutSubviews() {
      // doSomething()
    }
    
    // MARK: - Actions
    
    override func menuButtonDidTap() {
      // doSomething()
    }

4️⃣ ν”„λ‘œκ·Έλž˜λ° ꢌμž₯사항

  • κ°€λŠ₯ν•˜λ‹€λ©΄ λ³€μˆ˜λ₯Ό μ •μ˜ν•  λ•Œ ν•¨κ»˜ μ΄ˆκΈ°ν™”ν•˜λ„λ‘ ν•©λ‹ˆλ‹€.Β Then을 μ‚¬μš©ν•˜λ©΄ μ΄ˆκΈ°ν™”μ™€ ν•¨κ»˜ 속성을 지정할 수 μžˆμŠ΅λ‹ˆλ‹€.

    let label = UILabel().then {
      $0.textAlignment = .center
      $0.textColor = .black
      $0.text = "Hello, World!"
    }
  • μƒμˆ˜λ₯Ό μ •μ˜ν•  λ•Œμ—λŠ”Β enumλ₯Ό λ§Œλ“€μ–΄ λΉ„μŠ·ν•œ μƒμˆ˜λΌλ¦¬ λͺ¨μ•„λ‘‘λ‹ˆλ‹€. μž¬μ‚¬μš©μ„±κ³Ό μœ μ§€λ³΄μˆ˜ μΈ‘λ©΄μ—μ„œ 큰 ν–₯상을 κ°€μ Έμ˜΅λ‹ˆλ‹€.Β structΒ λŒ€μ‹ Β enum을 μ‚¬μš©ν•˜λŠ” μ΄μœ λŠ”, μƒμ„±μžκ°€ μ œκ³΅λ˜μ§€ μ•ŠλŠ” μžλ£Œν˜•μ„ μ‚¬μš©ν•˜κΈ° μœ„ν•΄μ„œμž…λ‹ˆλ‹€.Β CGFloatLiteralκ³ΌΒ SwiftyColorλ₯Ό μ‚¬μš©ν•΄μ„œ μ½”λ“œλ₯Ό λ‹¨μˆœν™”μ‹œν‚΅λ‹ˆλ‹€.

    final class ProfileViewController: UIViewController {
    
      private enum Metric {
        static let profileImageViewLeft = 10.f
        static let profileImageViewRight = 10.f
        static let nameLabelTopBottom = 8.f
        static let bioLabelTop = 6.f
      }
    
      private enum Font {
        static let nameLabel = UIFont.boldSystemFont(ofSize: 14)
        static let bioLabel = UIFont.boldSystemFont(ofSize: 12)
      }
    
      private enum Color {
        static let nameLabelText = 0x000000.color
        static let bioLabelText = 0x333333.color ~ 70%
      }
    
    }

    μ΄λ ‡κ²Œ μ„ μ–Έλœ μƒμˆ˜λ“€μ€ λ‹€μŒκ³Ό 같이 μ‚¬μš©λ  수 μžˆμŠ΅λ‹ˆλ‹€.

    self.profileImageView.frame.origin.x = Metric.profileImageViewLeft
    self.nameLabel.font = Font.nameLabel
    self.nameLabel.textColor = Color.nameLabelText
  • 더이상 상속이 λ°œμƒν•˜μ§€ μ•ŠλŠ” ν΄λž˜μŠ€λŠ” 항상 finalΒ ν‚€μ›Œλ“œλ‘œ μ„ μ–Έν•©λ‹ˆλ‹€.

  • ν”„λ‘œν† μ½œμ„ μ μš©ν•  λ•Œμ—λŠ” extension을 λ§Œλ“€μ–΄μ„œ κ΄€λ ¨λœ λ©”μ„œλ“œλ₯Ό λͺ¨μ•„λ‘‘λ‹ˆλ‹€.

  • private methodλŠ” extension으둜 λΆ„λ¦¬ν•©λ‹ˆλ‹€.

    βœ… 쒋은 예:

    final class MyViewController: UIViewController {
      // ...
    }
    
    // MARK: - UITableViewDataSource
    
    extension MyViewController: UITableViewDataSource {
      // ...
    }
    
    // MARK: - UITableViewDelegate
    
    extension MyViewController: UITableViewDelegate {
      // ...
    }
    
    // MARK: - Private Method
    
    extension MyViewController {
      private func goHome() {
    	/// do something
      }
    }

    ❌ λ‚˜μœ 예:

    final class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
      // ...
    }

5️⃣ Rx μ»¨λ²€μ…˜

1. RxCocoa μ—μ„œμ˜ 체이닝

  • ꡬ문의 첫번째 Operator κΈ°μ€€μœΌλ‘œ 쀄을 λ°”κΏ‰λ‹ˆλ‹€.

    comfirmButton.rx.tap
      .map{ }
      .bind()
      .disposed(by: disposeBag)
    
    buttonTapped
      .map { Reactor.Action.tapped }
      .bind(to: reactor.action)
      .disposed(by: disposeBag)	
    
    reactor.state
      .map { $0.id }	
      .disposed(by: disposeBag)
    	
    input.settingButtonTapped
      .withUnretained(self)
      .subscribe(onNext: { owner, _ in
        owner.coordinator?.destination.accept(.myPage)
      })
      .disposed(by: disposeBag)
        

2. withUnretained()

  • .withUnretained(self)λŠ” drive에 μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€.

  • λ”°λΌμ„œ.drive(with: self), .subscribe(with: self)을 μ‚¬μš©ν•˜λ„λ‘ ν•©λ‹ˆλ‹€.

  • selfλ₯Ό μ§€μΉ­ν•˜λŠ” λ³€μˆ˜λŠ” owner둜 μž‘μ„±ν•©λ‹ˆλ‹€.

    βœ… 쒋은 예:

    reactor.state
      .map { $0.logoutSuccess }
      .filter { $0 }
      .asDriver(onErrorJustReturn: false)
      .drive(with: self) { owner, _ in
      
      }
      .disposed(by: disposeBag)

3. Rx Extension

// Case 1
extension Reactive where Base: CustomButton {
  public var tap: ControlEvent<Void> {
    let source: Observable<Void> = self.base.rx.tapGesture()
      .when(.recognized)
      .map { _ in () }
    	return ControlEvent(events: source)
    }
  }
    
 // Case 2
extension Reactive where Base: UIView {
  public var tapped() -> ControlEvent<Void> {
    let source = self.base.rx.tapGesture().when(.recognized).map { _ in }
      return ControlEvent(events: source)
    }
  }

StyleShare κ°€μ΄λ“œλ₯Ό μ°Έκ³ ν•˜μ—¬ μž‘μ„±ν•˜μ˜€μŠ΅λ‹ˆλ‹€. πŸ”—StyleShare Guide