├── .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 |
--------------------------------------------------------------------------------