-
Notifications
You must be signed in to change notification settings - Fork 0
Swift Code Convention
23.12.21.(목) 변경사항
- MARK 주석
- (하이픈)
명시 내용 구체화 - 파라미터 마지막에 클로저가 등장할 시,
후행 클로저 사용
에 관한 내용 추가 - 클로저 사용 시,
Shorthand Argument
(단축인자. $0, $1, ...) 적극 활용 내용 추가 -
네임 스페이스
카테고리 추가 - 프로그래밍 권장사항에
addSubview(UIView)
,constraint
코드 스타일 관련 사항 추가
- 들여쓰기는 4개의 space를 사용한다.
- 콜론(
:
)을 사용할 때는, 콜론의 오른쪽에만 공백을 둔다.
-
코드 한 줄의 최대 길이를 99자로 제한하고, 최대 길이 초과 시 줄바꿈한다.
- 단, 불가피한 경우 최대 길이 초과를 허용한다.
- Xcode에서
Settings
-Text Editing
-Display
의Page guide at column
옵션을 체크하고 99자 설정.
-
함수의 정의가 최대 길이를 초과할 시, 아래와 같이 줄바꿈한다.
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() } )
- 단, 파라미터에 클로저가 2개 이상 존재하는 경우, 무조건 내려쓰기를 한다.
-
if let
구문이 길다고 느껴질 경우, 줄바꿈하고 한 칸 들여쓴다.if let user = self.veryLongFunctionNameWhichReturnsOptionalUser(), let name = user.veryLongFunctionNameWhichReturnsOptionalName(), user.gender == .female { // ... }
-
guard let
구문이 길다고 느껴질 경우, 줄바꿈하고 한 칸 들여쓴다.guard let user = self.veryLongFunctionNameWhichReturnsOptionalUser(), let name = user.veryLongFunctionNameWhichReturnsOptionalName(), user.gender == .female else { return }
- 모든 파일은 빈 줄로 끝나도록 한다.
- MARK 구문 위와 아래에는 공백을 삽입한다.
// MARK: - Layout override func layoutSubviews() { // doSomething() } // MARK: - Methods override func menuButtonDidTap() { // doSomething() }
- 모듈 임포트는 내장 프레임워크를 먼저 임포트하고, 빈 줄로 구분하여 서드파티 프레임워크를 임포트한다.
- 모듈 임포트는 알파벳 순으로 정렬한다.
import UIKit import SwiftyColor import SwiftyImage import Then import URLNavigator
- 네임 스페이스는 Font, Color, Text 등으로 구분되며, 각각의 파일로 관리한다.
enum TextLiteral { static let nameLabelText = "홍길동" static let bioLabelText = "남성" }
enum FontLiteral { static let nameLabelFont = UIFont.boldSystemFont(ofSize: 14) static let bioLabelFont = UIFont.boldSystemFont(ofSize: 12) }
enum ColorLiteral { static let nameLabelText = 0x000000.color static let bioLabelText = 0x333333.color ~ 70% }
- 클래스와 구조체의 이름에는
UpperCamelCase
를 사용한다.
- enum의 이름에는
UpperCamelCase
를 사용한다. - enum의 각 case에는
lowerCamelCase
를 사용한다.
-
프로토콜의 이름에는
UpperCamelCase
를 사용합니다. -
~을 할수 있음을 설명하는 프로토콜은 형용사로 작성해야 한다.
protocol Car { var speed: Int { get set } var name: String { get } func speedUp(speed: Int) -> Bool }
protocol Drivable { func accelerate(speed: Int) -> () func slowDown(speed: Int) -> () }
-
구조체나 클래스에서 프로토콜을 채택할 때는 콜론과 빈칸을 넣어 구분하여 명시합니다.
-
extension을 통해 채택할 때도 동일하게 적용됩니다.
-
함수 이름에는
lowerCamelCase
를 사용한다. -
함수 이름에는 되도록
get
과set
을 붙이지 않는다.좋은 예:
func name(for user: User) -> String?
나쁜 예:
func getName(for user: User) -> String?
-
Action 함수의 네이밍은
주어 + 동사 + 목적어
의 형태를 사용하며, 주어는 유추 가능하다면 생략할 수 있다.-
Tap(눌렀다 뗌) 은
UIControlEvents
의.touchUpInside
에 대응하고, Press(누름) 는.touchDown
에 대응한다. - will은 특정 행위가 일어나기 직전이고, did는 특정 행위가 일어난 직후이다.
- should는 일반적으로
Bool
을 반환하는 함수에 사용한다. - request는 에러가 발생허가너, 실패할 수 있는 비동기 작업에 사용한다
- fetch는 요청이 실패하지 않고, 결과를 바로 반환할 때 사용한다.
좋은 예:
func backButtonDidTap() { // ... }
나쁜 예:
func back() { // ... } func pressBack() { // ... }
-
Tap(눌렀다 뗌) 은
- 변수 이름에는
lowerCamelCase
를 사용한다.
- 상수 이름에는
lowerCamelCase
를 사용한다.
- 약어로 시작하는 경우 소문자로 표기하고, 그 외의 경우에는 항상 대문자로 표기한다.
-
잘 알려지지 않은 약어 사용은 지양하되, 필요 시 협의 후 사용한다.
let userID: Int? // ~ID let html: String? // html let websiteURL: URL? // ~URL let urlString: String? // url~
-
Delegate메서드는 프로토콜명으로 네임스페이스를 구분한다.
좋은 예
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) }
-
파라미터와 리턴 타입이 없는 Closure 정의 시,
() -> Void
형식을 사용한다. 좋은 예let completionBlock: (() -> Void)?
나쁜 예
let completionBlock: (() -> ())? let completionBlock: (Void -> Void)?
-
파라미터 마지막에 클로저가 등장할 시,
Trailing Closure(후행 클로저)
로 작성한다.func complete(number: Int, completion: @escaping () -> Void) { //... } complete(1) { print("Hello") }
-
Closure 정의 시, 가능한 경우 타입 정의를 생략하고,
Shorthand Argument
(= 단축인자. $0, $1, ...)를 적극 활용한다.좋은 예
..., completion: { print($0) // 단축인자 활용 권장 }
..., completion: { finished in // Argument를 명시하는 것이 코드 가독성에 유리할 때는 Argument 사용 doSomething() }
나쁜 예
..., completion: { (finished: Bool) -> Void in // doSomething() }
-
컴팩트한 코드를 지향하고, 컴파일러가 단일 인스턴스의 상수나 변수의 타입을 추론하도록 한다.
-
CGFloat나 Int64와 같은 노멀 타입이 아닌 경우, 특정 타입을 지정한다. 좋은 예
let apple = "Developer" let book1 = Book() let age = 25 let frameWidth: CGFloat = 120
나쁜 예
let apple: String = "Developer" let book1: Book = Book() let age: Int = 25
-
Array<T>
와Dictionary<T: U>
보다는 [T], [T: U]를 사용한다.좋은 예
var messages: [String]? var names: [Int: String]?
나쁜 예
var messages: Array<String>? var names: Dictionary<Int, String>?
-
빈 배열과 딕셔너리 선언 시, 타입 명시를 지향한다. 좋은 예
var student: [String: String] = [:] var students: [String] = []
나쁜 예
var student = [String: String]() var students = [String]()
- 주석 사용을 최소화한다.
- 설명이 필요한 코드일 시,
///
를 사용해 문서화에 사용되는 주석을 남긴다./// 사용자 프로필을 그려주는 뷰 class ProfileView: UIView { /// 사용자 닉네임을 그려주는 라벨 var nameLabel: UILabel! }
- MARK주석 사용 시,
-(하이픈)
을 사용해 연관된 코드를 구분짓는다.// MARK: - Init override init(frame: CGRect) { // doSomething() } deinit { // doSomething() } // MARK: Layout override func layoutSubviews() { // doSomething() }
-
가능하다면 변수를 정의할 때, 함께 초기화한다.
좋은 예
var number = 0
나쁜 예
var number: Int number = 0
- 상수를 정의할 때는
enum
을 만들어 비슷한 상수끼리 모아둔다.- 재사용성 및 유지보수성 향상
- struct를 사용하지 않아 생성자가 제공되지 않는 자료형을 사용할 수 있다.
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 } }
-
프로토콜(Protocol)을 채택할 때에는 extension을 만들어 해당 프로토콜과 관련된 메서드를 모아둔다.
좋은 예
final class MyViewController: UIViewController { // ... } // MARK: - UITableViewDataSource extension MyViewController: UITableViewDataSource { // ... } // MARK: - UITableViewDelegate extension MyViewController: UITableViewDelegate { // ... }
나쁜 예
final class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { // ... }
-
더 이상 상속이 발생하지 않는 클래스는 항상
final
키워드로 선언한다. -
변수 및 메서드에 접근제어자(open, public, internal, filePrivate, private)를 사용해 불필요한 접근을 최소화한다.
- Properties, Views, LifeCycle, Methods 순으로 구분하는 것을 원칙으로 한다.
- 필요 시, 세분화하여 구분할 수 있다.
-
LifeCycle
내부에는 직접적인 코드 작성을 지양한다.override func viewDidLoad() { super.viewDidLoad() attribute() layout() }
- UIView 등의 view의 속성을 클로저로 초기화할 때, 리턴 값은 Shorthand Argument($0, $1, ...)를 적극 활용한다.
- ViewController에 view의 레이아웃을 배치하는 것을
layout()
, view의 속성을 다루는 것은attribute()
, 그 외 설정에 대한 것은configure~()
또는setup~()
메서드에 작성한다. - layout()메서드는 addSubview(UIView)를 먼저 작성해 모든 view를 메모리에 올려둔 상태에서 각 view에 대한 Constraint를 설정한다.
예시
import UIKit
final class ViewController: UIViewController {
// MARK: - Properties
private var datum = [String]()
// MARK: - Enum
private enum Metric {
case ~
case ~
}
// MARK: - Views
private let label: UILabel = {
$0.text = ""
return $0
}(UILabel())
// MARK: - LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
layout()
attribute()
}
// MARK: - Methods
private func layout() {
view.addSubview(label)
label.snp.makeConstraints {
$0.edges.equalToSuperview()
}
}
private func attribute() {
view.background = .white
}
}
copyrightⓒ 2023 All rights reserved by JUNY0110