-
Notifications
You must be signed in to change notification settings - Fork 4
Swift 컨벤션
-
최대한 명시적인 이름을 사용한다.
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 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 | 코드 네이밍 프로퍼티에 추가 |