Skip to content

Swift 컨벤션

never-better edited this page Nov 18, 2022 · 8 revisions

목차

컨벤션을 위한 SwiftLint

얼죽아 SwiftLint

소스 파일

파일 이름

  • 최대한 명시적인 이름을 사용한다.

    ViewController의 경우 이름을 다 작성해준다. (VC 등으로 줄여서 사용하지 않음)

    // 좋은 예
    class HomeViewController: UIViewController {
    	//
    }
    
    class MovieDetailCollectionCell: UICollectionViewCell {
    
    }
    
    // 나쁜 예
    class HomeVC: UIViewController {
    	//
    }

    Delegate의 경우 객체이름 + 기능 + Delegate를 적는다.

    // HomeViewController의 화면 플로우 Delegate
    protocol HomeViewControllerFlowDelegate {
    
    }
  • Extension : 기본은 파일+기능으로 적는다. 파일 하나에 기능이 많아지면 + 만 사용해 암시적으로 적는다.

    기본 -> String+Regex
    파일에 기능이 많아짐 -> String+
    
  • Unit Test 파일의 경우 테스트 기능 + Tests를 적어준다.

    // 회원가입 정규식 로직 확인 유닛 테스트
    SignUpRegexTests
  • UI Test 파일의 경우 화면 이름 + UITests 을 적어준다.

    // 로그인 화면 UI 테스트
    LoginUITests

파일 길이

  • 최대 700줄이다. (Lint 기본값 경고:400, 에러:1000)

파일 구조

  • Scene 단위로 폴더를 만들고 그 안에서 View, Model 등을 나눈다.
#예시
GoogleService-Info #plist
Service-Info #plist
.swiftlint
.swiltlint.auto
.swiftgen
| EolJuga
	| App  #앱 첫 실행 관련
		- AppDelegate.Swift
		- SceneDelegate.Swift
		- Info
		- LaunchScreen
	| Service #서비스 로직 네트워크, 캐시 등
		| Network
    | Cache
	| Scene
    | Auth 
    | Tab
			| Home
				- HomeViewController.Swift
				- HomeViewModel.Swift
				| View
					- HomeSomethingView.Swift 		
      | 그 외 Tab View 들
	| Coordinator #코디네이터 파일 다 모아둠
	| Model # 앱에서 사용할 모델
  | Resources #이미지, 컬러 등을 모아둠. SwiftGen으로 생성된 파일도 포함
	| Util
    | Protocol #공통적으로 사용하는 프로토콜
    | Contant #상수 rawValue를 저장
    | Error #에러 관리
		| Extensions # Swift 기본 프로퍼티 Extension 
			| String 
				- String+Regex 
				- String+Won
			| UIColor
|EoljugaTests 
	- SignUpRegexTests
| EoljugaUITests
	- LoginUITests

코드 레이아웃

들여쓰기

  • tab으로 사용한다. (tab은 xcode에서 4칸)
extension String {
   func isValidRegex(_ regex: String) -> Bool {
   		//
   	} 
}

띄어쓰기

  • 공백문자 기본값은 유니코드 가로 공간 문자(U+0020) (= space 바 한 칸) 이다.
    : , , 의 경우 띄어쓰기는 뒤에만 사용한다.
   let example: Int = 1
   func addTwoInts(a: Int, b: Int) {
    
   }
그 외 `→` 나 `{`, `}`  는 앞 뒤로 띄어쓰기를 한다.
// 함수 리턴값

// return 앞 뒤로 꼭 띄어쓰기
guard let data = data else { return }

블럭 {} 시작 과 끝에는 공백이 없다.

class CartViewController: UIViewController {
	override func viewDidLoad() {
        super.viewDidLoad()
	}
}

줄바꿈

  • 함수 정의가 최대 길이를 초과하는 경우에는 아래와 같이 줄바꿈합니다. (초과 안하면 1줄에 적기)

    func collectionView(
      _ collectionView: UICollectionView,
      cellForItemAt indexPath: IndexPath
    ) -> UICollectionViewCell {
      // doSomething()
    }
    
    func animationController(
      forPresented presented: UIViewController,
      presenting: UIViewController,
      source: UIViewController
    ) -> UIViewControllerAnimatedTransitioning? {
      // doSomething()
    }
  • 초기화 하는 경우 인자가 여러 개 일 때, 인자에 따라 줄바꿈을 해준다.

    let actionSheet = UIActionSheet(
      title: "정말 계정을 삭제하실 건가요?",
    	delegate: self,
      cancelButtonTitle: "취소",
      destructiveButtonTitle: "삭제해주세요"
    )
  • 단, 파라미터에 클로저가 2개 이상 존재하는 경우에는 무조건 내려쓰기합니다.

    UIView.animate(
      withDuration: 0.25,
      animations: {
        // doSomething()
      },
      completion: { finished in
        // doSomething()
      }
    )
  • if let 구문이 길 경우에는 줄바꿈하고 한 칸 들여씁니다.

  • guard let 구문이 길 경우에는 줄바꿈하고 한 칸 들여씁니다. else는 guard와 같은 들여쓰기를 적용합니다.

    if let user = self.veryLongFunctionNameWhichReturnsOptionalUser(),
      let name = user.veryLongFunctionNameWhichReturnsOptionalName(),
      user.gender == .female {
      // ...
    }
    
     guard let user = self.veryLongFunctionNameWhichReturnsOptionalUser(),
      let name = user.veryLongFunctionNameWhichReturnsOptionalName(),
      user.gender == .female // 띄어쓰기 
    else {
      return
    }
  • Switch 문 작성시 한 줄에 작성할 수 있으면, 한 줄에 다 작성합니다. 한 줄 최대 길이를 넘어가면 줄바꿈을 합니다.

    // 좋은 예
    switch self {
    	case .home: return "home"
    	case .mypage: return "mypage"
    }
    
    // 좋은 예 : 한 줄 최대길이를 넘어 줄바꿈을 했다
    switch self {
    	case .home:
    		return "Lorem Ipsum is simply dummy text of the printing and typesetting industry."
    	case .mypage:
    		return "Lorem Ipsum is simply dummy text of the printing and typesetting industry."
    }
    
    // 나쁜 예
    switch self {
    	case .home:
    		return "home"
    	case .mypage:
    		return "mypage"
    }

최대 줄 길이

  • 한 줄은 최대 100자를 넘지 않아야 합니다.
    • Xcode의 Preferences → Text Editing → Display의 'Page guide at column' 옵션을 활성화하고 100자로 설정

빈 줄

  • 빈 줄은 공백이 없습니다.

파일 타입 순서

  • SwiftLint의 file_types_order를 따른다.

    /// 좋은 예
    
    // Supporting Types = 추상화, Delegate protocol
      
    // Main Types
    class TestViewController: UIViewController {
        // Type Aliases
        typealias CompletionHandler = ((TestEnum) -> Void)
    
        // 서브 타입 
        class TestClass {
            // 10 lines
        }
    
        // Stored Type Properties = static 프로퍼티
        static let cellIdentifier: String = "AmazingCell"
    
        // Stored Instance Properties = 저장 프로퍼티
        var shouldLayoutView1: Bool!
        weak var delegate: TestViewControllerDelegate?
    
        // Computed Instance Properties = 연산 프로퍼티
        private var hasAnyLayoutedView: Bool {
             return hasLayoutedView1 || hasLayoutedView2
        }
    
        // IBOutlets -> 프로젝트에서 쓰진 않지만 우선은 Lint에 포함되어 있음
        @IBOutlet private var view1: UIView!
    
        // Initializers = 초기화
        override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
            super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        // Type Methods = static 메소드
        static func makeViewController() -> TestViewController {
            // some code
        }
    
        // Life-Cycle Methods = 라이플 사이클 함수
        override func viewDidLoad() {
            super.viewDidLoad()
        }
    
        override func viewDidLayoutSubviews() {
            super.viewDidLayoutSubviews()
        }
    
        // IBActions -> IBOutlet과 마찬가지로 프로젝트에서 쓰진 않지만 우선은 Lint에 포함되어 있음
        @IBAction func goNextButtonPressed() {
            goToNextVc()
            delegate?.didPressTrackedButton()
        }
    
        @objc
        func goToRandomVcButtonPressed() {
            goToRandomVc()
        }
    
        // MARK: 메소드들
        func goToNextVc() { /* TODO */ }
    
        private func getRandomVc() -> UIViewController { return UIViewController() }
    
        // Subscripts = 서브 스크립트
        subscript(_ someIndexThatIsNotEvenUsed: Int) -> String {
            get {
                return "This is just a test"
            }
    
            set {
                log.warning("Just a test", newValue)
            }
        }
    }
    
    // Extensions = 프로토콜 채택 (추상화 제외 -> Main Type 선언시 채택)
    extension TestViewController: UITableViewDataSource {
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 1
        }
    }

코드 네이밍

코드 길이

  • 타입(클래스, 구조체, 프로토콜 등)은 최대 250자이다.
  • 타입(클래스, 구조체, 프로토콜 등)의 이름(name)은 3~40자이다.
  • 프로퍼티(identifier) 의 이름(name)은 3~40자이다.
    • 예외 : id, URL, url

클래스와 구조체

  • 클래스와 구조체 내부에서는 self를 사용할 필요가 없는 경우 사용하지 않습니다.

    // 좋은 예
    class ViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
    
    				navigationController?.pushViewController(
    					someViewController,
    					animated: true
    				)
    
        }
    }
    
    // 나쁜 예
    class ViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
    
    				self.navigationController?.pushViewController(
    					someViewController,
    					animated: true
    				)
    
        }
    }
  • 구조체를 생성할 때에는 Swift 구조체 생성자를 사용합니다.

    // 좋은 예
    let frame = CGRect(x: 0, y: 0, width: 100, height: 100)
    
    // 나쁜 예
    let frame = CGRectMake(0, 0, 100, 100)

함수

  • 함수 길이는 최대 50줄 입니다.

  • 함수 이름에는 lowerCamelCase를 사용합니다.

    func deleteUser() {
    		//
    }
  • 함수 이름 앞에는 되도록이면 get, set 을 붙이지 않습니다. 바로 명사로 시작하거나 다른 동사를 사용합니다.

    // 좋은 예
    func date(from string: String) -> Date?                        
    func distance(from location: CLLocation) -> CLLocationDistance        
    
    // 나쁜 예
    func getDate(from string: String) -> Date?
    func getDistance(from location: CLLocation) -> CLLocationDistance
  • 함수 이름에 인자가 명시적으로 나타나는 경우 인자를 생략가능 합니다.

    // 생략
    func addTwoInts(_ a: Int, _ b: Int) -> Int {
    	return a + b
    }
    
    // 생략 안 한 경우
    func addTwoInts(a: Int, b: Int) -> Int {
    	return a + b
    }

Action 함수의 네이밍은 '주어 + 동사 + 목적어' 형태를 사용합니다.

  • Tap(눌렀다 뗌)*은 UIControlEvents의 .touchUpInside에 대응하고, *Press(누름)*는 .touchDown에 대응합니다.

  • 동사는 원형으로 쓴다.

    // 좋은 예
    func backButtonDidTap() {
    	// ...
    }
    
    // 나쁜 예
    func back() {
    	// ...
    }
    
    func touchBack() {
    	// ...
    }
  • will~은 특정 행위가 일어나기 직전이고, did~는 특정 행위가 일어난 직후입니다.

    func viewDidLoad() {
    		//
    }
    // before, after 아님
  • Bool 타입 함수의 경우 is로 시작합니다.

    func isValid() -> Bool {
    		//
    }
  • 클래스 내부 띄어쓰기

    • 첫 줄은 띄운다.
    • 막 줄은 붙인다.
  • 함수 내부 띄어쓰기

    • 시작, 막줄 다 붙인다.
    • 위쪽에 변수 선언부가 몰려있으면 로직 시작 전 한 칸
    • 두 개 이상의 로직이 들어있다면 로직 사이에 한 칸
    • return 띄어쓰기는 줄 수에 따라 → 짧으면 붙이고, 길면 띄우기 → 코드리뷰시 코멘트 남겨주기
    func hello() -> String {
    	let a: Int = 1
    	var b: String = "world"
      var c: String = "안녕하세요"
    
    	b += "\(a)"
    	c += "\(a)"
    	c += b
    	
    	return c
    }

프로퍼티

  • 변수 이름에는 lowerCamelCase를 사용합니다.

  • 상수 이름에는 lowerCamelCase를 사용합니다.

  • 프로퍼티 초기화시 타입을 안 적을 수 있으면 적지 않는다.

    // 좋은 예
    let loginViewModel = LoginViewModel()
    
    // 나쁜 예
    let loginViewModel: LoginViewModel = LoginViewModel()
  • “프로토콜을 채택하는 객체”를 초기화

    protocol Coordinator { }
    
    class AuthCooordinator: Coordinator { }
    
    // 좋은 예
    let childCoordinator: [Coordinator]
  • 논외: 배열 초기화시 빈 값일 경우, 타입과 빈 배열을 적어준다

    // 좋은 예
    var city: [String] = []
    
    // 나쁜 예
    var city = [String]()

열거형

  • enum의 각 case에는 lowerCamelCase를 사용합니다.

    // 좋은 예
    enum Result {
    	case .success
    	case .failure
    }
    
    // 나쁜 예
    enum Result {
    	case .Success
    	case .Failure
    }
  • 나열을 하지 않고 무조건 띄어쓰기 한다.

    // 좋은 예 
    enum Alphabet {
    	case a
    	case b
    	case c
    	case d
    }
    
    // 나쁜 예 
    enum Alphabet {
    	case a, b, c, d
    }

약어

  • 약어로 시작하는 경우 소문자로 표기하고, 그 외의 경우에는 항상 대문자로 표기합니다.

    // 좋은 예
    let userID: String?
    let id: String?
    let html: String?
    let websiteURL: URL?
    let urlString: String?
    
    // 나쁜 예
    let userId: Int?
    let HTML: String?
    let websiteUrl: NSURL?
    let URLString: String?

옵셔널 바인딩

기본 옵셔널 바인딩을 사용한다. (xcode 14.0에 추가된 기능 안 씀)

// 좋은 예
if let data = data { 
	// 
}

// 나쁜 예
if let data {

}

프로토콜

추상화를 위한 프로토콜은 객체를 선언할 때 채택한다. (extension에는 추가적인 프로토콜만 채택한다)

protocol NetworkManager {
	//
}

class MyNetworkManager: NetworkManager {
	//
}
  • 파일 하나에 프로토콜 추가 선언 시 프로토콜을 최상단에 넣는다.

    protocol HomeViewControllerFlowDelegate {
    
    }
    
    class HomeViewController: HomeViewControllerFlowDelegate {
    
    }

Extension

  • 추가적인 프로토콜을 채택할 때 사용한다.

    extension HomeViewController: UICollectionViewDelegateFlowLayout {
    	
    }
  • 기존 Swift 타입에 기능을 추가할 때 사용한다.

    extension String {
    	//
    }
  • extension 이후 한 줄 띄우고 선언을 한다. (class와 동일)

주석

주석을 작성 안하는 쪽으로 코드를 작성한다.

정책과 관련됬거나, 설명이 필요하다고 생각되면 작성한다.

ViewController 에 여러 개의 기능이 있는 경우 연관된 기능끼리 // MARK: 로 분리해준다.

// MARK: 위, 아래 줄은 공백 줄이다.

// MARK: - Static Methods 는 static method가 없을 경우 작성하지 않는다.

class HomeViewController: UIViewController {

	typealias a = String

	// MARK: - Properties

	var a = 1

	// MARK: - Static Methods (없을 경우 작성하지 않는다.)

	// MARK: - Life Cycle

	func	viewDidLoad() { }

	// MARK: - Methods

}

함수이름이 명시적이라 한 번에 이해할 수 있는 경우 주석을 생략한다.

// MARK: - String Extension
extension String {
	/// 정규식 판별이라는 것이 
	func isValidRegex(_ regex: String) ->	Bool {

	}

	// 함수 이름에 한국 원으로 바꿔준다는 것이 명확함
	func toKoreanWon() -> String? {

	}

	// 달러로 바꿔준다는 것이 명확함
	func toDollar() -> String? {

	}
}

버전

버전 날짜 업데이트 내용
1.0 11 / 10 초안
1.1.0 11 / 15 줄바꿈 - Switch 추가
1.2.0 11 / 17 파일구조 추가
1.3.0 11 / 18 코드 네이밍 프로퍼티에 추가

출처

Clone this wiki locally