├── .github ├── CODEOWNERS ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── add-content.yml │ ├── report.yml │ └── update-content.yml └── PULL_REQUEST_TEMPLATE.md └── README.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # These owners will be the default owners for everything in the repo. 5 | * @M1zz @taek0622 @JUNY0110 @Lia316 @rriver2 @skycat0212 6 | 7 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Welcome 2 | 3 | swift-style-guide에 기여하러 오신 모든 분들을 열렬히 환영합니다! 4 | swift-style-guide는 여러분의 소중한 기여를 통해 성장하고 있습니다. 5 | 6 | ## 컨트리뷰션 가이드라인 7 | 8 | 아래의 가이드라인을 준수하여 풀리퀘스트를 확인 부탁드립니다: 9 | 10 | 1. 새로운 이슈를 추가하기 전에 중복인지 아닌지 검색을 통해 이전 이슈들을 확인해주세요. 11 | 2. 새로운 컨벤션 또는 기존 컨벤션의 업데이트 제안을 환영합니다. 12 | 3. 이슈에서 메인테이너의 본인 할당을 받으신 다음에 진행해주세요. (동시에 여러사람이 같은 작업을 막기 위해서 입니다.) 13 | 4. PR의 단위는 스타일 가이드의 컨벤션 입니다. (예시 : https://github.com/DeveloperAcademy-POSTECH/swift-style-guide/pull/1) 14 | 5. PR의 Development에서 진행한 이슈를 선택해주세요. (누군가 오랫동안 이슈를 방치하는 것을 방지하기 위함입니다.) 15 | - **각 컨벤션마다 개별 PR 요청을 해주세요.** 16 | 17 | 18 | 여러분의 기여로 우리는 모두 함께 성장합니다! :smiley: 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/add-content.yml: -------------------------------------------------------------------------------- 1 | name: ✨ 내용 추가 2 | description: 추가해야할 스타일 가이드 3 | title: "[내용 추가] <제목>" 4 | labels: ["✨ 내용 추가"] 5 | body: 6 | - type: input 7 | id: duplicate 8 | attributes: 9 | label: 이슈 중복 확인 10 | description: 본 이슈가 중복이 아닌지 검색을 통해 확인하셨나요? 11 | placeholder: 확인했습니다. 12 | validations: 13 | required: true 14 | - type: input 15 | id: category 16 | attributes: 17 | label: 카테고리 18 | description: 스타일 가이드 항목에 해당되는 카테고리를 작성해주세요. 19 | placeholder: ex. Naming of Delegates, Closure Expressions 20 | validations: 21 | required: true 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/report.yml: -------------------------------------------------------------------------------- 1 | name: 👻 오탈자 신고 2 | description: 오탈자 및 잘못된 내용 신고 3 | title: "[오탈자 신고] <제목>" 4 | labels: ["👻 오탈자 및 잘못된 내용"] 5 | body: 6 | - type: input 7 | id: duplicate 8 | attributes: 9 | label: 이슈 중복 확인 10 | description: 본 이슈가 중복이 아닌지 검색을 통해 확인하셨나요? 11 | placeholder: 확인했습니다. 12 | validations: 13 | required: true 14 | - type: input 15 | id: category 16 | attributes: 17 | label: 카테고리 18 | description: 스타일 가이드 항목에 해당되는 카테고리를 작성해주세요. 19 | placeholder: ex. Naming of Delegates, Closure Expressions 20 | validations: 21 | required: true 22 | - type: textarea 23 | id: content 24 | attributes: 25 | label: 내용 26 | description: 해당 내용의 잘못된 부분을 작성해주세요. 27 | placeholder: ex. ~의 내용은 ~ 부분이 잘못되어 ~라고 수정이 필요합니다. 28 | validations: 29 | required: true 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/update-content.yml: -------------------------------------------------------------------------------- 1 | name: 🥊 바꾸고 싶은 내용! 2 | description: 수정 및 삭제할 내용 3 | title: "[내용 수정 및 삭제] <제목>" 4 | labels: ["🥊 바꾸고 싶은 내용!"] 5 | body: 6 | - type: input 7 | id: duplicate 8 | attributes: 9 | label: 이슈 중복 확인 10 | description: 본 이슈가 중복이 아닌지 검색을 통해 확인하셨나요? 11 | placeholder: 확인했습니다. 12 | validations: 13 | required: true 14 | - type: input 15 | id: category 16 | attributes: 17 | label: 카테고리 18 | description: 스타일 가이드 항목에 해당되는 카테고리를 작성해주세요. 19 | placeholder: ex. Naming of Delegates, Closure Expressions 20 | validations: 21 | required: true 22 | - type: textarea 23 | id: reason 24 | attributes: 25 | label: 수정 이유 26 | description: 해당 내용의 수정 및 삭제가 필요한 이유를 간단하게 작성해주세요. 27 | placeholder: ex. 복수형의 이름을 s로만 구분하면 눈에 잘 띄지 않는 것 같아요 28 | validations: 29 | required: true 30 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## 카테고리 2 | 3 | 4 | 5 | 6 | 7 | ## 작업 사항 8 | 9 | 10 | 11 | 12 | 13 | ## 설명 및 이유 14 | 15 | 16 | 17 | 18 | 19 | ## 체크리스트 20 | 21 | 22 | 23 | 24 | - [ ] 기존에 없는 중복되지 않는 내용 25 | - [ ] 개요 및 이유 설명글 26 | - [ ] 오탈자 및 문법 확인 27 | 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swift-style-guide 2 | 3 | Apple Developer Academy의 개발자들이 따르고 있는 스위프트 스타일 가이드입니다. 다른 사람의 코드를 읽을 때 가독성을 높여주며, 4 | 내가 코드를 작성할 때 애매한 부분을 제거해줘 생산성 향상에 도움을 줍니다! 5 | 6 | 취향에 문제가 많기 때문에 코드를 작성하는데 어려움이 생긴다면 PR을 통해 언제나 의견을 주세요. 7 | 8 | 기여를 하고싶으신 분들을 위한 가이드는 [컨트리뷰션 가이드라인](./.github/CONTRIBUTING.md)을 참고해주세요. 9 | 10 | 11 | 12 | 13 | ## 목차 14 | 15 | 1. [네이밍](#네이밍) 16 | 1. [변수](#변수) 17 | 2. [함수](#함수) 18 | 3. [열거형](#열거형) 19 | 4. [구조체와 클래스](#구조체와-클래스) 20 | 5. [프로토콜](#프로토콜) 21 | 6. [델리게이트](#델리게이트) 22 | 2. [주석](#주석) 23 | 3. [띄어쓰기](#띄어쓰기) 24 | 4. 코드 구성 25 | 1. 미사용 코드 26 | 5. 접근제어자 27 | 6. 클래스와 스트럭트 28 | 7. 함수호출 29 | 8. [클로져](#클로져) 30 | 1. [후행 클로저 축약](#후행-클로저-축약) 31 | 2. [다중 후행 클로져](#다중-후행-클로져) 32 | 9. 타입 33 | 1. [타입 추론](#타입-추론) 34 | 2. [타입 어노테이션](#타입-어노테이션) 35 | 10. [메모리 관리](#메모리-관리) 36 | 11. [파일관리](#파일관리) 37 | 12. [SwiftUI](#SwiftUI) 38 | 1. [View 선언 방법](#View-선언-방법) 39 | 40 | 41 | ## 네이밍 42 | ### 변수 43 | - 변수 이름은 `lowerCamelCase`를 사용해주세요. 44 | - 배열과 같이 복수의 의미를 담고있는 변수라면 끝에 **s**를 붙여서 사용해주세요. 45 |
46 | 예제코드 47 | 48 | - **Good ✅** 49 | ```swift 50 | var categories: [String] 51 | var person: Person 52 | var isShowing: Bool 53 | ``` 54 | - **Bad ❌** 55 | ```swift 56 | var category: [String] 57 | var show: Bool 58 | ``` 59 |
60 | 61 | ### 함수 62 | - 함수 이름에는 `lowerCamelCase`를 사용해주세요. 63 | - 함수는 일반적으로 동사원형으로 시작해주세요. 64 | - Event-Handling 함수의 경우 (조동사 + 동사원형)으로 시작해주세요. 주어는 유추 가능하다면, 생략 가능합니다. 65 | - will은 특정 행위가 일어나기 직전을 의미합니다. 66 | - did는 특정 행위가 일어난 직후를 의미합니다. 67 |
68 | 예제코드 69 | 70 | - **Good ✅** 71 | ```swift 72 | class AcademyViewController { 73 | 74 | private func didFinishSession() { 75 | // ... 76 | } 77 | 78 | private func willFinishSession() { 79 | // ... 80 | } 81 | 82 | private func scheduleDidChange() { 83 | // ... 84 | } 85 | } 86 | ``` 87 | - **Bad ❌** 88 | ```swift 89 | class AcademyViewController { 90 | 91 | private func handleSessionEnd() { 92 | // ... 93 | } 94 | 95 | private func finishSession() { 96 | // ... 97 | } 98 | 99 | private func scheduleChanged() { 100 | // ... 101 | } 102 | } 103 | ``` 104 |
105 | 106 | - 데이터를 가져오는 함수의 경우, `get` 사용을 지양하고 `request`, `fetch`을 적절하게 사용해주세요. 107 | - `request` : 에러가 발생하거나, 실패할 수 있는 비동기 작업에 사용합니다. 예를 들어, http 통신을 통해 값을 요청하는 경우가 이에 해당합니다. 108 | - `fetch` : 요청이 실패하지 않고 결과를 바로 반환할 때 사용합니다. 예를 들어, data를 찾고자 하는 모든 행위를 할 때가 이에 해당합니다. 109 |
110 | 예제코드 111 | 112 | - **Good ✅** 113 | ```swift 114 | func reqeustData(for user: User) -> Data? 115 | func fetchData(for user: User) -> Data 116 | ``` 117 | - **Bad ❌** 118 | ```swift 119 | func getData(for user: User) -> Data? 120 | ``` 121 |
122 | 123 | 124 | ### 열거형 125 | - 열거형의 이름은 `UpperCamelCase`를 사용해주세요. 126 | - 열거형의 각 case에는 `lowerCamelCase`를 사용해주세요. 127 |
128 | 예제코드 129 | 130 | - **Good ✅** 131 | ```swift 132 | enum Result { 133 | case .success 134 | case .failure 135 | } 136 | ``` 137 | - **Bad ❌** 138 | ```swift 139 | enum result { 140 | case .Success 141 | case .Failure 142 | } 143 | ``` 144 |
145 | 146 | ### 구조체와 클래스 147 | - 구조체와 클래스의 이름은 `UpperCamelCase`를 사용해주세요. 148 | - 구조체와 함수의 이름 앞에 `prefix`를 붙이지 말아주세요. 149 | - 구조체와 클래스의 프로퍼티 및 메소드는 `lowerCamelCase`를 사용해주세요. 150 |
151 | 예제코드 152 | 153 | - **Good ✅** 154 | ```swift 155 | struct LeftRectangle { 156 | var width: Int 157 | var height: Int 158 | 159 | func drawRectangle() { 160 | // ... 161 | } 162 | } 163 | ``` 164 | ```swift 165 | class Mentee { 166 | let id: String 167 | let session: String 168 | var group: Int 169 | var team: Int 170 | 171 | func callOutMentor() { 172 | // ... 173 | } 174 | } 175 | ``` 176 | - **Bad ❌** 177 | ```swift 178 | struct rwRightRectangle { 179 | var Width: Int 180 | var Height: Int 181 | 182 | func DrawRectangle() { 183 | // ... 184 | } 185 | } 186 | ``` 187 | ```swift 188 | class rwMentor { 189 | let Id: String 190 | var Group: Int 191 | 192 | func GiveAdvice() { 193 | // ... 194 | } 195 | } 196 | ``` 197 |
198 | 199 | ### 프로토콜 200 | - 구조를 나타내는 프로토콜은 명사로 작성해야합니다. 201 | - 무언가를 할 수 있음(능력)을 설명하는 프로토콜은 형용사로 작성해야합니다. 202 |
203 | 예제코드 204 | 205 | - **Good ✅** 206 | ```swift 207 | protocol Car { 208 | var speed: Int { get set } 209 | var name: String { get } 210 | 211 | func speedUp(speed: Int) -> Bool 212 | } 213 | ``` 214 | ```swift 215 | protocol Drivable { 216 | func accelerate(speed: Int) -> () 217 | func slowDown(speed: Int) -> () 218 | } 219 | ``` 220 | - **Bad ❌** 221 | ```swift 222 | protocol Drivable { 223 | var speed: Int { get set } 224 | var name: String { get } 225 | 226 | func speedUp(speed: Int) -> Bool 227 | func accelerate(speed: Int) -> () 228 | func slowDown(speed: Int) -> () 229 | } 230 | ``` 231 |
232 | 233 | ### 델리게이트 234 | - protocol을 이용해 delegate 패턴을 구현합니다. 235 | - 함수의 첫번째 인자는 생략가능한 델리게이트의 소스 객체를 사용합니다. 236 | - **Good ✅** 237 | ```swift 238 | // 델리게이트의 소스 객체만을 메서드의 인자로 받는 경우 239 | protocol UserScrollViewDelegate { 240 | func scrollViewDidScroll(_ scrollView: UIScrollView) 241 | func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool 242 | } 243 | 244 | // 델리게이트의 소스 객체 다음에 추가적인 인자를 받는 경우 245 | protocol UserTableViewDelegate { 246 | func tableView( 247 | _ tableView: UITableView, 248 | willDisplayCell cell: Cell, 249 | cellForRowAt indexPath: IndexPath) 250 | ) 251 | func tableView( 252 | _ tableView: UITableView, 253 | numberOfRowsInSection section: Int) -> Int 254 | ) 255 | } 256 | ``` 257 | - **Bad ❌** 258 | ```swift 259 | protocol UserViewDelegate { 260 | // 인자를 생략한 경우 261 | func didScroll() 262 | // 델리게이트의 소스 객체를 인수로 사용하지 않은 경우 263 | func willDisplay(cell: Cell) 264 | // 함수명을 UpperCamelCase로 작성한 경우, 다른 클래스가 존재하면 컴파일 오류 발생 265 | func UserScrollView(_ scrollView: UIScrollView) 266 | } 267 | ``` 268 | 269 | ## 주석 270 | > 주석은 협업에 있어 가독성을 높이고 다른 사람의 코드를 이해하는 중요한 도구입니다. 271 | - 설명은 최대한 간결하고 핵심 요약에 집중해서 작성해주세요. 272 | - 함수와 메소드는 기본적으로 무엇을 하는지 무엇을 반환하는지 설명해주시고, 273 | 널효과나 void 반환은 생략합니다. 274 | - 작성한 주석은 퀵헬프 메뉴에서 언제든지 조회가 가능합니다. 275 |
276 | 예제코드 277 | 278 | - **Good ✅** 279 | ```swift 280 | /// 사용자 데이터를 추가합니다. 281 | /// - Parameter name: user fullname 282 | /// - Parameter age: user age 283 | func addData(name: String, age: Int) { 284 | // code to add data... 285 | } 286 | ``` 287 | 288 | ```swift 289 | /// DB내 사용자 이름과 ID로 나이를 조회합니다. 290 | /// - Parameter ID: user ID 291 | /// - Parameter name: user fullname 292 | /// - Returns: user age 293 | func readData(ID: Int, name: String) { 294 | var age: Int 295 | // code to read data... 296 | return age 297 | } 298 | ``` 299 | 300 | - **Bad ❌** 301 | ```swift 302 | // 사용자 데이터 추가 303 | func addData(name: String, age: Int) { 304 | // return void 305 | } 306 | ``` 307 |
308 | 309 | 310 | - 연관된 코드가 있다면 MARK를 사용하여 코드영역을 구분지는것을 권장합니다. 311 | 312 |
313 | 예제코드 314 | 315 | - **Example 💡** 316 | ```swift 317 | // MARK: - Gryffindor 318 | let password = "Fotuna Major" 319 | struct Gryffindor { 320 | let harry: String 321 | let ron: String 322 | let hermione: String 323 | } 324 | 325 | // MARK: - Slytherin 326 | class Slytherin { 327 | let voldemort: String 328 | let malfoy: String 329 | func deadlyCurse() { 330 | print("Avada Kedavra!") 331 | } 332 | } 333 | ``` 334 |
335 | 336 | 337 | - 아직 개발이 완료되지 않은 코드가 있다면 TODO나 FIXME를 사용하여 체크하는 것도 좋습니다. 338 |
339 | 예제코드 340 | 341 | - **Example 💡** 342 | ```swift 343 | // FIXME: - 버그 수정 필요 344 | public func buggyFunc() { 345 | // buggy code.. 346 | } 347 | 348 | // TODO: - 문자열 인코딩 함수 작업 계획 349 | private func todoFunc() { 350 | // tbd.. 351 | } 352 |
353 | 354 | 355 | ## 들여쓰기 356 | - 인덴테이션은 스페이스바 4개를 기본으로 하되, 스페이스바 4개는 탭 1개의 역할을 합니다. 357 |
358 | 예제코드 359 | 360 | - **Good ✅** 361 | ```swift 362 | func sayHiLeeo(isHappy: Bool) { 363 | if isHappy { 364 | print("Hi Leeo!") 365 | } 366 | } 367 | ``` 368 | - **Bad ❌** 369 | ```swift 370 | func sayHiLeeo(isHappy: Bool) { 371 | if isHappy { 372 | print("Hi Leeo!") 373 | } 374 | } 375 | ``` 376 |
377 | 378 | 379 | ## 띄어쓰기 380 | - 콜론(`:`)을 사용할 땐 콜론의 오른쪽으로 한 칸의 여백을 생성합니다. 콜론의 왼쪽은 공백없이 코드를 작성합니다. 381 | - **Example 💡** 382 | ```swift 383 | let leeo: HappyLeeo 384 | ``` 385 | 386 | ## 클로져 387 | ### 후행 클로저 축약 388 | - 단일 후행 클로저의 경우에는 타입유추, 함수 라벨 생략, 소괄호 생략을 사용합니다. 389 |
390 | 예제코드 391 | 392 | - **Function** 393 | ``` 394 | func someFunctionThatTakesAClosure(closure: (Int) -> Void) { 395 | // function body goes here 396 | } 397 | ``` 398 | 399 | - **Good ✅** 400 | ```swift 401 | someFunctionThatTakesAClosure { int in 402 | // trailing closure's body goes here 403 | } 404 | ``` 405 | 406 | - **Bad ❌** 407 | ```swift 408 | someFunctionThatTakesAClosure(closure: { (arguInt: Int) -> Void) 409 | // function body goes here 410 | }) 411 | ``` 412 |
413 | 414 | ### 다중 후행 클로져 415 | - 함수 또는 메서드의 형식 매개변수에서 클로져들만을 실 매개변수로 받는 경우 함수 또는 메서드 호출 시 함수 또는 메서드의 소괄호, 첫 번째 실 매개변수의 라벨, 실 매개변수 사이의 콤마를 생략합니다. 416 |
417 | 예제코드 418 | 419 | - **Good ✅** 420 | ```swift 421 | func doSomething(do: (String) -> Void, onSuccess: (Any) -> Void, onFailure: (Error) -> Void) { 422 | // function body 423 | } 424 | 425 | doSomething { something in 426 | // do closure 427 | } onSuccess: { result in 428 | // success closure 429 | } onFailure: { error in 430 | // failure closure 431 | } 432 | ``` 433 | 434 | - **Bad ❌** 435 | ```swift 436 | func doSomething(do: (String) -> Void, onSuccess: (Any) -> Void, onFailure: (Error) -> Void) { 437 | // function body 438 | } 439 | 440 | doSomething (do: { something in 441 | // do closure 442 | }, onSuccess: { result in 443 | // success closure 444 | }, onFailure: { error in 445 | // failure closure 446 | }) 447 | ``` 448 |
449 | 450 | 451 | ## 타입 452 | ### 타입 추론 453 | - 컴팩트 코드를 선호하고 컴파일러가 단일 인스턴스의 상수나 변수의 타입을 추론하도록 합니다. 454 | - 필요한 경우 `CGFloat`나 `Int64`와 같은 경우는 특정 타입을 지정해줍니다. 455 |
456 | 예제코드 457 | 458 | - **Good ✅** 459 | ```swift 460 | let apple = "Developer" 461 | let book1 = Book() 462 | let age = 25 463 | let frameWidth: CGFloat = 120 464 | ``` 465 | 466 | - **Bad ❌** 467 | ```swift 468 | let apple: String = "Developer" 469 | let book1: Book = Book() 470 | let age: Int = 25 471 | ``` 472 |
473 | 474 | ### 타입 어노테이션 475 | - 전체 제네릭 구문 `Array`와 `Dictionary` 보다는 단축 구문 `[T]`, `[T: U]`를 사용합니다. 476 |
477 | 예제코드 478 | 479 | - **Good ✅** 480 | ```swift 481 | var student: [String: String]? 482 | var students: [String]? 483 | ``` 484 | 485 | - **Bad ❌** 486 | ```swift 487 | var student: Dictionary? 488 | var students: Array? 489 | ``` 490 |
491 | 492 | 493 | - 빈 배열과 딕셔너리 선언 시, 타입을 명시하는 것을 선호합니다. 494 |
495 | 예제코드 496 | 497 | - **Good ✅** 498 | ```swift 499 | var student: [String: String] = [:] 500 | var students: [String] = [] 501 | ``` 502 | 503 | - **Bad ❌** 504 | ```swift 505 | var student = [String: String]() 506 | var students = [String]() 507 | ``` 508 |
509 | 510 | 511 | ## 메모리 관리 512 | - 메모리 누수의 원인이 되는 순환 참조가 일어나지 않도록 주의해주세요. 513 | - 객체 간의 관계를 분석하면서 `weak`와 `unowned`를 사용하여 순환 참조를 방지할 수 있습니다. 514 | - `weak` 참조 변수는 반드시 Optional 타입이어야 합니다. 515 |
516 | 예제코드 517 | 518 | - **Good ✅** 519 | ```swift 520 | class ExampleClass { 521 | weak var example: ExmapleClass? = nil 522 | 523 | init(){ 524 | print("init class") 525 | } 526 | 527 | deinit{ 528 | print("deinit class") 529 | } 530 | } 531 | 532 | // 객체 내의 인스턴스가 서로를 가리키고 있지만, weak 참조를 선언했기에 순환 참조가 일어나지 않습니다. 533 | var ex1: ExampleClass? = ExampleClass() 534 | var ex2: ExampleClass? = ExampleClass() 535 | 536 | ex1?.example = ex2 537 | ex2?.example = ex1 538 | 539 | ex1 = nil 540 | ex2 = nil 541 | 542 | // 출력결과 543 | // init class 544 | // init class 545 | // deinit class 546 | // deinit class 547 | ``` 548 |
549 | 550 | ## 파일관리 551 | - 파일 내에서 모듈 `import`를 알파벳순으로 지정하고 중복된 것들을 제거해주세요. 552 |
553 | 예제코드 554 | 555 | - **Good ✅** 556 | ```swift 557 | import Alamofire 558 | import Foundation 559 | import SnapKit 560 | ``` 561 | - **Bad ❌** 562 | ```swift 563 | import Foundation 564 | 565 | import SnapKit 566 | import Alamofire 567 | import Foundation 568 | ``` 569 | 570 |
571 | 572 | - `Computed properties`와 `property observers`가 있는 `property`는 같은 종류의 선언 집합 끝에 나타나야 합니다. 573 |
574 | 예제코드 575 | 576 | - **Good ✅** 577 | ```swift 578 | var gravity: CGFloat 579 | var atmosphere: Atmosphere { 580 | didSet { 581 | print("oh my god, the atmosphere changed") 582 | } 583 | } 584 | ``` 585 | - **Bad ❌** 586 | ```swift 587 | var atmosphere: Atmosphere { 588 | didSet { 589 | print("oh my god, the atmosphere changed") 590 | } 591 | } 592 | var gravity: CGFloat 593 | ``` 594 | 595 |
596 | 597 | ## SwiftUI 598 | ### View 선언 방법 599 | - 모든 뷰는 `Struct`로 정의하는 것을 권장합니다. `@ViewBuilder` function이나 computed property로 정의하는 것은 지양합니다. 600 | - 이를 통해 State와 Binding 등의 관계가 명확히 정의됩니다. 해당 뷰의 구현부를 보지 않고도 역할을 짐작할 수 있습니다. 601 | - function이나 computed property로 정의했을 때, 이를 다른 뷰에서도 재사용할 수 있게 바꾸려면 추가적인 작업이 필수입니다. 미리 struct로 정의해두면 이런 일을 방지할 수 있습니다. 602 |
603 | 604 |
605 | 예제코드 606 | 607 | - **Good ✅** 608 | ```swift 609 | struct Item: View { 610 | @State private var isFavorite: Bool = false 611 | var body: some View { 612 | FavoriteButton(isFavorite: $isFavorite) 613 | } 614 | 615 | struct FavoriteButton: View { // ✅ extension을 사용해서 정의해도 무방합니다 616 | @Binding var isFavorite: Bool 617 | var body: some View { 618 | Button { 619 | isFavorite.toggle() 620 | } label: { 621 | ... 622 | } 623 | } 624 | } 625 | } 626 | ``` 627 | - **Bad ❌** : `@ViewBuilder Function` 628 | ```swift 629 | struct Item: View { 630 | @State private var isFavorite: Bool = false 631 | var body: some View { 632 | FavoriteButton() 633 | } 634 | 635 | @ViewBuilder 636 | private func FavoriteButton() -> some View { // ❌ 637 | Button { 638 | isFavorite.toggle() 639 | } label: { 640 | ... 641 | } 642 | } 643 | } 644 | ``` 645 | - **Bad ❌** : `computed property` 646 | ```swift 647 | struct Item: View { 648 | @State private var isFavorite: Bool = false 649 | var body: some View { 650 | FavoriteButton 651 | } 652 | 653 | @ViewBuilder 654 | var FavoriteButton: some View { // ❌ 655 | Button { 656 | isFavorite.toggle() 657 | } label: { 658 | ... 659 | } 660 | } 661 | } 662 | ``` 663 |
664 | - 하나의 뷰 Struct에서 [레이아웃 컨테이너](https://developer.apple.com/documentation/swiftui/layout-fundamentals)(VStack, HStack, ZStack, Grid 등)는 최대 1개까지만 사용하는 것을 권장합니다. 665 | - 레이아웃 컨테이너를 2개 이상 겹치게 되면 배치의 방향성이 일관되지 않게 되므로 코드의 가독성이 매우 떨어집니다. 각 배치 방향이 무엇을 의미하는지 이름을 결정해두면 가독성이 더 좋아집니다. 666 | - 레이아웃 컨테이너는 남발되기 매우 쉽습니다. 레이아웃 컨테이너를 기준으로 뷰를 분리하는 과정에서 불필요한 레이아웃 컨테이너를 발견할 확률을 높일 수 있습니다. 667 |
668 | 669 |
670 | 예제코드 671 | 672 | - **Good ✅** 673 | ```swift 674 | struct Articles: View { 675 | var body: some View { 676 | VStack { 677 | Text("Featured") 678 | FeaturedArticles() // ✅ 679 | Divider() 680 | Text("All Articles") 681 | AllArticles() // ✅ 682 | } 683 | } 684 | 685 | struct FeaturedArticles: View { // ✅ 686 | var body: some View { 687 | HStack { 688 | NavigationLink { 689 | ... 690 | } label: { 691 | ... 692 | } 693 | NavigationLink { 694 | ... 695 | } label: { 696 | ... 697 | } 698 | } 699 | } 700 | } 701 | 702 | struct AllArticles: View { // ✅ 703 | var body: some View { 704 | HStack { 705 | NavigationLink { 706 | ... 707 | } label: { 708 | ... 709 | } 710 | NavigationLink { 711 | ... 712 | } label: { 713 | ... 714 | } 715 | } 716 | } 717 | } 718 | } 719 | ``` 720 | - **Bad ❌** 721 | ```swift 722 | struct Articles: View { 723 | var body: some View { 724 | VStack { 725 | Text("Featured") 726 | HStack { // ❌ 727 | NavigationLink { 728 | ... 729 | } label: { 730 | ... 731 | } 732 | NavigationLink { 733 | ... 734 | } label: { 735 | ... 736 | } 737 | } 738 | Divider() 739 | Text("All Articles") 740 | HStack { // ❌ 741 | NavigationLink { 742 | ... 743 | } label: { 744 | ... 745 | } 746 | NavigationLink { 747 | ... 748 | } label: { 749 | ... 750 | } 751 | } 752 | } 753 | } 754 | } 755 | ``` 756 |
757 | 758 | ## Reference 759 | - [Google Swift Style Guide](https://google.github.io/swift/) 760 | - [Airbnb Swift Style Guide](https://github.com/airbnb/swift) 761 | - [Linkedin Swift Style Guide](https://github.com/linkedin/swift-style-guide) 762 | - [Raywenderlich Swift Style Guide](https://github.com/raywenderlich/swift-style-guide) 763 | - [StyleShare Swift Style Guide](https://github.com/StyleShare/swift-style-guide#%EC%B5%9C%EB%8C%80-%EC%A4%84-%EA%B8%B8%EC%9D%B4) 764 | - [Channel Talk Swift Code Convention Guide](https://github.com/channel-io/ios-convention-guide) 765 | --------------------------------------------------------------------------------