├── README.md
└── contents
├── Template.md
├── week-1.md
├── week-10.md
├── week-2.md
├── week-3.md
├── week-4.md
├── week-5.md
├── week-6.md
├── week-7.md
├── week-8.md
└── week-9.md
/README.md:
--------------------------------------------------------------------------------
1 | # iOS 질문 모음
2 |
3 | **iOS 커뮤니티들의 기존 질문, 답변에 제가 찾은 정보와 찾으면서 참고했던 비슷한 질문들을 같이 정리해보았습니다.**
4 |
5 | **잘못된 정보 수정이나 추가하고 싶은 답변이 있으시다면 issue 나 PR 로 알려주시면 수정하겠습니다!**
6 |
7 | #### Questions
8 |
9 | 1. [alpha, opaque, opacity 의 차이를 모르겠어요.](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-1.md#q)
10 | 2. [특정 앱의 설치 여부를 확인하는 방법이 있나요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-1.md#q-1)
11 | 3. [화면 캡쳐를 막는 방법이 있나요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-1.md#q-2)
12 | 4. [이미지 애니메이션은 어떻게 구현하나요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-1.md#q-3)
13 | 5. [클로저에서 weak self 는 언제 사용하면 되나요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-1.md#q-4)
14 | 6. [움직이는 LaunchScreen 을 구현하고 싶어요.](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-2.md#q)
15 | 7. [override 할 때 super 의 메소드를 꼭 호출해야 하나요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-2.md#q-1)
16 | 8. [Designated initializer 와 Convenience initializer 의 차이가 무엇인가요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-2.md#q-2)
17 | 9. [loadView() 와 viewDidLoad() 의 차이가 무엇인가요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-2.md#q-3)
18 | 10. [iOS 13 미만에서도 애플 로그인을 구현해야 하나요? 구현해야 한다면 어떻게 구현하나요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-2.md#q-4)
19 | 11. [기기에서 애플 로그인을 할 때 사용자 정보(이름, 이메일)가 안 받아져요.](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-2.md#q-5)
20 | 12. [RxSwift 에서 DisposeBag 을 쓰는 이유가 무엇인가요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-3.md#q)
21 | 13. [leading, trailing 과 left, right 의 차이가 무엇인가요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-3.md#q-1)
22 | 14. [왜 IBOutlet 을 weak var 로 선언하나요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-3.md#q-2)
23 | 15. [메소드나 변수를 선언할 때 static 과 class 의 차이가 무엇인가요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-3.md#q-3)
24 | 16. [클로저 앞의 @escaping 은 무엇인가요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-3.md#q-4)
25 | 17. [class 와 final class 의 차이가 무엇인가요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-4.md#q)
26 | 18. [UI 업데이트는 왜 메인 스레드에서만 해야 하나요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-4.md#q-1)
27 | 19. [클래스 이름의 NS 접두사의 의미가 무엇인가요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-4.md#q-2)
28 | 20. [guard 와 if 의 차이점이 무엇인가요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-4.md#q-3)
29 | 21. [왜 delegate 를 weak 으로 선언하나요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-5.md#q)
30 | 22. [frame 과 bounds 의 차이가 무엇인가요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-5.md#q-1)
31 | 23. [App Transport Security 가 HTTP 통신을 차단해요.](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-5.md#q-2)
32 | 24. [DispatchQueue.global() 과 DispatchQueue.init() 의 차이가 무엇인가요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-6.md#q)
33 | 25. [DispatchQueue 에서 main.sync 는 언제 사용하나요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-6.md#q-1)
34 | 26. [특정한 여러 개의 비동기 작업이 완료된 후에 다른 작업을 실행해주고 싶어요.](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-6.md#q-2)
35 | 27. [동시에 실행되는 비동기 작업의 개수를 제한할 수 있나요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-6.md#q-3)
36 | 28. [== 와 === 의 차이가 무엇인가요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-7.md#q)
37 | 29. [mutating 이 무엇인가요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-7.md#q-1)
38 | 30. [lazy 가 무엇인가요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-7.md#q-2)
39 | 31. [UIWindow 가 무엇인가요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-8.md#q)
40 | 32. [ScrollView 에서 contentOffset 과 contentInset 이 무엇인가요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-8.md#q-1)
41 | 33. [오토레이아웃을 설정할 때의 margin 이 무엇인가요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-8.md#q-2)
42 | 34. [@objc 의 의미가 무엇인가요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-9.md#q)
43 | 35. [자기 자신의 프로퍼티나 메소드에 접근할 때 항상 self 를 붙여줘야 하나요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-9.md#q-1)
44 | 36. [코드로 오토레이아웃을 설정하고 싶어요.](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-9.md#q-2)
45 | 37. [뷰와 레이어의 차이가 무엇인가요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-10.md#q)
46 | 38. [Swift 에서 Optional 은 어떻게 구현되어 있나요?](https://github.com/TTOzzi/Question-Archive/blob/master/contents/week-10.md#q-1)
47 |
48 | #### 참고한 커뮤니티
49 | * [야곰닷넷](https://yagom.net/)
50 | * iOS Developers KR
51 | * [Apple Developer Forums](https://developer.apple.com/forums/)
52 | * [stackoverflow](https://stackoverflow.com/)
53 |
--------------------------------------------------------------------------------
/contents/Template.md:
--------------------------------------------------------------------------------
1 | # Template
2 |
3 | ### Q.
4 |
5 | > 질문 제목
6 |
7 | 질문 내용
8 |
9 | [질문 바로가기]()
10 |
11 | ### A.
12 |
13 | * 답변1
14 | * 답변2
15 | * ...
16 |
17 | ### 참고할 만한 비슷한 질문, 자료
18 |
19 | * [링크1]()
20 | * [링크2]()
21 | * ...
22 |
23 | -----
24 |
25 |
--------------------------------------------------------------------------------
/contents/week-1.md:
--------------------------------------------------------------------------------
1 | # iOS 질문모음 - 1
2 |
3 |
4 |
5 | ### Q.
6 |
7 | > alpha, opaque, opacity 의 차이를 모르겠어요.
8 |
9 | alpha, opaque, opacity 의 차이가 무엇인가요?
10 |
11 | [질문 바로가기](https://yagom.net/forums/topic/alpha-opaque-opacity의-차이를-명확히-모르겠어요-ㅠㅠ/)
12 |
13 | ### A.
14 |
15 | - [alpha](https://developer.apple.com/documentation/uikit/uiview/1622417-alpha) : 0.0 ~ 1.0 범위의 값으로, 0.0 은 완전한 투명, 1.0 은 완전한 불투명입니다.
16 |
17 | ++ 이 속성값을 변경하면 변경된 뷰 뿐만 아니라 변경된 뷰의 하위 뷰를 포함 한 모든 콘텐츠에도 영향을 줍니다.
18 |
19 | - [isOpaque](https://developer.apple.com/documentation/uikit/uiview/1622622-isopaque) : 뷰가 투명한지 불투명한지 결정하는 Bool 값, true 로 설정하면 drawing 시스템이 뷰를 완전히 불투명하게 처리합니다. -> 성능을 향상시킬 수 있음.
20 |
21 | 뷰의 투명도에 직접적인 영향을 주진 않고 drawing 시스템에게 뷰를 어떻게 처리해야 할지 알려줍니다. true 로 설정해주면 iOS 에서 뷰를 렌더링할 때 뷰가 불투명하다는 것을 가정하고 최적화하여 더 빠르게 렌더링합니다. 뷰에 조금이라도 불투명한 부분이 있다면 false 로 설정해주어야 합니다.
22 |
23 | - [opacity](https://developer.apple.com/documentation/quartzcore/calayer/1410933-opacity): 불투명도. 0.0(투명) ~ 1.0(불투명) 범위내에 있어야 하며, 해당 범위를 벗어나는 값은 최소값 또는 최대값으로 고정됩니다. 이 속성의 기본값은 1.0 이며, alpha 와 차이는 alpha 는 UIView 에, opacity 는 CALayer 의 프로퍼티라는 것입니다.
24 |
25 | ### 참고할 만한 비슷한 질문들
26 | * [UIView: opaque vs. alpha vs. opacity](https://stackoverflow.com/questions/8520434/uiview-opaque-vs-alpha-vs-opacity)
27 | * [Is the opacity and alpha the same thing for UIView](https://stackoverflow.com/questions/15381436/is-the-opacity-and-alpha-the-same-thing-for-uiview/15381634)
28 |
29 | ----
30 |
31 | ### Q.
32 |
33 | > 특정 앱의 설치 여부를 확인하는 방법이 있나요?
34 |
35 | 페이스북이나 인스타그램 같은 sns 앱들이 기기에 설치되어 있는지 확인할 수 있는 방법이 있을까요?
36 |
37 | [질문 바로가기](https://yagom.net/forums/topic/특정-앱이-설치되어-있는지-여부를-확인하는-방법이/)
38 |
39 | ### A.
40 |
41 | * [canOpenURL(_:)](https://developer.apple.com/documentation/uikit/uiapplication/1622952-canopenurl#discussion) 을 활용한 방법
42 |
43 | ```swift
44 | func isInstagramInstalled() -> Bool {
45 | return UIApplication.shared.canOpenURL("instagram://app".url())
46 | }
47 | func isFacebookInstalled() -> Bool {
48 | return UIApplication.shared.canOpenURL("fb://".url())
49 | }
50 | ```
51 |
52 | 매개 변수로 URL 을 받아 해당 URL 을 처리할 수 있는 앱이 있는지를 반환해주는 메소드입니다. **iOS 9.0 이상에서 사용할 때는 앱의 Info.plist 파일에 [LSApplicationQueriesSchemes](https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html#//apple_ref/doc/plist/info/LSApplicationQueriesSchemes) 를 추가해주어야 합니다. URLScheme 을 추가하지 않으면 앱의 설치 여부와 관계없이 항상 false 를 반환합니다.**
53 |
54 | * SDK 를 활용한 방법 ([Kakao iOS SDK](https://developers.kakao.com/sdk/reference/ios-legacy/release/Classes/KLKTalkLinkCenter.html#//api/name/isAvailableWithError:))
55 |
56 | ```swift
57 | func isKakaoInstalled() -> Bool {
58 | return KLKTalkLinkCenter.shared().isAvailableWithError()
59 | }
60 | ```
61 |
62 | 카카오톡뿐만 아니라 [페이스북](https://developers.facebook.com/docs/ios/), [트위터](https://developer.twitter.com/en/docs/developer-utilities/twitter-libraries) 등 다른 sns 앱들도 SDK 나 library 를 지원하고 있으니 찾아보면 다른 방법들도 있을 것 같습니다.
63 |
64 | ### 참고할 만한 비슷한 질문들
65 |
66 | * [How to check app is installed or not in phone](https://stackoverflow.com/questions/41545283/how-to-check-app-is-installed-or-not-in-phone)
67 |
68 | ----
69 |
70 | ### Q.
71 |
72 | > 화면 캡쳐를 막는 방법이 있나요?
73 |
74 | 캡쳐를 감지하는 Notification 은 찾았는데 막는 방법을 못 찾겠어요.
75 |
76 | 사용자가 캡쳐를 할 때 보내지는 [userDidTakeScreenshotNotification](https://developer.apple.com/documentation/uikit/uiapplication/1622966-userdidtakescreenshotnotificatio)
77 |
78 | [질문 바로가기](https://yagom.net/forums/topic/화면-캡쳐를-막는-방법을-알아보고-있는데요-어떻게/)
79 |
80 | ### A.
81 |
82 | * 어떤 원리를 통해 가능한지는 잘 모르겠지만 [ScreenShieldKit](https://screenshieldkit.com/) 이라는 상용 라이브러리도 있다고 들었습니다. 기본적으로 완전히 막지는 못하고 막아야 할 콘텐츠만 안 보이게 되는 것 같아요. 또, 저 라이브러리로 화면이 영상 녹화되는지도 파악하고 콘텐츠를 안 보이게 할 수 있는 것 같습니다.
83 | * 영상 녹화 중 안 보이게 하려면 이런 방법을 통해서도 가능할 듯합니다. [Detecting screen capturing in iOS 11](https://medium.com/@abhimuralidharan/detecting-screen-capturing-in-ios-11-cca15881c785)
84 |
85 | * 다른 사이트들에서 [Photos 프레임워크](https://developer.apple.com/documentation/photokit)와 userDidTakeScreenshotNotification 을 활용해 유저가 화면을 캡쳐했을 때 사진 앱에 접근해 캡쳐된 사진을 지우는 방법이 있다는 답변이 많아 직접 구현해 보았습니다.
86 |
87 | ```swift
88 | NotificationCenter.default.addObserver(forName: UIApplication.userDidTakeScreenshotNotification, object: nil, queue: .main) { _ in
89 | let fetchOptions = PHFetchOptions()
90 | // 생성 날짜 순으로 정렬
91 | fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
92 |
93 | // 지정한 옵션으로 정렬된 모든 이미지를 가져옴
94 | let fetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
95 |
96 | // 정렬된 이미지 중 가장 최근 이미지를 가져옴
97 | guard let capturedImage = fetchResult.firstObject else { return }
98 |
99 | // 삭제 동작 수행
100 | PHPhotoLibrary.shared().performChanges({
101 | PHAssetChangeRequest.deleteAssets([capturedImage] as NSFastEnumeration)
102 | }) { isSuccess, error in
103 | // 성공, 실패 후 동작 처리
104 | }
105 | }
106 | ```
107 |
108 | 그러나 생각했던 것과 달리 notification 을 받은 시점이 아직 사진 앱에 스크린샷이 추가되기 전이고, 사진 앱에 추가된 후라고 하더라도
109 |
110 |
111 |
112 | 위 사진처럼 매번 사용자에게 권한을 요청하여 캡쳐를 막는다고 보긴 어려웠습니다.
113 |
114 | ### 참고할 만한 비슷한 질문들
115 |
116 | * [Prevent user from taking screenshot of my app like Netflix](https://developer.apple.com/forums/thread/123725)
117 | * [Prevent screen capture in an iOS app](https://stackoverflow.com/questions/18680028/prevent-screen-capture-in-an-ios-app)
118 | * [iOS 11 Screen Recording - Copyright Risks](https://developer.apple.com/forums/thread/86521)
119 |
120 | ----
121 |
122 | ### Q.
123 |
124 | > 이미지 애니메이션은 어떻게 구현하나요?
125 |
126 | gif 로드, 애니메이션 프레임 수 만큼 이미지 파일 리스트를 반복으로 보여주기, 코드로 개별 이미지 애니메이션 구현 등 여러 방식을 적용해보고 있는데 여러 개의 이미지를 활용한 애니메이션을 구현할 때의 효율적인 방법이 있나요?
127 |
128 | [질문 바로가기](https://yagom.net/forums/topic/이미지-애니메이션-구현하실때-어떤-방식으로-하시/)
129 |
130 | ### A.
131 |
132 | * iOS 에서 gif 는 [UIImageView](https://developer.apple.com/documentation/uikit/uiimageview) 등의 클래스에서 직접적으로 지원하지 않습니다.
133 |
134 | [WKWebView](https://developer.apple.com/documentation/webkit/wkwebview) 등을 통한 간접적인 방법을 사용해야 하므로 효율이 좋지 못하고 성능상의 이점도 없습니다.
135 |
136 | * 가장 간단한 방법으로는 UIImageView 클래스의 [animationImages](https://developer.apple.com/documentation/uikit/uiimageview/1621068-animationimages) 프로퍼티를 사용하여 애니메이션에 사용할 이미지를 넣는 방법입니다. [How To Make iOS Animations With UIImageView in Swift](https://marcosantadev.com/make-uiimageview-animations/) 를 참고해보세요. 만약 좀 더 상세하게 제어하고 싶거나 복잡한 애니메이션이 필요하다고 생각하면 라이브러리를 활용하는 것도 좋은 방법입니다.
137 |
138 | * 효율을 위해서는 내장되어있는 기능을 사용하는 것이 가장 좋으나 디자이너가 복잡한 애니메이션을 요구하는 경우도 많습니다. 그럴 때 사용할 만한 [lottie](https://lottiefiles.com/) 라는 라이브러리도 있습니다. JSON 형식의 애니메이션을 사용하는데 꽤 직관적이어서 좋습니다. [lottie-ios github 저장소](https://github.com/airbnb/lottie-ios)
139 |
140 | * [UIImage](https://developer.apple.com/documentation/uikit/uiimage) 클래스의 [animatedImage(with:duration:)](https://developer.apple.com/documentation/uikit/uiimage/1624149-animatedimage) 메소드를 활용하는 방법도 있습니다.
141 |
142 | * 이미지 애니메이션에 한정된 것은 아니지만 좀 더 복잡하고 부드러운 애니메이션을 원한다면 [animateKeyframes(withDuration:delay:options:animations:completion:)](https://developer.apple.com/documentation/uikit/uiview/1622552-animatekeyframes) 나 [Core Animation](https://developer.apple.com/documentation/quartzcore) 을 활용한 여러 가지 방법들이 있습니다.
143 |
144 | ### 참고할 만한 비슷한 질문들
145 |
146 | * [How to animate the change of image in an UIImageView](https://stackoverflow.com/questions/2834573/how-to-animate-the-change-of-image-in-an-uiimageview)
147 | * [Swift - How to animate Images?](https://stackoverflow.com/questions/24364504/swift-how-to-animate-images)
148 | * [Animation using array of images in sequence](https://stackoverflow.com/questions/6040528/animation-using-array-of-images-in-sequence)
149 |
150 | ----
151 |
152 | ### Q.
153 |
154 | > 클로저에서 weak self 는 언제 사용하면 되나요?
155 |
156 | ```swift
157 | class Thing {
158 | var disposable: Disposable?
159 | var total: Int = 0
160 | deinit {
161 | disposable?.dispose()
162 | }
163 | init(producer: SignalProducer) {
164 | disposable = producer.startWithNext { number in
165 | self.total += number
166 | print(self.total)
167 | }
168 | }
169 | }
170 | ```
171 |
172 | 위 코드에서 클로저는 self 가 해제될 때 까지 기다리고 self 는 클로저가 해제될 때까지 기다리는 strong reference cycle 상황을 만든다고 하는데 이 부분이 이해가 안됩니다.
173 |
174 | ```swift
175 | disposable = producer.startWithNext { [weak self] number in
176 | self?.total += number
177 | print(self?.total)
178 | }
179 | ```
180 |
181 | 위와 같이 클로저 내부에서 self 를 사용하게 되는 모든 경우에서 [weak self] 를 사용하면 되는 것인지도 궁금합니다.
182 |
183 | 코드 출처 [[Swift] Closure에서 weak self 의 사용](https://greenchobo.tistory.com/3)
184 |
185 | [질문 바로가기](https://yagom.net/forums/topic/closure의-weak-self-사용에-대한-질문입니다/)
186 |
187 | ### A.
188 |
189 | * 우선 위 내용을 이해하려면 스위프트의 ARC 라는 개념과 클로저의 Capture 라는 개념을 이해해야 합니다.
190 |
191 | 예제 코드의 클로저는 클로저가 생성되는 시점의 인스턴스 상태를 유지하기 위해 클로저 내부의 인스턴스를 모두 획득(capture)해둡니다. 그러기 위해서 **self** 즉, 클로저는 프로퍼티로 갖게 되는 **Thing** 클래스의 인스턴스가 클로저에 의해 획득되어 reference count 가 1 증가합니다. 또, 해당 클로저가 **disposable** 이라는 인스턴스 프로퍼티에 할당되므로 클로저의 reference count 도 1 증가합니다. 서로 reference count 를 올려주기 때문에 순환참조 문제가 발생합니다.
192 |
193 | 이에 대해 더 알아보고 싶다면 스위프트 언어 가이드 문서 중 [ARC 문서](https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html) 를 참고하면 훨씬 도움이 됩니다. 영어가 너무 어렵다면 ARC 및 클로저 값 획득 등의 내용으로 검색해보면 많은 내용을 찾아볼 수 있습니다.
194 |
195 | [ARC 문서 한글 번역본: [Swift] Automatic Reference Counting 정리](http://minsone.github.io/mac/ios/swift-automatic-reference-counting-summary)
196 |
197 | * 순환참조 문제를 피하는 또 하나의 방법으로 unowned 도 있습니다. Optional unwrapping 을 고려하지 않아도 된다는 장점이 있지만, unowned 로 참조된 인스턴스가 메모리에서 해제된 후 접근을 시도하면 런타임 에러가 발생합니다. 참조할 인스턴스가 항상 해제되지 않은 상태라는 확신이 있을 때만 사용해야 합니다.
198 |
199 | ### 참고할 만한 비슷한 질문들
200 |
201 | * [Is it the right way using '[weak self]' in swift closure?](https://stackoverflow.com/questions/54719191/is-it-the-right-way-using-weak-self-in-swift-closure)
202 | * [Where does the weak self go?](https://stackoverflow.com/questions/41991467/where-does-the-weak-self-go)
203 | * [Shall we always use [unowned self] inside closure in Swift](https://stackoverflow.com/questions/24320347/shall-we-always-use-unowned-self-inside-closure-in-swift)
204 | * [When to use an unowned reference for closure](https://developer.apple.com/forums/thread/121522)
205 |
206 |
--------------------------------------------------------------------------------
/contents/week-10.md:
--------------------------------------------------------------------------------
1 | # iOS 질문모음 - 10
2 |
3 | ### Q.
4 |
5 | > 뷰와 레이어의 차이가 무엇인가요?
6 |
7 | 화면을 그리는 코드를 작성할 때 UIView 를 사용할 때가 있고 CALayer 를 사용할 때가 있는데, UIView 와 CALayer 의 차이가 무엇인가요?
8 |
9 | [질문 바로가기](https://stackoverflow.com/questions/7826306/what-are-the-differences-between-a-uiview-and-a-calayer)
10 |
11 | ### A.
12 |
13 | * 뷰와 레이어의 차이를 이해하려면 먼저 [CoreAnimation](https://developer.apple.com/documentation/quartzcore) 과 [UIKit](https://developer.apple.com/documentation/uikit) 의 차이부터 알아야 하는데요.
14 |
15 | 
16 |
17 | 위 사진은 iOS 에서 화면을 그릴 때 사용하는 프레임워크의 계층구조입니다(AppKit 은 macOS). CoreAnimation, CoreGraphics 는 많은 기능을 제공하지만, 간단한 기능을 구현하는 데에도 많은 코드를 작성해야 한다는 번거로움이 있습니다. 일반적으로 앱을 구현할 때 복잡한 UI, 애니메이션이 필요하지 않기 때문에 자주 사용하는 인터페이스들이 구현된 더 높은 수준의 프레임워크를 제공해주는데 이것이 UIKit 입니다. 그리고 이 UIKit 에서 제공해주는 클래스가 UIView 이고, CALayer 는 한 단계 낮은 수준인 CoreAnimation 의 클래스입니다.
18 |
19 | * [CALayer](https://developer.apple.com/documentation/quartzcore/calayer) 는 시각적 컨텐츠를 관리하고 컨텐츠를 화면에 표시하는 데 사용되는 위치, 크기, 변형과 같은 기하학적 정보를 가집니다. 레이어의 속성을 수정하여 콘텐츠에 애니메이션을 적용할 수도 있습니다. 레이어는 단순한 그래픽 표현만을 수행하며 별도의 스레드에서 GPU 를 사용해 그려집니다. 뷰에 비해 더 세부적인 설정이 가능하며 성능상 효율이 더 좋습니다.
20 |
21 | * [UIView](https://developer.apple.com/documentation/uikit/uiview) 는 UI 의 기본 구성 요소입니다. 직사각형 영역에서 컨텐츠를 렌더링하고 컨텐츠에서 발생하는 모든 이벤트를 처리합니다.
22 |
23 | 
24 |
25 | iOS 에서 모든 뷰는 레이어를 포함하고 있습니다. 뷰는 레이어를 포함하고 있고, 그 레이어를 그려주는 인터페이스와 이벤트 처리를 포함한 별도의 클래스입니다(공식문서에서는 thin wrapper 라고 표현합니다). 뷰는 [UIResponder](https://developer.apple.com/documentation/uikit/uiresponder) 를 상속받아 터치, 모션, 프레스와 같은 이벤트를 처리합니다. 뷰와 관련된 작업은 레이어와 달리 이벤트 처리를 하므로 메인 스레드에서 발생하며 CPU 를 사용합니다. 뷰에 의해 레이어가 생성되면 뷰가 자기 자신을 레이어의 [delegate](https://developer.apple.com/documentation/quartzcore/calayer/1410984-delegate) 로 자동 할당합니다. 뷰를 통해 컨텐츠를 그리는 코드를 작성할 수 있지만, 이는 뷰가 처리하는 것이 아닌 뷰가 포함하는 레이어에 위임해서 컨텐츠를 그리도록 하는 것 입니다.
26 |
27 | * 대부분의 경우 뷰를 활용해 화면을 그리겠지만, 레이어를 사용하는 것이 훨씬 좋거나 레이어를 사용해야만 하는 경우가 있습니다. 막대차트, 파이차트와 같은 이벤트 처리가 필요 없으면서 세부적인 시각적 표현이 필요한 경우나, 복잡한 애니메이션 설정이 필요한 경우 레이어를 사용해 구현하는 것이 좋습니다. CoreAnimation 은 [CATextLayer](https://developer.apple.com/documentation/quartzcore/catextlayer), [CAShapeLayer](https://developer.apple.com/documentation/quartzcore/cashapelayer) 등의 여러 가지 레이어 클래스를 제공하며, 이들을 활용해 정말 다양한 그래픽 표현이 가능합니다. 다양한 레이어 클래스를 활용한 예시는 [CALayer Tutorial for iOS: Gettinng Started](https://www.raywenderlich.com/10317653-calayer-tutorial-for-ios-getting-started) 를 참고하세요.
28 |
29 | ### 참고할 만한 비슷한 질문, 자료
30 |
31 | * [UIView vs CALayer](https://medium.com/@fassko/uiview-vs-calayer-b55d932ff1f5)
32 | * [iOS Brownbag: Views vs. Layers](https://dzone.com/articles/ios-brownbag-views-vs-layers)
33 | * [A Beginner's Guide to CALayer](https://www.appcoda.com/calayer-introduction/)
34 | * [Core Animation Programming Guide](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40004514-CH1-SW1)
35 |
36 | -----
37 |
38 | ### Q.
39 |
40 | > Swift 에서 Optional 은 어떻게 구현되어 있나요?
41 |
42 | Optional 이 어떻게 구현되어 있는지 궁금해요.
43 |
44 | [질문 바로가기](https://stackoverflow.com/questions/24548475/how-are-optional-values-implemented-in-swift)
45 |
46 | ### A.
47 |
48 | * 해당 답변은 [Swift](https://github.com/apple/swift) 의 [Optional.swift](https://github.com/apple/swift/blob/main/stdlib/public/core/Optional.swift#L205) 코드를 설명하는 글입니다. 더 자세한 정보를 원한다면 해당 코드를 직접 보시는 것을 추천합니다.
49 |
50 | * Swift 에서 [Optional](https://developer.apple.com/documentation/swift/optional) 은 제네릭을 활용한 열거형으로 구현되어있습니다.
51 |
52 | ```swift
53 | public enum Optional: ExpressibleByNilLiteral {
54 | case none
55 | case some(Wrapped)
56 | }
57 | ```
58 |
59 | `none` 과 `some` 총 두 가지의 케이스를 가지며 `none` 은 값이 없는 경우를, `some` 은 값이 있는 경우를 나타냅니다. 옵셔널 타입을 선언할 때, `Optional` 와 같은 형태로 사용할 수도 있지만, 보통 `Int?` 와 같이 타입 뒤에 물음표 접미사를 붙여 타입이 옵셔널임을 나타냅니다.
60 |
61 | * 옵셔널 타입은 총 두 개의 생성자가 구현되어있습니다.
62 |
63 | ```swift
64 | public init(_ some: Wrapped) { self = .some(some) }
65 | ```
66 |
67 | 할당되는 값이 있을 때 사용되는 생성자입니다. 제네릭 파라미터로 전달되는 `Wrapped` 타입의 값을 받아 인스턴스에 `some` 케이스를 할당합니다.
68 |
69 | ```swift
70 | let number: Int? = Optional(3) // 이런 방법으로도 생성할 수 있지만,
71 | let number: Int? = 3 // 이렇게만 값을 할당해줘도 타입 추론을 통해 내부적으로 init(_ some: Wrapped) 생성자가 호출됩니다.
72 | ```
73 |
74 | 다음 생성자는 할당되는 값이 nil 일때 사용되는 생성자입니다.
75 |
76 | ```swift
77 | // var number: Int? = nil 처럼 초기값이 nil 로 할당될 때 사용됩니다.
78 | public init(nilLiteral: ()) {
79 | self = .none
80 | }
81 | ```
82 |
83 | 이 생성자는 [ExpressibleByNilLiteral](https://developer.apple.com/documentation/swift/expressiblebynilliteral) 프로토콜의 요구사항입니다. 인스턴스에 `none` 케이스를 할당합니다.
84 |
85 | ```swift
86 | let number: Int? = Optional(nilLiteral: ()) // 이번에도 이렇게 생성할 수 있지만,
87 | let number: Int? = nil // 이렇게 nil 을 할당해줘도 내부적으로 init(nilLiteral: ()) 가 호출됩니다.
88 | ```
89 |
90 | 값이 있는 경우, 없는 경우 둘 다 직접 생성자를 호출할 일은 없습니다. 생성자를 생략하고 값을 할당해주어야 합니다.
91 |
92 | * 옵셔널 인스턴스를 강제언래핑 할 때 붙이는 `!` 접미사는 다음과 같이 구현되어 있습니다.
93 |
94 | ```swift
95 | public var unsafelyUnwrapped: Wrapped {
96 | get {
97 | if let x = self {
98 | return x
99 | }
100 | _debugPreconditionFailure("unsafelyUnwrapped of nil optional")
101 | }
102 | }
103 | ```
104 |
105 | `if let` 으로 `self` 를 바인딩하여 값이 있다면 값을 반환하고, 없다면 런타임에러를 발생시킵니다.
106 |
107 | ```swift
108 | var number: Int? = 3
109 | number.unsafelyUnwrapped // 3
110 | number = nil
111 | number.unsafelyUnwrapped // 런타임 에러 발생
112 | number! // number.unsafelyUnwrapped 와 같습니다.
113 | ```
114 |
115 | 직접 [unsafelyUnwrapped](https://developer.apple.com/documentation/swift/optional/1641793-unsafelyunwrapped) 를 호출할 수도 있지만, 보통은 접미사 `!` 를 붙여 `number!` 처럼 사용합니다.
116 |
117 | * 옵셔널 값에 기본값을 설정해주는 `??` 연산자의 구현입니다.
118 |
119 | ```swift
120 | public func ?? (optional: T?, defaultValue: @autoclosure () throws -> T)
121 | rethrows -> T {
122 | switch optional {
123 | case .some(let value):
124 | return value
125 | case .none:
126 | return try defaultValue()
127 | }
128 | }
129 | ```
130 |
131 | `Optional` 타입이 열거형으로 구현되어있어 `switch-case` 문으로 값이 있을 때와 없을 때를 나눠 구현된 것을 확인할 수 있습니다. 연산자의 왼쪽에 위치할 인자에 값이 존재한다면 그 값을, 존재하지 않는다면 `defaultValue` 클로저를 실행해 기본값을 반환합니다. 다음과 같이 사용됩니다.
132 |
133 | ```swift
134 | var number: Int? = 3
135 | print(number ?? 0) // 3 출력
136 | number = nil
137 | print(number ?? 0) // number 의 값이 없으므로 기본값 0 출력
138 | ```
139 |
140 | * 옵셔널 값을 클로저를 통해 변환하기 위한 [map](https://developer.apple.com/documentation/swift/optional/1539476-map), [flatMap](https://developer.apple.com/documentation/swift/optional/1540500-flatmap) 이 구현되어 있습니다.
141 |
142 | ```swift
143 | public func map(
144 | _ transform: (Wrapped) throws -> U // 클로저의 반환값이 옵셔널이 아님
145 | ) rethrows -> U? {
146 | switch self {
147 | case .some(let y):
148 | return .some(try transform(y))
149 | case .none:
150 | return .none
151 | }
152 | }
153 |
154 | public func flatMap(
155 | _ transform: (Wrapped) throws -> U? // 클로저의 반환값이 옵셔널
156 | ) rethrows -> U? {
157 | switch self {
158 | case .some(let y):
159 | return try transform(y)
160 | case .none:
161 | return .none
162 | }
163 | }
164 | ```
165 |
166 | 인자로 받은 클로저로 옵셔널 인스턴스의 값을 변환합니다. 두 메소드의 차이는 클로저 반환값의 옵셔널 여부입니다. `map` 은 클로저의 결과값으로 옵셔널이 아닌 값을 반환해야 하고, `flatMap` 은 옵셔널 값을 반환해도 됩니다.
167 |
168 | ```swift
169 | func map(_ string : String?) -> Int? {
170 | return string.map { Int($0) } // Value of optional type 'Int?' must be unwrapped to a value of type 'Int' 에러 발생
171 | }
172 |
173 | func flatMap(_ string: String?) -> Int? {
174 | return string.flatMap { Int($0) }
175 | }
176 | ```
177 |
178 | `map`, `flatMap` 을 사용하여 `String?` 을 `Int?` 로 변환하는 함수입니다. `map` 을 사용한 함수에선 클로저의 반환값이 `Int?` 가 아닌 `Int` 여야 한다는 컴파일 에러가 발생합니다.
179 |
180 | * 디버깅을 위한 프로토콜도 채택하고 있습니다.
181 |
182 | ```swift
183 | extension Optional: CustomDebugStringConvertible {
184 | public var debugDescription: String {
185 | switch self {
186 | case .some(let value):
187 | var result = "Optional("
188 | debugPrint(value, terminator: "", to: &result)
189 | result += ")"
190 | return result
191 | case .none:
192 | return "nil"
193 | }
194 | }
195 | }
196 |
197 | extension Optional: CustomReflectable {
198 | public var customMirror: Mirror {
199 | switch self {
200 | case .some(let value):
201 | return Mirror(
202 | self,
203 | children: [ "some": value ],
204 | displayStyle: .optional)
205 | case .none:
206 | return Mirror(self, children: [:], displayStyle: .optional)
207 | }
208 | }
209 | }
210 | ```
211 |
212 | 디버깅 할 때 텍스트 출력을 위한 문자열을 정의하는 [CustomDebugStringConvertible](https://developer.apple.com/documentation/swift/customdebugstringconvertible) 과 인스턴스의 구조를 표현해주는 [Mirror](https://developer.apple.com/documentation/swift/mirror) 를 정의하는 [CustomReflectable](https://developer.apple.com/documentation/swift/customreflectable) 을 채택하고 있습니다.
213 |
214 | ```swift
215 | let nilNumber: Int? = nil
216 | let number: Int? = 3
217 |
218 | print("CustomDebugStringConvertible\n")
219 | debugPrint(nilNumber)
220 | debugPrint(number)
221 | print("\nCustomReflectable\n")
222 | dump(nilNumber)
223 | dump(number)
224 | ```
225 |
226 | 두 프로토콜을 사용하는 대표적인 메소드인 [debugPrint](https://developer.apple.com/documentation/swift/1539920-debugprint) 와 [dump](https://developer.apple.com/documentation/swift/1539127-dump) 를 호출해보았습니다.
227 |
228 |
229 |
230 | 내부 구현에 맞게 출력되고 있는 것을 확인할 수 있습니다.
231 |
232 | * 옵셔널의 제네릭 파라미터로 전달되는 `Wrapped` 타입이 [Hashable](https://developer.apple.com/documentation/swift/hashable) 이나 [Equatable](https://developer.apple.com/documentation/swift/equatable) 을 만족할 때도 `Equatable` 의 `==` 연산자, `Hashable` 의 `hash(into:)` 메소드가 제대로 작동하도록 기본구현이 되어있습니다. [where 절을 활용한 extension](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID553) 으로 메소드들의 기본구현을 해주고 있습니다.
233 |
234 | ```swift
235 | extension Optional: Equatable where Wrapped: Equatable {
236 | public static func ==(lhs: Wrapped?, rhs: Wrapped?) -> Bool {
237 | switch (lhs, rhs) {
238 | case let (l?, r?):
239 | return l == r
240 | case (nil, nil):
241 | return true
242 | default:
243 | return false
244 | }
245 | }
246 | }
247 | ```
248 |
249 | 두 개의 인자가 모두 값이 존재할 때만 `Equatable` 을 채택한 `Wrapped` 타입의 `==` 연산자로 비교한 결과를 반환하고, `nil` 을 포함하는 경우는 따로 `case` 로 나눠 처리하는 것을 확인할 수 있습니다.
250 |
251 | ```swift
252 | extension Optional: Hashable where Wrapped: Hashable {
253 | public func hash(into hasher: inout Hasher) {
254 | switch self {
255 | case .none:
256 | hasher.combine(0 as UInt8)
257 | case .some(let wrapped):
258 | hasher.combine(1 as UInt8)
259 | hasher.combine(wrapped)
260 | }
261 | }
262 | }
263 | ```
264 |
265 | 값의 유무를 `0`, `1` 로 구분하여 `nil` 일땐 0 을 해시하여 모든 `nil` 이 같은 해시값을 가지도록 하고, 값이 있을 땐 기존 해시값에 `1` 을 해시한 값을 추가하여 옵셔널로 선언된 인스턴스와 옵셔널이 아닌 인스턴스의 해시값에 차이를 두고 있습니다.
266 |
267 | ```swift
268 | let stringNil: String? = nil
269 | let intNil: Int? = nil
270 |
271 | print(stringNil.hashValue == intNil.hashValue) // true
272 |
273 | let number: Int = 3
274 | let optionalNumber: Int? = 3
275 |
276 | print(number.hashValue == optionalNumber.hashValue) // false
277 | ```
278 |
279 | `String?` 으로 선언된 `nil` 과 `Int?` 로 선언된 `nil` 의 해시값이 같고, `Int` 로 선언된 `3` 과 `Int?` 로 선언된 `3` 의 해시값이 다른 것을 확인할 수 있습니다.
280 |
281 | * 비교할 타입이 `Equatable` 을 채택하지 않더라도 `nil` 과의 비교는 가능합니다.
282 |
283 | ```swift
284 | public struct _OptionalNilComparisonType: ExpressibleByNilLiteral {
285 | public init(nilLiteral: ()) {
286 | }
287 | }
288 | ```
289 |
290 | `nil` 과의 비교 연산자 코드를 작성하기 위해 `nil` 을 나타내줄 `_OptionalNilComparisonType` 구조체가 선언되어있습니다. `ExpressibleByNilLiteral` 를 채택하고 생성자만 가지고 있습니다.
291 |
292 | ```swift
293 | public static func ==(lhs: Wrapped?, rhs: _OptionalNilComparisonType) -> Bool {
294 | switch lhs {
295 | case .some:
296 | return false
297 | case .none:
298 | return true
299 | }
300 | }
301 | ```
302 |
303 | 연산자의 좌측에 있는 인자가 `nil` 인지 아닌지에 따라 결과를 결정합니다. `==` 외에도 `!=`, `~=` 이 구현되어있습니다.
304 |
305 | ```swift
306 | struct A { }
307 |
308 | var a: A? = A()
309 | print(a == nil) // false
310 | a = nil
311 | print(a == nil) // true
312 | ```
313 |
314 | `A` 구조체가 `Equatable` 을 채택하지 않았음에도 nil 과의 비교에서는 `==` 연산자를 사용할 수 있는 것을 확인할 수 있습니다.
315 |
316 | ### 참고할 만한 비슷한 질문, 자료
317 |
318 | * [Swift! Optionals?](https://medium.com/ios-os-x-development/swift-optionals-78dafaa53f3)
319 | * [Map and flatMap difference in optional unwrapping in Swift 1.2](https://stackoverflow.com/questions/29556656/map-and-flatmap-difference-in-optional-unwrapping-in-swift-1-2)
320 |
--------------------------------------------------------------------------------
/contents/week-2.md:
--------------------------------------------------------------------------------
1 | # iOS 질문모음 - 2
2 |
3 |
4 |
5 | ### Q.
6 |
7 | > 움직이는 LaunchScreen 을 구현하고 싶어요.
8 |
9 | LaunchScreen 에 이미지 슬라이드 애니메이션을 적용하고 싶은데 방법이 있을까요?
10 |
11 | [질문 바로가기](https://yagom.net/forums/topic/object-c에서-런치스크린-질문입니다/)
12 |
13 | ### A.
14 |
15 | * 우선 LaunchScreen 은 동적인 화면이 아니라 정적인 화면 구성만 가능합니다. [Human Interface Guidelines: Launch Screen](https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/launch-screen) 에 따르면 Launch Screen 은 앱의 첫 화면과 비슷한 형태여야 합니다. 그리고 앱이 실행되는 중에 잠깐만 보이는 화면이기에 애니메이션 등을 구현하기는 부적합합니다. 예외적으로 게임 앱의 경우, 게임 엔진을 시작하는 데 많은 시간이 소요되므로 로딩 화면을 표시할 수 있습니다.
16 | * 시작 화면에 애니메이션 등이 보이는 것은 꼼수가 필요합니다. 시작화면이 보여진 후에 앱의 메인화면이 나오게 되는데, 그 화면이 시작화면과 동일한 상태로 시작하게 만들고, 거기서 애니메이션을 실행하여 다음 화면으로 이동하는 등의 처리를 합니다.
17 | * Launch Screen 을 xib 로 만들고, 앱이 시작되고 정적인 Launch Screen 보여준 후 다시 불러와 애니메이션을 적용하고 불러온 뷰를 제거하는 방식도 있습니다. [Animate Your iOS Splash Screen](https://www.viget.com/articles/animated-ios-launch-screen/) 을 참고해보세요. 애니메이션 구현 때문에 글이 긴데 애니메이션 구현 부분을 제외하고 LaunchScreenManager 부분만 봐도 좋을듯합니다.
18 | * 옆으로 슬라이드 되는 효과를 알고 싶다면 [UIScrollView](https://developer.apple.com/documentation/uikit/uiscrollview) 에 대해 공부하면 됩니다. [UIView](https://developer.apple.com/documentation/uikit/uiview) 의 애니메이션 관련 메서드와 UIScrollView 의 [setContentOffset(_:animated:)](https://developer.apple.com/documentation/uikit/uiscrollview/1619400-setcontentoffset) 정도만 공부해도 큰 힌트가 될 것 같습니다. [automatic UIScrollView with paging](https://stackoverflow.com/questions/17168741/automatic-uiscrollview-with-paging) 도 확인해보세요.
19 |
20 | ### 참고할 만한 비슷한 질문들
21 |
22 | * [iOS Launch screen code not running](https://stackoverflow.com/questions/27398723/ios-launch-screen-code-not-running)
23 | * [how to add animation to launch screen in iOS 9.3 using Objective c](https://stackoverflow.com/questions/37112950/how-to-add-animation-to-launch-screen-in-ios-9-3-using-objective-c)
24 | * [Difference between launch image and splash screen](https://stackoverflow.com/questions/12140464/difference-between-launxch-image-and-splash-screen)
25 | * [Animated Splash Screen](https://developer.apple.com/forums/thread/110295)
26 | * [Splash screen in iOS games](https://stackoverflow.com/questions/29047522/splash-screen-in-ios-games)
27 |
28 | ----
29 |
30 | ### Q.
31 |
32 | > override 할 때 super 의 메소드를 꼭 호출해야 하나요?
33 |
34 | viewController 에서
35 |
36 | ```swift
37 | override func viewDidLoad() {
38 | super.viewDidLoad()
39 | }
40 | ```
41 |
42 | super.viewDidLoad() 를 빼도 에러가 안 나던데 꼭 호출해야 하나요?
43 |
44 | [질문 바로가기](https://yagom.net/forums/topic/override-할-때-super-꼭-호출해야-하나요/)
45 |
46 | ### A.
47 |
48 | * super 는 부모 클래스를 의미하는데요. 질문 내용은 오버라이딩 할 때 부모의 작업을 실행할지 말지 선택하는 것이라 할 수 있겠죠. 보통 [viewDidLoad()](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621495-viewdidload) 같이 뷰 라이프 사이클은 [템플릿 패턴](https://en.wikipedia.org/wiki/Template_method_pattern)으로 구현되어 있는데 뷰 컨트롤러의 뷰가 메모리에 로드될 때, OS 에 의해 호출되는 것이기 때문에 super 의 메소드를 호출하는 게 좋습니다.
49 | * super 의 메소드를 호출하는 것이 선택인 경우도 있고 필수인 경우도 있습니다. 꼭 필요한지 아닌지는 문서에 보면 대부분 나와 있습니다. [AppKit viewDidLoad() 공식문서](https://developer.apple.com/documentation/appkit/nsviewcontroller/1434476-viewdidload) 처럼 discussion 부분에 설명이 있을 겁니다.
50 | * override 할 때 super 를 호출하지 않아야 하는 경우도 있습니다. 대표적인 예시로 [loadView()](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621454-loadview) 가 있습니다.
51 | * super 메소드를 호출하는 것을 자꾸 깜빡하신다면 코딩 스타일을 교정해주는 [SwiftLint](https://realm.github.io/SwiftLint/overridden_super_call.html) 라이브러리를 이용해보셔도 좋습니다.
52 | * 상속과 오버라이딩에 대해 더 알고 싶다면 [Swift: Inheritance](https://docs.swift.org/swift-book/LanguageGuide/Inheritance.html) 도 읽어보면 좋을것 같아요!
53 |
54 | ### 참고할 만한 비슷한 질문들
55 |
56 | * [When to use super when overriding ios methods](https://stackoverflow.com/questions/38689059/when-to-use-super-when-overriding-ios-methods)
57 | ----
58 |
59 | ### Q.
60 |
61 | > Designated initializer 와 Convenience initializer 의 차이가 무엇인가요?
62 |
63 | Apple 문서에 보면 Convenience init 은 '같은 클래스에서 다른 이니셜라이저를 호출해야 한다' 라고 하는데 잘 이해가 안 됩니다. Convenience init 은 어떤 상황에 사용되고 Designated init 과의 차이는 무엇인가요?
64 |
65 | [질문 바로가기](https://yagom.net/forums/topic/swift-초기화-이니셜라이져/)
66 |
67 | ### A.
68 |
69 | * Designated init 은 클래스의 모든 프로퍼티가 초기화될 수 있도록 해줘야 하고, Convenience init 은 쉽게 생각하면 보조 이니셜라이저라고 할 수 있는데, Designated init 의 파라미터 일부를 기본값으로 설정해서 Convenience init 안에서 Designated init 을 호출해서 쓸 수 있는 거예요.
70 |
71 | ```swift
72 | class Beverage {
73 | var name: String
74 | var capacity: Int
75 | // 지정 이니셜라이저 - 모든 인스턴스의 저장 프로퍼티 값 초기화(할당)
76 | init(name: String, capacity: Int) {
77 | self.name = name
78 | self.capacity = capacity
79 | }
80 | // 편의 이니셜라이저 - 지정 이니셜라이저를 통해 인스턴스 초기화
81 | convenience init(name: String) {
82 | self.init(name: name, capacity: 330)
83 | }
84 | // 편의 이니셜라이저 - 다른 편의 이니셜라이저를 통해 인스턴스 초기화
85 | convenience init() {
86 | self.init(name: "Fanta")
87 | }
88 | }
89 | ```
90 |
91 | Convenience init 에는 속성 중 인스턴스 생성 시점에 지정해줘야 할 것만 파라미터로 넣어놓고, 그 안에서 기본값이 들어간 다른 이니셜라이저를 호출한다고 생각하시면 됩니다! 예시코드처럼 Convenience init 에는 Designated init 뿐만 아니라 다른 Convenience init 을 호출해도 됩니다.
92 |
93 | 
94 |
95 | 하지만 위 그림처럼 같은 클래스 내부에서 init 체인이 연결되는 끝 지점은 항상 Designated init 이여야 합니다.
96 |
97 | * 더 자세한 내용은 [Swift: Initialization](https://docs.swift.org/swift-book/LanguageGuide/Initialization.html) 을 읽어보세요!
98 |
99 | ### 참고할 만한 비슷한 질문들
100 |
101 | * [What the difference between designated and convenience init in this code below](https://stackoverflow.com/questions/29563147/what-the-difference-between-designated-and-convenience-init-in-this-code-below)
102 | * [What is the difference between convenience init vs init in swift, explicit examples better](https://stackoverflow.com/questions/40093484/what-is-the-difference-between-convenience-init-vs-init-in-swift-explicit-examp)
103 |
104 | ----
105 |
106 | ### Q.
107 |
108 | > loadView() 와 viewDidLoad() 의 차이가 무엇인가요?
109 |
110 | loadView() 와 viewDidLoad() 의 차이점은 무엇이고 각각 어떤 경우에 사용하나요?
111 |
112 | 그리고 코드만으로 UI 를 작성할 때는 view 를 그리는 코드를 loadView 에 작성해야 하나요?
113 |
114 | [질문 바로가기](https://yagom.net/forums/topic/loadview와-viewdidload-차이에-대한-질문입니다/)
115 |
116 | ### A.
117 |
118 | * 좋은 글이 있어 공유 드립니다. 뷰가 그려질 때와 뷰가 로드될 때를 나눠서 생각해보시면 좋을 것 같습니다. [[iOS] View 가 Load 될 경우](https://mrgamza.tistory.com/279)
119 | * [loadView()](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621454-loadview) 메소드는 뷰 컨트롤러가 자신의 뷰, 그러니까 흔히 self.view 처럼 접근하는 그 뷰 컨트롤러의 메인 뷰를 로드할 때 호출되는 메소드입니다. 즉, 그 메인 뷰를 생성하려고 호출하는 메소드죠. 그래서 이 메소드 안에서 새로운 뷰를 만들어서 뷰 컨트롤러의 메인 뷰로 설정해줘도 됩니다. 뷰 컨트롤러의 기본 뷰를 커스텀 뷰로 사용하고자 할 때 유용합니다. 스토리보드를 쓰면 어차피 스토리보드에 있는 뷰를 가져와 쓸 테니 굳이 필요하지 않다고 볼 수 있겠네요.
120 | * [viewDidLoad()](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621495-viewdidload) 는 이 뷰가 모두 생성되고 메모리에 생성된 후에 호출되는 메소드입니다. 즉, `뷰 컨트롤러의 메인 뷰가 생성되었으니 이제 이 위에 하고 싶은 걸 해라` 라는 뜻으로 보면 되겠습니다. 뷰가 생성된 이후에 해야 할 동작을 viewDidLoad() 내부에 작성합니다.
121 | * [viewDidLoad() vs. loadView()(Swift3)](https://medium.com/yay-its-erica/viewdidload-vs-loadview-swift3-47f4ad195602), [viewDidLoad() Vs. loadView()](https://medium.com/swlh/viewdidload-vs-loadview-ddec6ac9bdd7) 이 글들도 참고해보면 좋고, 애플의 공식문서를 읽어봐도 도움이 될 것 같아요.
122 |
123 | ### 참고할 만한 비슷한 질문들
124 |
125 | * [iPhone SDK: what is the difference between loadView and viewDidLoad?](https://stackoverflow.com/questions/573958/iphone-sdk-what-is-the-difference-between-loadview-and-viewdidload)
126 | * [what is the difference between loadView and viewDidLoad?](https://stackoverflow.com/questions/3423785/what-is-the-difference-between-loadview-and-viewdidload)
127 |
128 | ----
129 |
130 | ### Q.
131 |
132 | > iOS 13 미만에서도 애플 로그인을 구현해야 하나요? 구현해야 한다면 어떻게 구현하나요?
133 |
134 | [App Store Review Guidelines](https://developer.apple.com/app-store/review/guidelines/#sign-in-with-apple) 의 **Sign in with Apple** 에서는 다른 소셜 로그인 서비스를 사용하는 앱은 애플 로그인도 제공해야 한다고 합니다. 하지만 애플이 제공하는 [AuthenticationServices](https://developer.apple.com/documentation/authenticationservices) 프레임워크의 애플 로그인 기능은 iOS 13 이상만 지원합니다. iOS 13 미만에서도 애플 로그인을 구현해야 하나요?
135 |
136 | [질문 바로가기](https://developer.apple.com/forums/thread/122755?answerId=417003022#417003022)
137 |
138 | ### A.
139 |
140 | * iOS 가 아닌 다른 플랫폼이나 iOS 12 미만의 기기에서도 웹을 통해 애플 로그인을 구현할 수 있습니다. [available](https://docs.swift.org/swift-book/ReferenceManual/Attributes.html) 속성을 활용해 iOS 버전으로 분기처리를 하여 13 이상의 기기에서 실행되었을 때는 AuthenticationServices 으로 로그인을 하고, 13 미만의 기기에서는 [WKWebView](https://developer.apple.com/documentation/webkit/wkwebview) 로 애플 로그인이 구현된 웹페이지를 띄워줍니다. 자세한 구현은 [Sign in with Apple JS](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js) 와 [Incorporating Sign in with Apple into Other Platforms](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js/incorporating_sign_in_with_apple_into_other_platforms) 를 참고하세요.
141 |
142 | ### 참고할 만한 비슷한 질문들
143 |
144 | * [How can I implement Sign-In-With-Apple in a webview?](https://stackoverflow.com/questions/60461480/how-can-i-implement-sign-in-with-apple-in-a-webview)
145 | * [Will "Sign in With Apple" allow apps to be backward compatible with iOS 12 and lower?](https://stackoverflow.com/questions/57928420/will-sign-in-with-apple-allow-apps-to-be-backward-compatible-with-ios-12-and-l)
146 | * [Sign In With Apple and Older Devices](https://developer.apple.com/forums/thread/649267)
147 |
148 | ----
149 |
150 | ### Q.
151 |
152 | > 기기에서 애플 로그인을 할 때 사용자 정보(이름, 이메일)가 안 받아져요.
153 |
154 | 애플 로그인을 [ASAuthorizationAppleIDRequest](https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidrequest?language=objc) 의 [requestedScopes](https://developer.apple.com/documentation/authenticationservices/asauthorizationopenidrequest/3153062-requestedscopes) 에 [fullName](https://developer.apple.com/documentation/authenticationservices/asauthorization/scope/3153028-fullname) 과 [email](https://developer.apple.com/documentation/authenticationservices/asauthorization/scope/3153027-email) 을 설정해서 이름과 이메일을 응답으로 받아오도록 구현하였습니다. 시뮬레이터에서 테스트할 때는 애플 로그인을 할 때마다 사용자 정보를 받아오지만, 실제 기기에서 테스트하면 첫 요청에서는 사용자 정보를 잘 받아오는데 그 후부턴 [user identifier](https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidcredential/3153037-user) 만 받아오고 fullName 과 email 에는 nil 이 반환됩니다. 어떻게 해결해야 할까요?
155 |
156 | [질문 바로가기](https://developer.apple.com/forums/thread/121496)
157 |
158 | ### A.
159 |
160 | * 사용자가 해당 앱에 처음 로그인할 때만 사용자 정보를 [ASAuthorizationAppleIDCredential](https://developer.apple.com/documentation/authenticationservices/asauthorizationappleidcredential) 에 담아 보냅니다. 이후 동일한 계정으로 앱에 로그인하면 사용자 정보가 공유되지 않고 사용자 식별자만 ASAuthorizationAppleIDCredential 으로 반환합니다. 서버에 계정이 성공적으로 생성 되었는지 확인할 수 있을 때까지 처음에 받은 사용자 정보를 캐시 해두는 것을 권장합니다. [Authenticationg Users with Sign in with Apple](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/authenticating_users_with_sign_in_with_apple) 의 **Send Information to App Servers and Verify Tokens** 부분을 참고하세요.
161 | * 실제 기기에서 사용자 정보를 받아오는 동작을 여러 번 테스트해야 하는 상황이라면 앱의 Bundle Identifier 를 변경하거나, [Apple ID 계정 관리](https://appleid.apple.com/#!&page=signin) 의 **Apple ID를 사용하는 앱 및 웹 사이트** 에서 앱을 제거하여 첫 번째 로그인처럼 만들어주는 방법도 있습니다.
162 |
163 |
--------------------------------------------------------------------------------
/contents/week-3.md:
--------------------------------------------------------------------------------
1 | # iOS 질문모음 - 3
2 |
3 | ### Q.
4 |
5 | > RxSwift 에서 DisposeBag 을 쓰는 이유가 무엇인가요?
6 |
7 | 여러 예제코드에서 Observable 을 subscribe 한 후 항상 disposed(by: disposebag) 를 사용하던데 dispose 를 사용하는 이유가 무엇인가요? 메모리 누수를 줄이기 위해서인가요? 만약 dispose 를 해주지 않으면 어떻게 되나요?
8 |
9 | [질문 바로가기](https://yagom.net/forums/topic/rxswift-disposebag을-써야하는-이유/)
10 |
11 | ### A.
12 |
13 | * RxSwift 에서 [Observable](https://github.com/ReactiveX/RxSwift/blob/master/RxSwift/Observable.swift) 을 subscribe 하면 항상 [Disposable](https://github.com/ReactiveX/RxSwift/blob/master/RxSwift/Disposable.swift) 을 반환합니다. 이 Disposable 들을 dispose 해주지 않으면 메모리에 계속 남아 메모리 누수가 발생합니다. 그렇다면 일일이 Dispoable 프로토콜에 구현되어있는 dispose() 를 호출하여 없애주어야 하는데 [RxSwift 가이드 문서의 Disposing](https://github.com/ReactiveX/RxSwift/blob/master/Documentation/GettingStarted.md#disposing) 에서는 dispose() 를 직접 호출하지 말고 DisposeBag 이나, takeUntil() 을 통해 dispose 하기를 권장합니다.
14 |
15 | * [DisposeBag](https://github.com/ReactiveX/RxSwift/blob/master/RxSwift/Disposables/DisposeBag.swift) 의 내부구현을 살펴보면
16 |
17 | ```swift
18 | private func dispose() {
19 | let oldDisposables = self._dispose()
20 |
21 | for disposable in oldDisposables {
22 | disposable.dispose()
23 | }
24 | }
25 |
26 | deinit {
27 | self.dispose()
28 | }
29 | ```
30 |
31 | 이렇게 DisposeBag 에 담긴 모든 Disposable 들을 dispose 하는 메소드가 구현되어있고 이를 deinit 에서 호출합니다. 따라서 DisposeBag 을 가진 인스턴스가 메모리에서 해제될 때 DisposeBag 도 같이 해제되면서 내부의 Disposable 들을 dispose 해줍니다.
32 |
33 | 추가로 특정 시점에 DisposeBag 의 Disposable 들을 초기화해야 할 경우 DisposeBag 의 dispose 메소드가 private 으로 선언되어 있어서
34 |
35 | ```swift
36 | self.disposeBag = DisposeBag()
37 | ```
38 |
39 | 이런 방식으로 초기화할 수 있습니다.
40 |
41 | 같은 동작을 하면서 명시적으로 dispose 를 호출해주고 싶다면 [CompositeDisposable](https://github.com/ReactiveX/RxSwift/blob/master/RxSwift/Disposables/CompositeDisposable.swift) 을 사용해보세요. dispose 뿐만 아니라 키값을 통해 특정 disposable 만 제거하는 등 다양한 기능이 있습니다.
42 |
43 | * takeUntil() 연산자를 통해 특정 인스턴스가 deallocated 될 때까지만 살려두는 방법도 있습니다.
44 |
45 | ```swift
46 | sequence
47 | .takeUntil(self.rx.deallocated)
48 | .subscribe {
49 | print($0)
50 | }
51 | ```
52 |
53 | * Dispose 를 하지 않아서 발생하는 문제도 있지만, subscribe 할 때 escaping 클로저에서의 강한 참조 때문에 발생하는 메모리 문제도 주의해야 합니다. RxSwift 에서의 메모리 누수 처리에 대해 더 자세히 알고 싶다면 [Disposing RxSwift's Memory Leaks](https://medium.com/gett-engineering/disposing-rxswifts-memory-leaks-6ceb73162170) 를 읽어보세요.
54 |
55 | ### 참고할 만한 비슷한 질문들
56 |
57 | * [When should we call addDisposableTo(disposeBag) in RxSwift?](https://stackoverflow.com/questions/37724766/when-should-we-call-adddisposabletodisposebag-in-rxswift)
58 | * [RxSwift DisposeBag usage in ViewController](https://stackoverflow.com/questions/55028020/rxswift-disposebag-usage-in-viewcontroller)
59 | * [How do I unsubscribe from an observable?](https://stackoverflow.com/questions/39458195/how-do-i-unsubscribe-from-an-observable)
60 | * [DisposeBag memory leak?](https://stackoverflow.com/questions/44296776/disposebag-memory-leak)
61 |
62 | -----
63 |
64 | ### Q.
65 |
66 | > leading, trailing 과 left, right 의 차이가 무엇인가요?
67 |
68 | 오토레이아웃을 공부하면서 left, right 보다는 leading, trailing 을 사용하는 것을 권장한다는 이야기를 많이 들었는데요.
69 |
70 | leading, trailing 과 left, right 는 어떤 차이가 있나요?
71 |
72 | [질문 바로가기](https://yagom.net/forums/topic/leading-trailing과-left-right/)
73 |
74 | ### A.
75 |
76 | * leading, trailing 은 글의 시작방향과 끝방향을 뜻하는데요. 해당 기기의 언어 설정에 따라 유동적으로 변화합니다. 영어, 한국어 등의 LTR(left-to-right) 언어를 사용하는 기기에서는 leading 이 왼쪽, trailing 이 오른쪽이 되고 반대로 아랍어, 히브리어 등의 RTL(right-to-left) 언어를 사용하는 기기에서는 leading 이 오른쪽, trailing 이 왼쪽이 됩니다.
77 | * left, right 는 항상 화면에서의 왼쪽, 오른쪽을 가리킵니다.
78 | * [Auto Layout Guide: Rules of Thumb](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithConstraintsinInterfaceBuidler.html#//apple_ref/doc/uid/TP40010853-CH10-SW7) 에도 `Always use leading and trailing constraints instead of right and left.` 라는 문구가 있으니 UI 를 구성할 때 절대적으로 오른쪽, 왼쪽에 위치해야 하는 것이 아니라면 leading, trailing 을 사용하는 것을 권장합니다.
79 |
80 | ### 참고할 만한 비슷한 질문들
81 |
82 | * [Difference between leftAnchor and leadingAnchor?](https://stackoverflow.com/questions/32981532/difference-between-leftanchor-and-leadinganchor/32981750)
83 | * [Difference between NSLayoutAttributeLeft vs NSLayoutAttributeLeading](https://stackoverflow.com/questions/19971508/difference-between-nslayoutattributeleft-vs-nslayoutattributeleading)
84 |
85 | -----
86 |
87 | ### Q.
88 |
89 | > 왜 IBOutlet 을 weak var 로 선언하나요?
90 |
91 | 스토리보드를 공부하던 도중 IBOutlet 을 weak var 로 선언하는 것에 의문이 생겼습니다.
92 |
93 | 스토리보드에서 UI 를 생성했을 때, 스토리보드 내에서 해당 UI 의 인스턴스가 생성되는 건가요?
94 |
95 | IBOutlet 을 상수가 아닌 변수로 선언하는 이유가 무엇인가요?
96 |
97 | IBOutlet 을 strong 으로 선언해도 오류가 발생하지 않는데 필요에 따라 weak, strong 을 구분해서 써야한다고 합니다. 어떤 경우에 IBOutlet 을 strong 으로 선언하나요?
98 |
99 | 참고한 글
100 |
101 | * [The difference between Weak & Strong in Swift](https://medium.com/@janakmshah/the-difference-between-weak-strong-in-swift-2c953cedd7c0)
102 | * [Should Outlets Be Weak or Strong](https://cocoacasts.com/should-outlets-be-weak-or-strong)
103 |
104 | [질문 바로가기](https://yagom.net/forums/topic/스토리보드와-인터페이스-빌더를-이용한-뷰-생성시/)
105 |
106 | ### A.
107 |
108 | * 스토리보드에서 UI 를 생성하더라도 인스턴스가 스토리보드에서 생성되는 것은 아닙니다. 앱이 실행되어 뷰를 스토리보드에서 불러오는 순간에 스토리보드에 구성해 놓은 모양대로 뷰의 인스턴스가 생성되어 동작하게 됩니다. 즉, 스토리보드에는 어떤 뷰 클래스의 뷰가 어디에 위치할지 등의 정보만 담아두고, 실제로 뷰 클래스의 인스턴스가 생성되는 시점은 스토리보드로부터 뷰 구성 정보를 불러와서 화면에 보여주려 할 때(앱 동작 중) 입니다. 그래서 런타임 오류 발생 여지가 있어도 스토리보드에서는 오류가 발생하지 않고, 실행해서 화면이 보여지려고 하는 순간 오류로 앱이 죽죠. 스토리보드는 앱 동작 전에 이미 만들어져있으므로 스토리보드에 인스턴스가 생성된다고 볼 수는 없습니다. 그저 우리 눈에 그렇게 보이는 것뿐입니다.
109 |
110 | * IBOutlet 은 변수로만 선언 가능합니다. 상수는 불가능합니다. 그 이유는 위의 설명과 연관이 있는데요, 이미 IBOutlet 프로퍼티가 메모리에 생성된 후에 동적으로 스토리보드 정보로부터 만들어진 인스턴스를 IBOutlet 프로퍼티에 할당해야 하기 때문입니다. 그런데 해당 프로퍼티가 상수면 변경(할당)이 불가능하므로 항상 변수여야 합니다.
111 |
112 | * 통상 처음 배우는 분들은 뷰 컨트롤러의 뷰 위에 얹어지는 자식 뷰(subview)를 IBOutlet 프로퍼티로 지정하므로 그 기준으로 설명하겠습니다. 뷰 컨트롤러의 뷰 위에 얹어지는 자식 뷰를 IBOutlet 프로퍼티에 strong 으로 할당하면 자식 뷰의 retain count 가 1 증가합니다. IBOutlet 프로퍼티에 할당된 자식 뷰가 또 다른 뷰의 자식 뷰로 얹어지면 retain count 가 1 추가로 증가합니다. 그래서 뷰 컨트롤러의 뷰 위에 얹어진 자식 뷰의 retain count 는 IBOutlet 이 strong 으로 할당된 경우 기본적으로 2의 retain count 를 가집니다.
113 |
114 | * 위의 설명처럼 strong 으로 선언해도 오류는 발생하지 않습니다. 다만 자식 뷰가 부모 뷰(superview)에서 떨어져 나와도 retain count 가 1이 남아있으므로 뷰 컨트롤러가 메모리에서 해제되기 전까지 IBOutlet 변수에 할당된 뷰는 메모리에서 해제되지 않습니다(물론 부모 뷰에서 떨어져 나온 후 IBOutlet 변수에 nil 을 할당하면 메모리에서 해제됩니다. 이해가 안되면 ARC 에 대해 조금 더 공부하면 좋습니다.). 이를 의도하고 strong 으로 선언한다면 괜찮습니다. 경우에 따라서 해당 뷰를 부모 뷰에 붙였다 뗐다 해야하는 경우엔 유용합니다. 하지만 이것도 뷰를 매번 붙였다([addSubview(_:)](https://developer.apple.com/documentation/uikit/uiview/1622616-addsubview)) 뗐다([removeFromSuperview()](https://developer.apple.com/documentation/uikit/uiview/1622421-removefromsuperview))하기 보다는 뷰를 잠깐 안 보이게 [isHidden](https://developer.apple.com/documentation/uikit/uiview/1622585-ishidden) 으로 숨겨주는 방법이 있습니다. 이때는 부모 뷰에서 실질적으로 떨어져 나온 것이 아니므로 retain count 의 변화는 없습니다. 그래서 통상 의도적인 목적이 없는 경우에는 IBOutlet 변수를 weak 로 선언하여 IBOutlet 인스턴스의 retain count 를 1로 만들어줍니다. 부모 뷰에서 떨어져 나오면 retain count 가 0이 되므로 바로 메모리에서 해제되죠.
115 |
116 | * 아래 영상을 보면 뷰를 스토리보드에 추가해주고 MyView 클래스를 뷰의 커스텀클래스로 지정해준 후 IBOutlet 을 strong 으로 선언했을 때, IBOutlet 변수에 할당된 뷰를 부모 뷰에서 제거해도 deinit 이 호출되지 않아요. 반대로 weak 로 선언한 후에 실행하면 뷰가 부모 뷰에서 제거된 이후에 deinit 이 호출되는 것을 확인할 수 있을 겁니다.
117 |
118 | ```swift
119 | class MyView: UIView {
120 | deinit {
121 | print("뷰가 메모리에서 해제됨")
122 | }
123 | }
124 | ```
125 |
126 | 
127 |
128 | ### 참고할 만한 비슷한 질문들
129 |
130 | * [Why can't an @IBOutlet be assigned let instead of var in Swift](https://stackoverflow.com/questions/46893514/why-cant-an-iboutlet-be-assigned-let-instead-of-var-in-swift)
131 | * [Why does Xcode create a weak reference for an IBOutlet?](https://stackoverflow.com/questions/21654113/why-does-xcode-create-a-weak-reference-for-an-iboutlet)
132 | * [What is the point of using weak variables in IBOutlets?](https://www.quora.com/What-is-the-point-of-using-weak-variables-in-IBOutlets)
133 |
134 | -----
135 |
136 | ### Q.
137 |
138 | > 메소드나 변수를 선언할 때 static 과 class 의 차이가 무엇인가요?
139 |
140 | static 으로 선언된 메소드와 class 로 선언된 메소드의 차이가 무엇인가요?
141 |
142 | 그리고 class 변수를 `class var = "classVar"` 이렇게 선언하니 에러가 나던데 class 변수를 사용할 수 있나요?
143 |
144 | [질문 바로가기](https://stackoverflow.com/questions/29636633/static-vs-class-functions-variables-in-swift-classes)
145 |
146 | ### A.
147 |
148 | * static 과 class 둘 다 인스턴스가 아닌 타입 자체에서 호출합니다. 이 둘의 가장 큰 차이점은 서브클래스에서 class 로 선언된 프로퍼티나 메소드는 오버라이딩이 가능하고 static 은 오버라이딩이 불가능하다는 것입니다.
149 |
150 | ```swift
151 | class SuperClass {
152 | class func classFunc() {
153 | print("class function")
154 | }
155 |
156 | static func staticFunc() {
157 | print("static function")
158 | }
159 | }
160 |
161 | class SubClass: SuperClass {
162 | override class func classFunc() {
163 | print("override class function")
164 | }
165 |
166 | // Cannot override static method 에러 발생
167 | override static func staticFunc() {
168 | print("override static function")
169 | }
170 | }
171 | ```
172 |
173 | 위 예시코드를 보면 static 으로 선언한 메소드를 오버라이드 하려 하면 static 은 오버라이드 할 수 없다는 컴파일 에러가 발생합니다.
174 |
175 | ```swift
176 | class SuperClass {
177 | // Class stored properties not supported in classes; did you mean 'static'? 에러 발생
178 | class var classVar: String = "class variable"
179 |
180 | class var computedClassVar: String {
181 | return "class variable"
182 | }
183 |
184 | static var staticVar: String = "static variable"
185 | }
186 |
187 | class SubClass: SuperClass {
188 | override class var computedClassVar: String {
189 | return "override class variable"
190 | }
191 |
192 | // Cannot override with a stored property 'staticVar' 에러 발생
193 | override static var staticVar: String = "override static variable"
194 | }
195 | ```
196 |
197 | 그리고 class 프로퍼티는 연산 프로퍼티로만 선언할 수 있습니다. 저장 프로퍼티를 선언하고 싶다면 static 을 사용해야 합니다. 프로퍼티 역시 메소드와 같이 class 로 선언한 프로퍼티만 오버라이딩이 가능합니다.
198 |
199 | ```swift
200 | struct CustomStruct {
201 | // Class properties are only allowed within classes; use 'static' to declare a static property 에러 발생
202 | class var classVar: String {
203 | return "class variable"
204 | }
205 | // Class methods are only allowed within classes; use 'static' to declare a static method 에러 발생
206 | class func classFunc() {
207 | print("class function")
208 | }
209 | }
210 | ```
211 |
212 | 추가로 구조체에서는 상속과 오버라이딩이 불가능하므로 static 만 사용가능합니다.
213 |
214 | * 정리하자면 class 에서 오버라이딩이 필요한 정적 프로퍼티와 메소드를 선언해야 할 때만 class 를 쓰고 그게 아니라면 static 으로 선언하면 됩니다. 좀 더 자세한 내용은 [Swift: Methods - Type Methods](https://docs.swift.org/swift-book/LanguageGuide/Methods.html#ID241) 를 읽어보세요!
215 |
216 | ### 참고할 만한 비슷한 질문들
217 |
218 | * [static vs class as class variable/method (Swift)](https://stackoverflow.com/questions/29206465/static-vs-class-as-class-variable-method-swift)
219 | * [What is the difference between static func and class func in Swift](https://stackoverflow.com/questions/25156377/what-is-the-difference-between-static-func-and-class-func-in-swift)
220 | * [Class variables not yet supported](https://stackoverflow.com/questions/24015207/class-variables-not-yet-supported)
221 |
222 | ----
223 |
224 | ### Q.
225 |
226 | > 클로저 앞의 @escaping 은 무엇인가요?
227 |
228 | 함수에서 인자로 클로저를 받을 때 클로저 앞에 @escaping 이 있는 경우가 많던데 어떤 경우에 @escaping 을 사용하나요?
229 |
230 | [질문 바로가기](https://stackoverflow.com/questions/39504180/escaping-closures-in-swift)
231 |
232 | ### A.
233 |
234 | * 스위프트에서 인자로 전달되는 모든 클로저의 기본값은 @noescape 입니다. 따로 인자 앞에 @escaping 을 명시하지 않으면 @noescape 로 인식하게 됩니다. @noescape 클로저는 함수 내부에서만 호출할 수 있으며 함수가 종료되면 메모리에서 해제됩니다. 하지만 함수가 반환된 후에 클로저가 필요한 경우가 있습니다. 주로 클로저를 다른 곳에 넘겨주어 저장해두거나, 비동기 작업의 completion 이 필요할 때 @escaping 을 사용합니다. @escaping 은 함수의 인자로 클로저가 전달되고 그 클로저가 함수가 반환된 후에 호출될 때 사용합니다.
235 |
236 | * ```swift
237 | func noescapeClosure(closure: () -> Void) {
238 | closure()
239 | }
240 |
241 | func noescapeClosureWithAsync(closure: () -> Void) {
242 | // Escaping closure captures non-escaping parameter 'closure' 에러 발생
243 | DispatchQueue.main.async {
244 | closure()
245 | }
246 | }
247 |
248 | func escapingClosure(closure: @escaping () -> Void) {
249 | DispatchQueue.main.async {
250 | closure()
251 | }
252 | }
253 | ```
254 |
255 | noescapeClosure(closure:) 함수는 함수가 종료되기 전에 클로저를 호출하므로 @escaping 을 선언할 필요가 없습니다. 하지만 noescapeClosureWithAsync(closure:) 함수는 비동기 방식으로 클로저를 호출하기 때문에 함수가 종료된 후 클로저가 호출될 가능성이 있으므로(반드시 함수가 종료된 후에 호출되어야 하는 것은 아닙니다.) @escaping 을 선언해주지 않으면 컴파일 에러가 발생합니다.
256 |
257 | * ```swift
258 | var completionHandlers: [() -> Void] = []
259 |
260 | func appendNoescapeClosure(closure: () -> Void) {
261 | // Converting non-escaping parameter 'closure' to generic parameter 'Element' may allow it to escape 에러 발생
262 | completionHandlers.append(closure)
263 | }
264 |
265 | func appendEscapingClosure(closure: @escaping () -> Void) {
266 | completionHandlers.append(closure)
267 | }
268 | ```
269 |
270 | 클로저를 인자로 받아 completionHandlers 배열에 저장하는 함수입니다. 인자로 받은 클로저를 함수 범위 밖의 배열에 저장하기 때문에 이 경우에도 @escaping 을 선언해주지 않으면 컴파일 에러가 발생합니다.
271 |
272 | * 더 자세한 정보는 [Swift: Closures - Escaping Closures](https://docs.swift.org/swift-book/LanguageGuide/Closures.html#ID546) 읽어보세요!
273 |
274 | ### 참고할 만한 비슷한 질문들
275 |
276 | * [Swift @escaping and Completion Handler](https://stackoverflow.com/questions/46245517/swift-escaping-and-completion-handler)
277 | * [In Swift, What is the difference between (() -> ()) and @escaping () -> Void?](https://stackoverflow.com/questions/61492303/in-swift-what-is-the-difference-between-and-escaping-void)
278 | * [@escaping closure actually runs before return](https://stackoverflow.com/questions/49614905/escaping-closure-actually-runs-before-return)
--------------------------------------------------------------------------------
/contents/week-4.md:
--------------------------------------------------------------------------------
1 | # iOS 질문 모음 - 4
2 |
3 | ### Q.
4 |
5 | > class 와 final class 의 차이가 무엇인가요?
6 |
7 | 그냥 선언한 클래스와 final 을 붙여 선언한 클래스의 차이가 무엇인가요?
8 |
9 | [질문 바로가기](https://stackoverflow.com/questions/46049783/what-is-the-difference-between-final-class-and-class)
10 |
11 | ### A.
12 |
13 | * 클래스를 선언할 때 final 을 사용해 클래스의 상속을 막을 수 있습니다. final 로 선언된 클래스는 다른 클래스에서 상속할 수 없습니다.
14 |
15 | ```swift
16 | final class A {
17 | }
18 |
19 | // Inheritance from a final class 'A' 에러 발생
20 | class B: A {
21 | }
22 | ```
23 |
24 | final 로 선언된 클래스를 상속받으려 하면 컴파일 에러가 발생합니다.
25 |
26 | * 클래스뿐만 아니라 메소드, 프로퍼티, 서브스크립트에도 final 을 명시하여 재정의를 막을 수 있습니다.
27 |
28 | ```swift
29 | class A {
30 | final var name: String {
31 | return "A"
32 | }
33 |
34 | final subscript() -> Any? {
35 | get { nil }
36 | set(newValue) { }
37 | }
38 |
39 | final func hello() {
40 | print("hello A")
41 | }
42 | }
43 |
44 | class B: A {
45 | // Property overrides a 'final' property 에러 발생
46 | override var name: String {
47 | return "B"
48 | }
49 |
50 | // Subscript overrides a 'final' subscript 에러 발생
51 | override subscript() -> Any? {
52 | get { "override!" }
53 | set (newValue) { }
54 | }
55 |
56 | // Instance method overrides a 'final' instance method
57 | override func hello() {
58 | print("hello B")
59 | }
60 | }
61 | ```
62 |
63 | 클래스의 상속은 허용하되 부분적으로 재정의를 막고 싶을 때 주로 사용합니다. [Swift: Inheritance - Preventing Overrides](https://docs.swift.org/swift-book/LanguageGuide/Inheritance.html#ID202) 도 읽어보세요!
64 |
65 | * 만약 구현한 클래스를 상속할 일이 없다면 final 로 선언해주는 게 좋습니다. 상속하지 않을 클래스를 final 로 선언해주면 컴파일러에게 선언된 함수를 찾지 않고 직접 호출해야 한다고 알려주어 함수 호출에 의한 오버헤드를 줄여 성능을 향상시킵니다. 자세한 내용은 [Increasing Performance by Reducing Dynamic Dispatch](https://developer.apple.com/swift/blog/?id=27) 를 참고하세요.
66 |
67 | ### 참고할 만한 비슷한 질문들
68 |
69 | * [Swift. Make all classes final?](https://stackoverflow.com/questions/47041883/swift-make-all-classes-final)
70 | * [Does marking a Swift class final also make all contained vars, lets and functions gain Static Dispatch benefits automatically?](https://stackoverflow.com/questions/55308577/does-marking-a-swift-class-final-also-make-all-contained-vars-lets-and-function)
71 |
72 | -----
73 |
74 | ### Q.
75 |
76 | > UI 업데이트는 왜 메인 스레드에서만 해야 하나요?
77 |
78 | 애플은 모든 UI 관련 작업이 메인 스레드에서 수행되어야 한다고 합니다.
79 |
80 | 백그라운드 스레드에서 UI 업데이트 작업을 하면 왜 오류가 발생하나요?
81 |
82 | [질문 바로가기](https://www.quora.com/Why-must-the-UI-always-be-updated-on-Main-Thread)
83 |
84 | ### A.
85 |
86 | * 앱을 실행하면 [Cocoa Touch](https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Cocoa.html) 에서 [UIApplication](https://developer.apple.com/documentation/uikit/uiapplication) 의 인스턴스가 메인 스레드에서 설정됩니다.
87 |
88 | 
89 |
90 | 앱의 UI event 는 일반적으로 UIApplication -> [UIWindow](https://developer.apple.com/documentation/uikit/uiwindow) -> [UIViewController](https://developer.apple.com/documentation/uikit/uiviewcontroller) -> [UIView](https://developer.apple.com/documentation/uikit/uiview) -> subviews([UILabel](https://developer.apple.com/documentation/uikit/uilabel), [UIButton](https://developer.apple.com/documentation/uikit/uibutton) 등) 와 같이 chain 으로 연결되는데, 이 [responder chain](https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/using_responders_and_the_responder_chain_to_handle_events) 을 따라 UIApplication 으로 전달됩니다. 이러한 event chain 이 메인 스레드에서 동작하므로 responder chain 에 포함된 모든 UI 관련 동작들은 메인 스레드에서 수행되어야 합니다.
91 |
92 | 간단하게 요약하자면 UI 와 관련된 모든 이벤트 처리를 메인 스레드에서 하므로 UI 업데이트는 반드시 메인 스레드에서 해야 합니다. [UIKit 공식문서](https://developer.apple.com/documentation/uikit)에서도 **Important** 로 메인 스레드에서의 UI 업데이트를 강조하고 있습니다.
93 |
94 | * 또 다른 이유는 아이폰의 그래픽 렌더링 방식입니다. 아이폰의 [그래픽스 파이프라인](https://ko.wikipedia.org/wiki/그래픽스_파이프라인)은 동기식으로 동작합니다.
95 |
96 | 
97 |
98 | 레이블의 텍스트를 업데이트하는 상황을 예로 들어보겠습니다. 먼저 화면에 표시할 레이블의 계층 구조를 인코딩하여 렌더 서버로 전송합니다(Commit Transaction). 전송된 레이블의 뷰 계층은 렌더 서버에서 디코딩되고(Decode), 렌더 서버는 GPU 에게 렌더링 요청을 합니다(Draw Calls). 그런 다음 GPU 가 렌더링 작업을 시작합니다(Render). [벡터 형태](https://ko.wikipedia.org/wiki/벡터_그래픽스)의 글꼴을 [래스터화](https://ko.wikipedia.org/wiki/래스터화)하여 텍스트를 픽셀로 변환합니다. 텍스트를 자르거나, 투명도를 적용할 때와 같이 해당 텍스트가 혼합된 레이어의 일부분일 경우 그래픽 렌더러는 보여줘야 할 레이블의 픽셀을 계산합니다. 렌더링 작업이 모두 완료되면 픽셀 정보들을 화면에 표시하게 됩니다(Display).
99 |
100 | 
101 |
102 | Core Animation Pipeline 에서는 1/60 초 만에 준비작업을 끝내고, 렌더링 서버로 데이터를 전송한 후 1/60 초 만에 렌더링을 완료합니다. 이런 식으로 동기식임에도 불구하고 앱이 멈추지 않고 화면 표시가 가능합니다.
103 |
104 | 하지만 백그라운드 스레드에서 UI 업데이트를 하게 된다면, 많은 백그라운드 스레드가 각기 다른 뷰 계층 구조를 인코딩하여 렌더 서버로 전송할 것이고, 이에 따라 GPU 에 많은 렌더링 요청을 보내게 됩니다. 렌더링은 시스템 리소스가 많이 드는 작업이므로 GPU 가 이를 처리할 수 없어 심각한 문제가 발생합니다. 그래서 UI 업데이트는 메인 스레드에서만 수행되어야 합니다.
105 |
106 | Core Animation 과 모바일 GPU 의 작동 방식에 대해 더 자세한 정보를 얻고 싶다면 [WWDC 2014: Advanced Graphics and Animations](https://asciiwwdc.com/2014/sessions/419) 를 읽어보세요.
107 |
108 | ### 참고할 만한 비슷한 질문들
109 |
110 | * [Why must UIKit operations be performed on the main thread?](https://stackoverflow.com/questions/18467114/why-must-uikit-operations-be-performed-on-the-main-thread)
111 | * [iOS) 왜 main.sync 를 하면 안될까](https://zeddios.tistory.com/519)
112 | * [iOS: Why the UI need to be updated on Main Thread](https://medium.com/@duwei199714/ios-why-the-ui-need-to-be-updated-on-main-thread-fd0fef070e7f)
113 |
114 | ----
115 |
116 | ### Q.
117 |
118 | > 클래스 이름의 NS 접두사의 의미가 무엇인가요?
119 |
120 | [Cocoa / Cocoa Touch](https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Cocoa.html) 의 많은 클래스의 이름에 NS 접두사가 붙어있는 것을 볼 수 있었는데요. 어떤 의미인가요?
121 |
122 | [질문 바로가기](https://stackoverflow.com/questions/473758/what-does-the-ns-prefix-mean)
123 |
124 | ### A.
125 |
126 | * 먼저 NS 접두사를 이해하려면 [Objective-C](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/Introduction/Introduction.html) 의 특징을 알아야합니다. Objective-C 는 C 언어를 동적이고 객체 지향적으로 확장한 언어입니다. C 언어를 확장했기에 C++ 의 [namespace](https://docs.microsoft.com/ko-kr/cpp/cpp/namespaces-cpp?view=vs-2019) 같은 기능이 없습니다. 그래서 전역 namespace 에서 이름 충돌을 피하기위해 접두사가 필요합니다. 자기 자신만 사용할 목적으로 작성한 코드에서는 이 충돌을 신경쓰지 않아도 되지만, 다른 사람이 사용할 수 있도록 프레임워크 또는 라이브러리를 작성하는 경우 고유 접두사를 붙여야합니다. [Coding Guidelines for Cocoa](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingBasics.html#//apple_ref/doc/uid/20001281-1002226-BBCJECED) 과 [Google Objective-C Style Guide](https://google.github.io/styleguide/objcguide.html#prefixes) 에 접두사에 대한 설명이 있습니다. [CocoaDev: ChooseYourOwnPrefix](http://cocoadev.github.io/ChooseYourOwnPrefix/) 를 보면 Cocoa community 의 수많은 개발자가 자신이 선택한 고유한 접두사를 기록해놓은 것을 볼 수 있습니다.
127 |
128 | Cocoa 프레임워크는 [NeXTSTEP](https://en.wikipedia.org/wiki/NeXTSTEP) 운영체제용 앱을 만들기 위한 코드로부터 시작되었습니다. 1996년 애플이 다시 NeXT 를 인수했을 때 기존 클래스 이름을 포함하여 NeXTSTEP 의 라이브러리인 Foundation 과 AppKit 이 OS X 에 통합되었습니다(애플의 Cocoa 프레임워크에서는 현재까지도 같은 이름을 사용 중입니다). 이때 NeXTSTEP 의 개발자들은 앞서 설명한 충돌을 피하기 위한 접두사를 이름 앞에 추가하였는데, 이것이 바로 **NS** 입니다.
129 |
130 | * NS 접두사가 붙어있는 타입들은 Swift 가 나오기 전 Objective-C 에서 사용되었던 타입들입니다. [Working with Foundation Types](https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/working_with_foundation_types) 를 보면 알 수 있듯이 현재는 대부분의 NS 타입들이 Swift 의 타입들로 Bridge 되어 있어 Swift 의 타입들을 많이 사용하지만, NS 타입만의 고유한 특성을 활용하기 위해 NS 타입을 사용하기도 합니다.
131 |
132 | ### 참고할 만한 비슷한 질문들
133 |
134 | * [Swift - which types to use? NSString or String](https://stackoverflow.com/questions/24038629/swift-which-types-to-use-nsstring-or-string/24038680)
135 | * [Error 와 NSError 의 차이가 무엇인가요?](https://yagom.net/forums/topic/기초적인-swift-문법-질문드립니다-ㅠㅠ/)
136 | * [What is difference between NSDictionary vs Dictionary in Swift?](https://stackoverflow.com/questions/25554259/what-is-difference-between-nsdictionary-vs-dictionary-in-swift)
137 | * [What are these NS prefixed types?](https://forums.swift.org/t/what-are-these-ns-prefixed-types/19045)
138 |
139 | ----
140 |
141 | ### Q.
142 |
143 | > guard 와 if 의 차이점이 무엇인가요?
144 |
145 | guard 를 사용해 조건문을 작성하면 if 를 사용할 때와 어떤 차이가 있나요?
146 |
147 | [질문 바로가기](https://stackoverflow.com/questions/30791488/swifts-guard-keyword)
148 |
149 | ### A.
150 |
151 | * 먼저 if 를 사용한 코드입니다.
152 |
153 | ```swift
154 | func foo(x: Int?) {
155 | if let x = x where x > 0 {
156 | // 조건을 만족했을 때 실행할 코드를 이곳에 작성합니다.
157 | } else {
158 | // 조건을 만족하지 못했을 때 실행할 코드를 이곳에 작성합니다.
159 | }
160 | // 이곳에 작성하는 코드는 위 조건과 관계없이 실행되게 됩니다.
161 | }
162 | ```
163 |
164 | if 안에 조건을 만족했을 때 실행할 코드들을 모두 작성하게 됩니다. if 블록을 벗어나면 옵셔널 바인딩 된 x 는 사용할 수 없습니다.
165 |
166 | guard 와는 달리 if 밖에서 조건과 관계없이 실행되는 코드를 작성할 수 있습니다.
167 |
168 | * guard 를 사용한 코드입니다.
169 |
170 | ```swift
171 | func foo(x: Int?) {
172 | guard let x = x where x > 0 else {
173 | // 조건을 만족하지 못했을 때 실행할 코드를 이곳에 작성합니다.
174 | return
175 | }
176 |
177 | // 조건을 만족했을 때 실행할 코드를 이곳에 작성합니다.
178 | }
179 | ```
180 |
181 | 조건을 만족하지 않으면 else 문이 실행되어 else 문 안의 코드를 실행하고 함수를 종료합니다. guard 를 사용하면 조건을 만족하지 않을 땐 항상 continue, break, return 등을 사용하여 guard 가 선언된 스코프를 빠져나가야 합니다.
182 |
183 | 조건을 만족하면 현재 함수 내 guard 문이 실행된 이후의 모든 범위에서 옵셔널 바인딩 한 x 를 사용할 수 있습니다.
184 |
185 | * 몇가지 팁을 드리자면, if 안에 스코프를 빠져나가는 코드가 있는 경우에는 guard 로 바꾸는 것이 좋습니다. 예외처리를 할 때도 guard 를 사용하면 좀 더 명시적인 예외처리가 가능합니다. if 가 여러개 중첩되어 코드 줄바꿈의 깊이가 깊어졌을 때도 guard 를 사용하면 깊이를 줄이고 가독성을 향상시킬 수 있습니다.
186 |
187 | ```swift
188 | func isZero(x: String) -> Bool? {
189 | if let x = Int(x) {
190 | if x == 0 {
191 | return true
192 | } else {
193 | return false
194 | }
195 | } else {
196 | return nil
197 | }
198 | }
199 | ```
200 |
201 | ```swift
202 | func isZero(x: String) -> Bool? {
203 | guard let x = Int(x) else { return nil }
204 | guard x == 0 else { return false }
205 | return true
206 | }
207 | ```
208 |
209 | String 을 인자로 받아 Int 로 변환하고, 그 값이 0 인지 아닌지 확인하는 함수입니다. 인자로 받은 문자열이 숫자가 아니면 nil 을 반환하고 0이면 true, 0이 아니면 false 를 반환합니다. if 를 사용해서 구현한 함수보다 guard 를 사용한 함수가 코드의 깊이도 얕고, 어떤 때에 어떤 값을 반환하는지 좀 더 명확하게 알 수 있는 것을 확인할 수 있습니다.
210 |
211 | * if 보다 guard 를 사용했을 때 더 좋은 상황이 많지만, if 를 사용해야만 하는 상황도 있으므로 위의 특징들을 참고해서 본인이 작성한 코드의 상황에 맞게 사용하시면 됩니다.
212 |
213 | ### 참고할 만한 비슷한 질문들
214 |
215 | * [Swift: guard let vs if let](https://stackoverflow.com/questions/32256834/swift-guard-let-vs-if-let)
216 | * [What is basic difference between guard statement and if...else statement?](https://stackoverflow.com/questions/34703089/what-is-basic-difference-between-guard-statement-and-if-else-statement/34703255)
217 |
--------------------------------------------------------------------------------
/contents/week-5.md:
--------------------------------------------------------------------------------
1 | # iOS 질문 모음 - 5
2 |
3 | ### Q.
4 |
5 | > 왜 delegate 를 weak 으로 선언하나요?
6 |
7 | delegation 패턴에서 delegate 가 weak 으로 선언된 것을 확인할 수 있는데요. 왜 delegate 를 weak 으로 선언하나요?
8 |
9 | [질문 바로가기](https://stackoverflow.com/questions/8449040/why-use-weak-pointer-for-delegation)
10 |
11 | ### A.
12 |
13 | * [retain cycle](https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html#ID51) 을 피하기 위해 delegate 를 weak 으로 선언합니다. retain cycle 은 두 클래스 인스턴스가 서로에 대한 강력한 참조를 가질 때 발생합니다.
14 |
15 | 흔히 사용되는 UITableView 로 예를 들어보겠습니다.
16 |
17 | ```swift
18 | class ViewController: UIViewController, UITableViewDelegate {
19 | // ViewController 가 tableView 를 소유
20 | var tableView: UITableView = UITableView()
21 |
22 | override func viewDidLoad() {
23 | super.viewDidLoad()
24 | view.addSubview(tableView)
25 | // tableView 의 delegate 로 자기 자신(ViewController)을 지정
26 | tableView.delegate = self
27 | }
28 | }
29 |
30 | ```
31 |
32 | 편의를 위해 레이아웃 설정 코드는 생략하였습니다. ViewController 가 tableView 를 소유하고 있고, ViewController 를 tableView 의 delegate 로 지정합니다.
33 |
34 | 위 코드를 그림으로 표현하면 다음과 같은데요.
35 |
36 |
37 |
38 | ViewController 가 tableView 를 강력하게 참조하고, tableView 의 delegate 가 ViewController 를 약하게 참조합니다. 공식문서에서 UITableView 의 [delegate](https://developer.apple.com/documentation/uikit/uitableview/1614894-delegate) 를 찾아보면 weak 으로 선언되어있는 것을 확인할 수 있습니다.
39 |
40 | ```swift
41 | weak var delegate: UITableViewDelegate? { get set }
42 | ```
43 |
44 | 만약 delegate 가 strong 으로 선언되어있었다면 서로가 서로를 강하게 참조하는 retain cycle 이 발생하여 두 객체가 메모리에서 해제되지 않는 문제가 발생할 것입니다.
45 |
46 | * delegate 를 누구로 지정 해주냐에 따라 strong 으로 선언해도 되는 경우도 존재합니다.
47 |
48 | ```swift
49 | protocol CustomViewDelegate: class {
50 | func userDidTap()
51 | }
52 |
53 | class CustomView: UIView {
54 | var delegate: CustomViewDelegate?
55 |
56 | func mockDelegatecall() {
57 | delegate?.userDidTap()
58 | }
59 | }
60 |
61 | // UserViewDelegate 프로토콜을 채택한 클래스
62 | class Delegate: CustomViewDelegate {
63 | func userDidTap() {
64 | print("tap!")
65 | }
66 | }
67 |
68 | class ViewController: UIViewController {
69 | // 별도의 delegate 객체를 생성
70 | let customView = CustomView()
71 | let customViewDelegate = Delegate()
72 |
73 | override func viewDidLoad() {
74 | super.viewDidLoad()
75 | view.addSubview(customView)
76 | customView.delegate = customViewDelegate
77 | }
78 | }
79 | ```
80 |
81 | ViewController 가 customView 와 customViewDelegate 를 소유하고 있고 customView 의 delegate 로 customViewDelegate 를 지정해주었습니다.
82 |
83 | 위 코드를 그림으로 표현하면 다음과 같습니다.
84 |
85 |
86 |
87 | delegate 를 담당하는 별도의 객체를 생성함으로써 customView 내부에 delegate 를 weak 으로 선언하지 않았지만, retain cycle 이 발생하지 않습니다.
88 |
89 | 추가로 delegate 를 구조체로 지정하는 경우에도 weak 을 사용하지 않아도 됩니다. 구조체는 값 유형이기 때문에 retain cycle 을 발생시키지 않습니다.
90 |
91 | * weak 으로 선언하지 않아도 되는 경우도 있으나, 복잡한 구조의 코드에서 weak 으로 선언하지 않은 delegate 는 예상치 못한 retain cycle 을 발생시킬 수 있으니 weak 으로 선언하는 것을 권장합니다.
92 |
93 | ### 참고할 만한 비슷한 질문, 자료
94 |
95 | * [Swift delegation - when to use weak pointer on delegate](https://stackoverflow.com/questions/30056526/swift-delegation-when-to-use-weak-pointer-on-delegate?rq=1)
96 | * [How can I make a weak protocol reference in 'pure' Swift (without @objc)](https://stackoverflow.com/questions/24066304/how-can-i-make-a-weak-protocol-reference-in-pure-swift-without-objc)
97 | * [swift delegation - when to use weak reference, why 'delegate' is nil?](https://stackoverflow.com/questions/34611873/swift-delegation-when-to-use-weak-reference-why-delegate-is-nil)
98 |
99 | -----
100 |
101 | ### Q.
102 |
103 | > frame 과 bounds 의 차이가 무엇인가요?
104 |
105 | UIView 과 UIView 를 상속받는 모든 뷰들은 frame 과 bounds 를 가지는데, 이 둘은 어떤 차이가 있나요?
106 |
107 | [질문 바로가기](https://stackoverflow.com/questions/1210047/cocoa-whats-the-difference-between-the-frame-and-the-bounds)
108 |
109 | ### A.
110 |
111 | * [frame](https://developer.apple.com/documentation/uikit/uiview/1622621-frame) 은 **부모 뷰의 좌표계**에서 뷰 위치와 크기를 정의합니다. 반면에 [bounds](https://developer.apple.com/documentation/uikit/uiview/1622580-bounds) 는 **자체 좌표계**에서 뷰의 위치와 크기를 정의합니다.
112 |
113 | * 다음 그림은 같은 뷰에서의 frame 과 bounds 을 표현한 것입니다. 두 이미지에서 빨간색 점은 frame, bounds 의 원점을 나타냅니다. 각각을 설명할 때 frame 은 부모 뷰의 좌표계를 기준으로 하므로 부모 뷰가 필요하지만, bounds 는 자체 좌표계를 기준으로 해서 부모 뷰와는 관계가 없기에 표시해주지 않았습니다.
114 |
115 | 
116 |
117 | ```swift
118 | Frame
119 | origin = (0, 0)
120 | width = 80
121 | height = 130
122 | Bounds
123 | origin = (0, 0)
124 | width = 80
125 | height = 130
126 | ```
127 |
128 | 현재 상태에서의 frame 과 bounds 는 동일합니다.
129 |
130 | 
131 |
132 | ```swift
133 | Frame
134 | origin = (40, 60)
135 | width = 80
136 | height = 130
137 | Bounds
138 | origin = (0, 0)
139 | width = 80
140 | height = 130
141 | ```
142 |
143 | 뷰의 위치를 변경하면 frame 의 좌푯값이 바뀌는 것을 확인할 수 있습니다. frame 이 부모 뷰의 좌표계를 기준으로 하기 때문인데요. 이와 달리 bounds 는 자기 자신을 기준으로 봤을 땐 아무런 변동사항이 없으므로 값에 아무런 변화가 없습니다.
144 |
145 | * 지금까지 frame 의 width, height 와 bounds 의 width, height 는 같았습니다. 하지만 이 두 값이 항상 같지만은 않습니다.
146 |
147 | 
148 |
149 | ```swift
150 | Frame
151 | origin = (20, 52)
152 | width = 118
153 | height = 187
154 | Bounds
155 | origin = (0, 0)
156 | width = 80
157 | height = 130
158 | ```
159 |
160 | [transform](https://developer.apple.com/documentation/quartzcore/calayer/1410836-transform) 을 활용해 뷰의 중점을 기준으로 뷰를 회전한 모습입니다. 뷰 자체에는 변화가 없기에 여전히 bounds 는 변화가 없지만, frame 의 크기는 달라진 것을 확인할 수 있습니다.
161 |
162 | [I Totally Didn't Understand Frames and Bounds](https://ashfurrow.com/blog/i-totally-didnt-understand-frames-and-bounds/) 에서는 frame 을 `부모 뷰의 좌표계를 기준으로 뷰에 적용된 모든 변형을 감싸는 가장 작은 상자` 라고 표현합니다. 이제 frame 과 bounds 의 차이가 어느 정도 보이지 않나요? 그럼 다음 예시로 넘어가겠습니다.
163 |
164 | * 지금까지의 예시에서 bounds 의 원점은 항상 (0,0) 이었습니다. 그럼 언제 bounds 의 원점이 이동할까요?
165 |
166 | 
167 |
168 | ```swift
169 | Frame
170 | origin = (40, 60)
171 | width = 80
172 | height = 130
173 | Bounds
174 | origin = (0, 0)
175 | width = 80
176 | height = 130
177 | ```
178 |
179 | 뷰의 자식 뷰가 너무 커서 한 번에 표시할 수 없는 경우입니다. 자신의 크기보다 큰 이미지 뷰를 자식 뷰로 가진 모습을 볼 수 있습니다. 뷰의 크기만큼만 이미지가 표시되고 있습니다.
180 |
181 | 
182 |
183 | ```swift
184 | Frame
185 | origin = (40, 60)
186 | width = 80
187 | height = 130
188 | Bounds
189 | origin = (280, 70)
190 | width = 80
191 | height = 130
192 | ```
193 |
194 | bounds 의 원점을 이동한 모습입니다. 부모 뷰를 기준으로 봤을 때 뷰의 프레임은 이동하지 않았지만, 뷰의 bounds 의 원점 좌표가 변경되었기 때문에 뷰가 표시하는 이미지 영역이 변경되었습니다. UIScrollView 가 이와 같은 원리로 동작합니다. UIScrollView 에 대한 자세한 정보를 알고 싶다면 [Understanding UIScrollView](https://oleb.net/blog/2014/04/understanding-uiscrollview/) 를 참고하세요.
195 |
196 | * 그럼 frame 과 bounds 는 어떤 경우에 사용할까요?
197 |
198 | * frame 은 부모 뷰를 기준으로 위치와 크기를 계산하기 때문에 뷰의 위치와 관련된 계산을 하거나, 뷰의 크기를 변경하는 등 외부의 변경을 수행할 때 사용합니다.
199 |
200 | * bounds 는 [draw(_:)](https://developer.apple.com/documentation/uikit/uiview/1622529-drawrect) 를 활용해 뷰 내부에 그림을 그리거나, transform 변형한 뷰의 크기를 알고 싶을 때, 자식 뷰를 정렬하는 것과 같이 내부적인 변경을 수행할 때 사용합니다.
201 |
202 | * [FrameVsBounds](https://github.com/maniramezan/FrameVsBounds) 에 frame 과 bounds 의 변화를 한눈에 보기 쉽게 만든 앱이 있습니다. 글만으론 frame 과 bounds 의 차이가 이해가 잘 안 된다면 참고해보세요.
203 |
204 |
205 |
206 | ### 참고할 만한 비슷한 질문, 자료
207 |
208 | * [UIView frame, bounds and center](https://stackoverflow.com/questions/5361369/uiview-frame-bounds-and-center)
209 | * [iOS ) Frame과 Bounds의 차이 (1/2)](https://zeddios.tistory.com/203)
210 | * [Stanford University - CS193P: Views, Drawing, Animation](https://www.slideshare.net/profmido/05-views)
211 |
212 | ----
213 |
214 | ### Q.
215 |
216 | > App Transport Security 가 HTTP 통신을 차단해요.
217 |
218 |
219 |
220 | App Transport Security 에 의해 http 리소스 로드가 차단됩니다. 차단을 해제하려면 Info.plist 를 어떻게 수정해야 하나요?
221 |
222 | [질문 바로가기](https://stackoverflow.com/questions/31254725/transport-security-has-blocked-a-cleartext-http/32560433#32560433)
223 |
224 | ### A.
225 |
226 | * [ATS(App Transport Security)](https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW33) 는 iOS 9 에서 도입된 개인정보 보호 기능입니다. 앱의 네트워크 연결을 취약점이 없는 산업 표준 프로토콜과 암호만 사용하도록 하여 개인 정보 보호 및 [데이터 무결성](https://ko.wikipedia.org/wiki/데이터_무결성)을 향상시킵니다. 이는 사용자가 개인 정보 유출에 대한 걱정 없이 앱을 사용할 수 있도록 해줍니다.
227 |
228 | * 앱에서 네트워크 통신을 할 때 HTTPS 가 아닌 HTTP 를 쓰게 된다면 네트워크 통신에서 안전성을 보장하지 못하므로 ATS 가 보안을 위해 차단하게 됩니다. HTTP 를 꼭 사용해야만 한다면 Info.plist 에서 특정 도메인에 대해 예외처리를 해줄 수 있습니다.
229 |
230 |
231 |
232 | 먼저 + 버튼을 눌러 `Information Property List` 에 `App Transport Security Settings` 를 추가합니다. `App Transport Security Settings` 에 예외처리할 도메인들을 담을 `Exception Domains` 를 만든 후, 예외처리할 도메인(예시에서는 `randomuser.me`)을 추가하고, `NSTemporaryExceptionAllowsInsecureHTTPLoads` 를 YES 로 설정해줍니다.
233 |
234 | 코드로 추가한다면 다음과 같습니다. Info.plist 에 마우스 오른쪽 클릭을 하면 나오는 메뉴에서 Open As -> Source Code 를 누르면 코드로 키를 추가할 수 있습니다.
235 |
236 |
237 |
238 | 기존 `` 의 `` 안에 아래의 코드블럭을 추가하면 됩니다.
239 |
240 | ```swift
241 | NSAppTransportSecurity
242 |
243 | NSExceptionDomains
244 |
245 | randomuser.me
246 |
247 | NSTemporaryExceptionAllowsInsecureHTTPLoads
248 |
249 |
250 |
251 |
252 | ```
253 |
254 | * `App Transport Security Settings` 의 `Allow Arbitrary Loads` 를 Yes 로 설정하여 ATS 를 비활성화하는 방법도 있지만, 디버깅이나 개발 과정에서만 사용하고 실제 앱 출시에는 보안 문제가 발생할 수 있으므로 권장하지 않습니다.
255 |
256 | * 이 외에도 ATS 의 설정을 바꿀 수 있는 여러 가지 키들이 존재합니다. 더 자세한 정보는 [App Transport Security dictionary primary keys](https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW34) 와 [Exception domains dictionary keys](https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW44) 를 참고하세요.
257 |
258 | ### 참고할 만한 비슷한 질문, 자료
259 |
260 | * [How can I add NSAppTransportSecurity to my info.plist file?](https://stackoverflow.com/questions/31216758/how-can-i-add-nsapptransportsecurity-to-my-info-plist-file/31629980#31629980)
261 | * [HTTP VS HTTPS 차이, 알면 사이트의 레벨이 보인다.](http://blog.wishket.com/http-vs-https-차이-알면-사이트의-레벨이-보인다/)
262 | * [SSL, TLS, HTTPS 의 정의](https://www.digicert.com/kr/what-is-ssl-tls-https/)
263 |
--------------------------------------------------------------------------------
/contents/week-6.md:
--------------------------------------------------------------------------------
1 | # iOS 질문 모음 - 6
2 |
3 | ### Q.
4 |
5 | > DispatchQueue.global() 과 DispatchQueue.init() 의 차이가 무엇인가요?
6 |
7 | 백그라운드 스레드를 관리하는 큐를 생성하고 싶은데요. [DispatchQueue.global()](https://developer.apple.com/documentation/dispatch/dispatchqueue/2300077-global) 으로 생성하는 방법과 [DispatchQueue.init()](https://developer.apple.com/documentation/dispatch/dispatchqueue/2300059-init) 으로 label 을 설정하여 생성하는 방법에는 어떤 차이가 있나요?
8 |
9 | [질문 바로가기](https://stackoverflow.com/questions/53489764/understanding-dispatch-queue-threading/53489813)
10 |
11 | ### A.
12 |
13 | * DispatchQueue.global() 은 인자로 받은 우선순위에 해당하는 전역 시스템 큐를 반환합니다. 이 전역 시스템 큐는 여러 작업을 동시에 수행하는 concurrent queue 입니다.
14 |
15 | * DispatchQueue 의 생성자로 만든 큐는 인자로 받은 여러 설정을 적용한 큐를 생성합니다.
16 |
17 | ```swift
18 | let myQueue = DispatchQueue(label: "myQueue")
19 | ```
20 |
21 | 위와 같이 label 만으로 만들게 되면, unspecified 의 우선순위를 가진 serial queue 를 생성합니다.
22 |
23 | ```swift
24 | let myQueue = DispatchQueue(label: "myQueue", qos: .userInitiated, attributes: .concurrent)
25 | ```
26 |
27 | 우선순위와 serial, concurrent 구분을 직접 설정해줄 수도 있습니다.
28 |
29 | * 지금까지의 예시들을 봤을 때 두 방식에 별다른 차이가 없는데요. 그럼 왜 global() 을 사용하지 않고 생성자를 사용할까요?
30 |
31 | 첫 번째는 디버깅에서의 이점이 있습니다.
32 |
33 | ```swift
34 | func debugQueue(_ queue: DispatchQueue) {
35 | for _ in 1...5 {
36 | queue.async {
37 | print("break point")
38 | }
39 | }
40 |
41 | for _ in 1...5 {
42 | queue.async {
43 | print("break point")
44 | }
45 | }
46 | }
47 | ```
48 |
49 | 디버깅 상황을 만들기 위해 큐를 인자로 받아 그 큐에서 간단한 동작을 하는 함수를 만들었습니다. 위에서 만든 myQueue 를 인자로 넘겨주고 print 가 실행되는 부분에 브레이크포인트를 설정해보겠습니다.
50 |
51 |
52 |
53 | Xcode 의 Debug navigator 를 보면 브레이크포인트가 설정된 코드가 실행된 큐와 스레드, 그리고 해당 큐가 관리 중인 스레드들도 확인할 수 있습니다. 지금은 큐가 하나뿐이지만 사용하는 큐가 더 많아지고, 해야 하는 작업량도 많아진다면 label 을 통해 큐를 구분할 수 있으니 디버깅하기 편해지겠죠?
54 |
55 |
56 |
57 | 반면에 DispatchQueue.global() 을 넘겨주면 시스템에 구현되어있는 전역 큐를 사용하게 되어 디버깅이 어려워지게 됩니다.
58 |
59 | 두 번째는 [barrier](https://developer.apple.com/documentation/dispatch/dispatchworkitemflags/1780674-barrier) 와 같은 DispatchQueue 의 몇 가지 세부적인 설정을 사용하기 위함입니다. barrier 란 단어 뜻 그대로 동시성을 지원하는 비동기 큐에서 장벽 같은 역할을 해주는 기능인데요.
60 |
61 | 
62 |
63 | 위 그림처럼 비동기 작업들이 실행되다가 flag 가 barrier 로 설정된 작업이 실행되면 그 작업이 끝날 때 까지 serial queue 처럼 동작하게 됩니다. 간단한 예시코드를 보겠습니다.
64 |
65 | ```swift
66 | let myQueue = DispatchQueue(label: "myQueue", attributes: .concurrent)
67 |
68 | for i in 1...5 {
69 | myQueue.async {
70 | print("\(i)")
71 | }
72 | }
73 |
74 | myQueue.async(flags: .barrier) {
75 | print("barrier!!")
76 | sleep(5)
77 | }
78 |
79 | for i in 6...10 {
80 | myQueue.async {
81 | print("\(i)")
82 | }
83 | }
84 | ```
85 |
86 | 숫자들을 비동기로 출력하는 코드 사이에 flags 를 barrier 로 설정한 코드를 추가해주었습니다.
87 |
88 |
89 |
90 | 숫자들이 출력되다가 barrier 블록이 실행되자 다음 작업들이 barrier 블록이 끝날 때까지 기다리는 것을 확인할 수 있습니다. 만약 barrier 가 없다면 concurrent queue 에서 비동기로 숫자들을 출력하기 때문에 한번에 모든 숫자가 출력될 것입니다. [barrier](https://developer.apple.com/documentation/dispatch/1452917-dispatch_barrier_sync?language=occ) 공식문서를 보면 barrier 를 지정하는 큐는 직접 만든 concurrent queue 여야만 한다고 합니다. barrier 를 포함한 몇몇 세부적인 설정들은 글로벌 큐에서는 사용할 수 없습니다.
91 |
92 | ### 참고할 만한 비슷한 질문, 자료
93 |
94 | * [Grand Central Dispatch Tutorial for Swift 4](https://www.raywenderlich.com/5370-grand-central-dispatch-tutorial-for-swift-4-part-1-2)
95 | * [DispatchQueue sync vs sync barrier in concurrent queue](https://stackoverflow.com/questions/58236153/dispatchqueue-sync-vs-sync-barrier-in-concurrent-queue)
96 |
97 | ----
98 |
99 | ### Q.
100 |
101 | > DispatchQueue 에서 main.sync 는 언제 사용하나요?
102 |
103 | DispatchQueue 에 대해 공부하면서 생긴 의문인데, 많은 자료에서 DispatchQueue.main 에서 sync 를 호출하지 말라고 합니다. 왜 호출하지 말라고 하는 것이며, 그럼 왜 GCD 에서 DispatchQueue.main.sync 가 구현되어있는 건가요?
104 |
105 | [질문 바로가기](https://stackoverflow.com/questions/42772907/what-does-main-sync-in-global-async-mean)
106 |
107 | ### A.
108 |
109 | * 먼저 DispatchQueue.main 에서 sync 를 호출하면 안 되는 이유를 이해하려면 [Deadlock(교착 상태)](https://ko.wikipedia.org/wiki/교착_상태) 이란 개념을 알아야 하는데요. Deadlock 이란 `두 개 이상의 작업이 서로 상대방의 작업이 끝나기만을 기다리고 있기 때문에 결과적으로 아무것도 완료되지 못하는 상태` 라고 합니다. Deadlock 은 DispatchQueue.main 뿐만 아니라 백그라운드 스레드를 관리하는 큐에서도 발생할 수 있습니다.
110 |
111 | * 간단한 코드로 예를 들어보겠습니다. DispatchQueue 의 생성자로 myQueue 를 생성해주었습니다. 생성자의 attributes 를 concurrent 로 설정해주지 않으면 기본값으로 serial queue 를 생성하게 됩니다.
112 |
113 | ```swift
114 | let myQueue = DispatchQueue(label: "label")
115 |
116 | myQueue.async {
117 | myQueue.sync {
118 | // 외부 블록이 완료되기 전에 내부 블록은 시작되지 않습니다.
119 | // 외부 블록은 내부 블록이 완료되기를 기다립니다.
120 | // => deadlock
121 | }
122 | // 이 부분은 영원히 실행되지 않습니다.
123 | }
124 | ```
125 |
126 | myQueue 는 **serial queue** 이므로 한 번에 하나의 작업을 합니다. serial queue 에 추가되는 작업은 DispatchQueue 에서 관리하는 고유한 스레드에서 실행됩니다. 따라서 위 예시에서 sync 내부 블록은 외부 블록이 완료될 때까지 기다리고, sync 블록 이후의 동작은 sync 내부 블록이 완료될 때까지 기다리는 deadlock 이 발생하게 됩니다.
127 |
128 | 이 문제는 myQueue 를 생성할 때
129 |
130 | ```swift
131 | let myQueue = DispatchQueue(label: "label", attributes: .concurrent)
132 | ```
133 |
134 | 와 같이 concurrent 속성을 설정하여 한번에 여러 작업을 실행할 수 있도록 해주어 해결할 수 있습니다.
135 |
136 | * 다시 본론으로 돌아오면, DispatchQueue.main 은 프로그램의 메인 스레드에서 작업을 실행하는 전역적으로 사용이 가능한 **serial queue** 입니다. 앱의 run loop 와 함께 작동하며, 대기 중인 동작을 run loop 의 다른 이벤트 처리와 조율해줍니다. 이러한 DispatchQueue.main 에서 sync 를 호출하게 된다면 끊임없이 앱의 이벤트 처리를 하고 있던 메인 스레드가 sync 호출에 의해 멈추게 되고 위에서 예시로 들었던 코드와 같이 deadlock 이 발생하게 됩니다.
137 |
138 | * 그럼 DispatchQueue.main.sync 는 어떤 경우에 사용할까요? 백그라운드 스레드에서 이루어지는 작업들 사이에 순서에 맞게 메인 스레드에서 어떠한 작업이 이루어져야 할 때 사용합니다. 다음 코드를 보겠습니다.
139 |
140 | ```swift
141 | DispatchQueue.global().async {
142 | // UI 업데이트 전 실행되어야만 하는 코드
143 | DispatchQueue.main.sync {
144 | // UI 업데이트
145 | }
146 | // UI 업데이트 후에 실행되어야만 하는 코드
147 | }
148 | ```
149 |
150 | 백그라운드 스레드에서 작업이 진행되는 중 UI 업데이트가 이루어져야 하는 상황입니다. 좀 더 구체적으로 예를 들자면 테이블 뷰나 콜렉션 뷰에 셀을 추가하거나 제거할 때, 셀의 추가, 제거 작업이 이루어지던 중 네트워크 통신과 같은 다른 비동기 작업으로 인해 DataSource 의 데이터가 변경된다면 DataSource 의 데이터와 뷰의 데이터가 일치하지 않아 에러가 발생하게 됩니다. 이를 방지하기 위해 사용합니다. 애플의 [PHPhotoLibraryChangeObserver](https://developer.apple.com/documentation/photokit/phphotolibrarychangeobserver) 문서 `Handling photo library changes in a collection view` 의 예시코드에서도 사진앨범의 데이터와 업데이트한 콜렉션 뷰의 데이터가 달라지는 상황을 방지하기위해 DispatchQueue.main.sync 를 호출하는 것을 확인할 수 있습니다.
151 |
152 | ### 참고할 만한 비슷한 질문, 자료
153 |
154 | * [How do I create a deadlock in Grand Central Dispatch?](https://stackoverflow.com/questions/15381209/how-do-i-create-a-deadlock-in-grand-central-dispatch)
155 | * [why concurrentQueue.sync DON'T cause deadlock](https://stackoverflow.com/questions/53293965/why-concurrentqueue-sync-dont-cause-deadlock)
156 | * [Concurrency Programming Guide](https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW2)
157 | * [Difference between DispatchQueue.main.async and DispatchQueue.main.sync](https://stackoverflow.com/questions/44324595/difference-between-dispatchqueue-main-async-and-dispatchqueue-main-sync)
158 | * [How to dispatch on main queue synchronously without a deadlock?](https://stackoverflow.com/questions/10330679/how-to-dispatch-on-main-queue-synchronously-without-a-deadlock)
159 |
160 | -----
161 |
162 | ### Q.
163 |
164 | > 특정한 여러 개의 비동기 작업이 완료된 후에 다른 작업을 실행해주고 싶어요.
165 |
166 | 반복문을 사용하여 네트워크 요청을 여러 번 보내는데, 모든 요청이 완료된 후 다른 작업을 실행하는 방법이 있나요?
167 |
168 | [질문 바로가기](https://stackoverflow.com/questions/35906568/wait-until-swift-for-loop-with-asynchronous-network-requests-finishes-executing)
169 |
170 | ### A.
171 |
172 | * [DispatchGroup](https://developer.apple.com/documentation/dispatch/dispatchgroup) 을 활용해 그룹에 연결된 작업들이 완료되었을 때 핸들러를 실행해줄 수 있습니다. 5개의 이미지를 다운받은 후 UI 를 업데이트해줘야 하는 상황을 예시로 들어보겠습니다.
173 |
174 | ```swift
175 | override func viewDidLoad() {
176 | super.viewDidLoad()
177 |
178 | for _ in 1...5 {
179 | DispatchQueue.global().async {
180 | print("이미지 다운로드 요청!")
181 | sleep(3)
182 | print("이미지 다운로드 완료!")
183 | }
184 | }
185 |
186 | print("UI 업데이트!")
187 | }
188 | ```
189 |
190 | 이미지 데이터를 다운받는데 3초가 걸린다고 가정하고 작성한 예시코드입니다. 코드를 실행해보면
191 |
192 |
193 |
194 | 이미지 다운로드를 비동기로 요청하기 때문에 UI 업데이트와 이미지 다운로드 간의 순서를 보장하지 않는 것을 확인할 수 있습니다.
195 |
196 | ```swift
197 | override func viewDidLoad() {
198 | super.viewDidLoad()
199 |
200 | let downloadGroup = DispatchGroup()
201 |
202 | for _ in 1...5 {
203 | DispatchQueue.global().async(group: downloadGroup) {
204 | print("이미지 다운로드 요청!")
205 | sleep(3)
206 | print("이미지 다운로드 완료!")
207 | }
208 | }
209 |
210 | downloadGroup.notify(queue: .main) {
211 | print("UI 업데이트!")
212 | }
213 | }
214 | ```
215 |
216 | downloadGroup 이라는 DispatchGroup 을 만들어 다운로드 요청 작업들을 downloadGroup 에 연결해주고 downloadGroup 의 작업이 끝나면 main 큐에서 UI 업데이트를 하도록 해주었습니다.
217 |
218 |
219 |
220 | 이미지 다운로드가 모두 완료된 후 UI 업데이트가 출력되는 것을 확인할 수 있습니다.
221 |
222 | ```swift
223 | downloadGroup.wait()
224 | ```
225 |
226 | [wait()](https://developer.apple.com/documentation/dispatch/dispatchgroup/2016090-wait) 을 활용해 wait() 을 호출한 큐에서 그룹의 작업이 완료될 때까지 동기적으로 기다리도록 할 수도 있습니다. 그러나 이 방법은 wait() 을 호출한 큐를 차단하므로 deadlock 이 발생할 수 있어 주의해야 합니다. deadlock 발생을 피하려고 [wait(timeout:)](https://developer.apple.com/documentation/dispatch/dispatchgroup/1780590-wait) 으로 최대 대기 시간을 설정해주기도 합니다.
227 |
228 | * 비동기 작업관련 API 가 내부적으로 비동기로 구현되어 있어 async 블록에서 group 지정을 해줄 수 없는 경우 다음과 같이 [enter()](https://developer.apple.com/documentation/dispatch/dispatchgroup/1452803-enter), [leave()](https://developer.apple.com/documentation/dispatch/dispatchgroup/1452872-leave) 를 활용하는 방법도 있습니다.
229 |
230 | ```swift
231 | override func viewDidLoad() {
232 | super.viewDidLoad()
233 |
234 | let downloadGroup = DispatchGroup()
235 |
236 | for _ in 1...5 {
237 | guard let imageURL = URL(string: "https://example.com") else { return }
238 | downloadGroup.enter()
239 | URLSession.shared.dataTask(with: imageURL) { (data, response, error) in
240 | // 이미지 다운로드 요청 완료 후 동작
241 | downloadGroup.leave()
242 | }.resume()
243 | }
244 |
245 | downloadGroup.notify(queue: .main) {
246 | print("UI 업데이트!")
247 | }
248 | }
249 | ```
250 |
251 | ### 참고할 만한 비슷한 질문, 자료
252 |
253 | * [Waiting until the task finishes](https://stackoverflow.com/questions/42484281/waiting-until-the-task-finishes)
254 | * [Waiting until two async blocks are executed before stating another block](https://stackoverflow.com/questions/11909629/waiting-until-two-async-blocks-are-executed-before-starting-another-block)
255 | * [Swift DispatchGroup notify before task finish](https://stackoverflow.com/questions/11909629/waiting-until-two-async-blocks-are-executed-before-starting-another-block)
256 |
257 | ----
258 |
259 | ### Q.
260 |
261 | > 동시에 실행되는 비동기 작업의 개수를 제한할 수 있나요?
262 |
263 | 비동기적으로 데이터베이스에서 데이터를 수집하고 있습니다. 동시에 실행되는 작업의 개수를 제한할 방법이 있나요?
264 |
265 | [질문 바로가기]()
266 |
267 | ### A.
268 |
269 | * [OperationQueue](https://developer.apple.com/documentation/foundation/operationqueue) 의 [maxConcurrentOperationCount](https://developer.apple.com/documentation/foundation/operationqueue/1414982-maxconcurrentoperationcount) 나 [DispatchSemaphore](https://developer.apple.com/documentation/dispatch/dispatchsemaphore) 를 활용해 제한할 수 있습니다. 데이터를 수집하는데에 3초의 시간이 소모되며, 동시에 3개의 작업만 해야 한다고 가정하고 예시코드를 작성해 보겠습니다.
270 |
271 | * OperationQueue 를 활용한 방법
272 |
273 | ```swift
274 | func acquire(data: Int) {
275 | print("데이터 \(data) 수집 시작!")
276 | sleep(3)
277 | print("데이터 \(data) 수집 완료!")
278 | }
279 |
280 | let acquiringQueue = OperationQueue()
281 | acquiringQueue.maxConcurrentOperationCount = 3
282 |
283 | for i in 1...10 {
284 | acquiringQueue.addOperation {
285 | acquire(data: i)
286 | }
287 | }
288 | ```
289 |
290 | 데이터 수집 작업을 관리할 acquiringQueue 라는 OperationQueue 를 만들어 주었습니다. OperationQueue 의 maxConcurrentOperationCount 를 3으로 설정해 동시에 실행할 수 있는 작업의 수를 3개로 제한해주었습니다. 다음은 총 10개의 데이터를 수집하는 코드를 실행한 모습입니다.
291 |
292 |
293 |
294 | 작업을 3개씩 실행하는 것을 확인할 수 있습니다.
295 |
296 | * DispatchSemaphore 를 활용한 방법
297 |
298 | ```swift
299 | let acquiringQueue = DispatchQueue(label: "acquiringQueue", attributes: .concurrent)
300 | let acquiringSemaphore = DispatchSemaphore(value: 3)
301 |
302 | DispatchQueue.global().async {
303 | for i in 1...10 {
304 | // count -= 1
305 | acquiringSemaphore.wait()
306 | acquiringQueue.async {
307 | acquire(data: i)
308 | // count += 1
309 | acquiringSemaphore.signal()
310 | }
311 | }
312 | }
313 | ```
314 |
315 | 이번에는 DispatchQueue 의 생성자로 동시성을 지원하는 사용자 지정 큐와 DispatchSemaphore 를 생성해주었습니다. 세마포어를 만들 때 사용 가능한 리소스의 수를 지정해줍니다. 이 값은 세마포어가 제한할 작업의 개수가 됩니다. 비동기 호출 직전에 [wait()](https://developer.apple.com/documentation/dispatch/dispatchsemaphore/2016071-wait) 을 호출하여 세마포어의 값을 1 감소시킵니다. 세마포어의 값이 음수가 되면 함수는 커널에 스레드를 차단하도록 지시합니다. wait() 을 메인 스레드에서 호출하게 되면 메인 스레드를 차단하기 때문에 주의해야합니다. 비동기 작업이 완료된 후 [signal()](https://developer.apple.com/documentation/dispatch/dispatchsemaphore/1452919-signal) 을 호출하여 세마포어의 값을 1 증가시키면 차단된 스레드 중 하나가 차단 해제되고 남은 작업을 수행하게 됩니다. 실행 결과는 OperationQueue 를 활용한 방법과 같습니다.
316 |
317 | * 동시에 실행되는 작업의 개수를 제한하는 경우 외에도 일반적으로 DispatchSemaphore 는 공유 자원에 접근하는 스레드의 개수를 제한해야 할 때 유용하게 사용합니다.
318 |
319 | ### 참고할 만한 비슷한 질문, 자료
320 |
321 | * [When to use Semaphore instead of Dispatch Group?](https://stackoverflow.com/questions/49923810/when-to-use-semaphore-instead-of-dispatch-group)
322 | * [Does DispatchSemaphore wait for specific thread objects?](https://stackoverflow.com/questions/61309944/does-dispatchsemaphore-wait-for-specific-thread-objects)
323 | * [can I call on semaphore.wait() main thread?](https://stackoverflow.com/questions/50791315/can-i-call-on-semaphore-wait-main-thread)
324 |
--------------------------------------------------------------------------------
/contents/week-7.md:
--------------------------------------------------------------------------------
1 | # iOS 질문 모음 - 7
2 |
3 | ### Q.
4 |
5 | > == 와 === 의 차이가 무엇인가요?
6 |
7 | `==` 와 `===` 은 어떤 차이가 있나요?
8 |
9 | [질문 바로가기](https://stackoverflow.com/questions/24002819/difference-between-and)
10 |
11 | ### A.
12 |
13 | * `==` 연산자는 인스턴스의 **값**이 같은지 확인하고, `===` 연산자는 **참조하고 있는 인스턴스**가 같은지 확인합니다.
14 |
15 | * 참조 타입인 클래스는 여러 상수와 변수가 동일한 인스턴스를 참조할 수 있습니다. 클래스의 인스턴스는 힙(Heap) 영역에 할당되고 그 인스턴스를 참조하는 상수, 변수는 스택(Stack) 영역에 할당됩니다. `==` 연산자는 인스턴스가 서로 같은지를 의미하며, 두 비교 대상의 참조가 같을 수도 있지만, 꼭 같은 참조를 가져야만 하지는 않습니다. `===` 연산자는 참조 타입만 비교할 수 있지만, `==` 연산자는 값 타입도 비교할 수 있습니다. String, Int 와 같은 대부분의 Swift 표준 라이브러리 기본 타입들은 [Equatable](https://developer.apple.com/documentation/swift/equatable) 프로토콜을 채택하고 있으나, 사용자 정의 클래스, 구조체는 Equatable 프로토콜을 채택하고 서로 같음을 판단하는 기준을 `static func == (lhs:, rhs:) -> Bool` 메소드를 구현함으로써 제공해주어야 합니다.
16 |
17 | ```swift
18 | class Person: Equatable {
19 | let id: Int
20 | let name: String
21 |
22 | init(id: Int, name: String) {
23 | self.id = id
24 | self.name = name
25 | }
26 |
27 | static func == (lhs: Person, rhs: Person) -> Bool {
28 | return lhs.id == rhs.id
29 | }
30 | }
31 | ```
32 |
33 | id 와 name 을 프로퍼티로 가지는 Person 클래스입니다. Equatable 프로토콜을 채택하여 id 프로퍼티의 값의 일치 여부로 서로 같음을 판단하도록 구현해주었습니다.
34 |
35 | ```swift
36 | let person1 = Person(id: 5, name: "Bob")
37 | let person2 = Person(id: 5, name: "TTOzzi")
38 |
39 | person1 == person2 // return true
40 | ```
41 |
42 | name 프로퍼티의 값이 다르더라도 id 값만으로 비교하므로 true 를 반환하는 것을 확인할 수 있습니다.
43 |
44 | * `==` 와 달리 `===` 는 전역 함수로 이미 구현되어있습니다.
45 |
46 | ```swift
47 | @inlinable
48 | public func === (lhs: AnyObject?, rhs: AnyObject?) -> Bool {
49 | switch (lhs, rhs) {
50 | case let (l?, r?):
51 | return ObjectIdentifier(l) == ObjectIdentifier(r)
52 | case (nil, nil):
53 | return true
54 | default:
55 | return false
56 | }
57 | }
58 | ```
59 |
60 | `===` 연산자의 내부 구현입니다. [AnyObject](https://developer.apple.com/documentation/swift/anyobject) 를 인자로 받도록 구현하여 비교 대상을 클래스 타입으로 제한합니다. 각 비교 대상으로 클래스 인스턴스의 고유한 ID 값인 [ObjectIdentifier](https://developer.apple.com/documentation/swift/objectidentifier) 를 생성하여 그 둘을 비교하도록 구현되어있습니다.
61 |
62 | ```swift
63 | let person1 = Person(id: 5, name: "Bob")
64 | let person2 = Person(id: 5, name: "Bob")
65 | let person3 = person1
66 |
67 | person1 === person2 // return false
68 | person1 === person3 // return true
69 | ```
70 |
71 | person1 과 person2 의 비교에서 프로퍼티가 완전히 일치함에도 false 를 반환하는 것을 확인할 수 있습니다. 그에 반해 person3 는 person1 과 같은 인스턴스를 참조하므로 true 를 반환합니다.
72 |
73 | * Swift 에서 값의 일치 여부를 판단하는 연산자의 구현에 대해 좀 더 알고 싶다면 [Equatable.swift](https://github.com/apple/swift/blob/master/stdlib/public/core/Equatable.swift) 를 참고해보세요.
74 |
75 | ### 참고할 만한 비슷한 질문, 자료
76 |
77 | * [Difference between using ObjectIdentifier() and '===' Operator](https://stackoverflow.com/questions/39587027/difference-between-using-objectidentifier-and-operator)
78 |
79 | -----
80 |
81 | ### Q.
82 |
83 | > mutating 이 무엇인가요?
84 |
85 | 메소드 앞의 mutating 키워드의 의미가 무엇인가요?
86 |
87 | [질문 바로가기](https://stackoverflow.com/questions/51128666/what-does-the-swift-mutating-keyword-mean)
88 |
89 | ### A.
90 |
91 | * 기본적으로 값 타입인 구조체와 열거형의 프로퍼티는 인스턴스 메소드 내에서 수정할 수 없습니다. 하지만 특정한 메소드 내에서 프로퍼티의 값을 변경해야 할 때가 있다면 메소드 앞에 mutating 키워드를 붙여 내부 프로퍼티를 수정하는 메소드를 만들 수 있습니다.
92 |
93 | ```swift
94 | struct Point {
95 | var x = 0.0
96 | var y = 0.0
97 |
98 | func moveBy(x deltaX: Double, y deltaY: Double) {
99 | // 'self' is immutable 에러 발생
100 | x += deltaX
101 | y += deltaY
102 | }
103 | }
104 | ```
105 |
106 | x, y 좌표를 가지는 Point 구조체를 만들어 주었습니다. moveBy(x:y:) 메소드에서 Point 구조체 내부의 x, y 프로퍼티의 값을 수정하게 되면 에러가 발생합니다.
107 |
108 | ```swift
109 | struct Point {
110 | var x = 0.0
111 | var y = 0.0
112 |
113 | mutating func moveBy(x deltaX: Double, y deltaY: Double) {
114 | x += deltaX
115 | y += deltaY
116 | }
117 | }
118 | ```
119 |
120 | 이럴 때, mutating 키워드를 메소드 앞에 붙여 메소드가 구조체의 값을 변경할 것임을 명시해야 합니다.
121 |
122 | ```swift
123 | struct Point {
124 | var x = 0.0
125 | var y = 0.0
126 |
127 | mutating func moveBy(x deltaX: Double, y deltaY: Double) {
128 | self = Point(x: x + deltaX, y: y + deltaY)
129 | }
130 | }
131 | ```
132 |
133 | self 에 새로운 인스턴스를 만들어 할당할 수도 있습니다. mutating 메소드로 내부 프로퍼티의 값을 변경하면 프로퍼티만 변경되는 것이 아니라, **프로퍼티가 변경된 완전히 새로운 인스턴스를 만들어 기존 인스턴스를 대체**하므로 각각의 프로퍼티를 수정하는 것과 self 에 새로운 인스턴스를 만들어 할당하는 것은 완벽하게 똑같이 작동합니다.
134 |
135 | ```swift
136 | let somePoint = Point()
137 | // Cannot use mutating member on immutable value 에러 발생
138 | somePoint.moveBy(x: 1, y: 2)
139 | ```
140 |
141 | 같은 맥락으로 mutating 메소드를 호출한다는 것은 기존의 인스턴스를 새로운 인스턴스로 대체한다는 것을 의미하므로 구조체가 상수로 선언되어 있다면 mutating 메소드를 호출할 수 없습니다.
142 |
143 | * 구조체와 같이 열거형도 똑같이 동작하며 다음과 같이 활용할 수 있습니다.
144 |
145 | ```swift
146 | enum TriStateSwitch {
147 | case off, low, high
148 |
149 | mutating func next() {
150 | switch self {
151 | case .off:
152 | self = .low
153 | case .low:
154 | self = .high
155 | case .high:
156 | self = .off
157 | }
158 | }
159 | }
160 |
161 | var ovenLight = TriStateSwitch.low
162 | ovenLight.next() // high
163 | ovenLight.next() // off
164 | ```
165 |
166 | ### 참고할 만한 비슷한 질문, 자료
167 |
168 | * [Swift and mutating struct](https://stackoverflow.com/questions/24035648/swift-and-mutating-struct)
169 | * [Mutating function inside class](https://stackoverflow.com/questions/38422781/mutating-function-inside-class)
170 | * [Swift: Methods](https://docs.swift.org/swift-book/LanguageGuide/Methods.html)
171 |
172 | -----
173 |
174 | ### Q.
175 |
176 | > lazy 가 무엇인가요?
177 |
178 | Swift 에서 lazy 키워드의 의미가 무엇인가요?
179 |
180 | [질문 바로가기](https://stackoverflow.com/questions/44153817/what-is-lazy-meaning-in-swift)
181 |
182 | ### A.
183 |
184 | * lazy 키워드로 변수를 선언하여 해당 변수가 처음 요청될 때 계산되는 변수를 만들 수 있습니다.
185 |
186 | ```swift
187 | class Properties {
188 | var property: Int = Properties.someExpensiveFunction("property")
189 | lazy var lazyProperty: Int = Properties.someExpensiveFunction("lazyProperty")
190 |
191 | static func someExpensiveFunction(_ name: String) -> Int {
192 | // 복잡한 로직...
193 | print("calculating \(name)...")
194 | return 0
195 | }
196 | }
197 | ```
198 |
199 | 복잡한 계산 로직을 갖고, 많은 리소스를 소모하는 메소드 someExpensiveFunction 의 반환 값을 프로퍼티로 가지는 Properties 클래스입니다.
200 |
201 | ```swift
202 | let properties = Properties()
203 | ```
204 |
205 |
206 |
207 | 생성자로 인스턴스를 생성하면, property 만 생성되는 것을 확인할 수 있습니다.
208 |
209 | ```swift
210 | let properties = Properties()
211 | print(properties.lazyProperty)
212 | ```
213 |
214 |
215 |
216 | lazy 로 선언한 프로퍼티는 프로퍼티에 접근할 때 생성됩니다. 이처럼 프로퍼티의 초깃값을 계산하는 데에 많은 리소스를 소모하는 경우, 인스턴스를 생성할 때 계산하지 않고 프로퍼티가 필요할 때 계산을 수행하도록 구현하기 위해 사용합니다.
217 |
218 | * 추가로 인스턴스의 초기화가 완료될 때까지 값을 알 수 없는 외부 요인에 따라 프로퍼티의 초깃값이 달라질 때도 유용하게 사용합니다.
219 |
220 | * lazy 프로퍼티는 처음 접근할 때만 계산되고, 그 후엔 값을 저장합니다. 하지만 프로퍼티가 초기화되지 않은 상태에서 여러 스레드에서 동시에 프로퍼티에 접근하는 경우, 프로퍼티를 초기화하기 위한 계산이 여러 번 이루어질 수 있습니다.
221 |
222 | ```swift
223 | let properties = Properties()
224 | for _ in 1...10 {
225 | DispatchQueue.global().async {
226 | print(properties.lazyProperty)
227 | }
228 | }
229 | ```
230 |
231 | 여러 개의 백그라운드 스레드에서 동시에 lazyProperty 에 접근하는 코드입니다.
232 |
233 |
234 |
235 | lazyProperty 의 초기화가 여러번 이루어진 것을 확인할 수 있습니다. 이런 경우, 중간에 값의 변경이 이루어져 예상치 못한 결과를 초래할 수 있으므로 주의해야 합니다.
236 |
237 | * lazy 프로퍼티와 비슷하게 동작하는 [LazySequence](https://developer.apple.com/documentation/swift/lazysequence#overview) 도 존재합니다. 배열, 문자열 등 [Sequence](https://developer.apple.com/documentation/swift/sequence) 프로토콜을 채택한 타입이라면 기본적으로 [lazy](https://developer.apple.com/documentation/swift/sequence/1641562-lazy) 에 구현되어 있습니다. LazySequence 는 map, filter 와 같은 고차함수를 사용했을 때, 시퀀스 전체를 계산하지 않고, 접근한 인덱스의 값을 가져오는 데 필요한 계산만 수행합니다.
238 |
239 | ```swift
240 | func double(_ number: Int) -> Int {
241 | print("calculating \(number)...")
242 | return number * 2
243 | }
244 | ```
245 |
246 | 인자로 받은 정수에 2를 곱해 반환하는 메소드입니다.
247 |
248 | ```swift
249 | let doubled = [1, 2, 3].map { double($0) }
250 | ```
251 |
252 |
253 |
254 | 일반적인 배열에 map 을 사용하면 계산된 배열을 doubled 에 저장할 때 배열의 모든 값을 계산합니다.
255 |
256 | ```swift
257 | let lazyDoubled = [1, 2, 3].lazy.map { double($0) }
258 | print(lazyDoubled[1])
259 | ```
260 |
261 |
262 |
263 | 배열에 구현되어있는 LazySequence 에 map 을 사용하였습니다. 접근한 값을 가져오는 데 필요한 계산만 수행하는 것을 확인할 수 있습니다. LazySequence 는 lazy 프로퍼티와는 달리 값을 저장하지 않고 매번 계산합니다. 이를 인지하고, 상황에 맞게 적절하게 사용하면 불필요한 계산을 피하고 성능을 향상시킬수 있을 것입니다. 좀 더 자세한 정보와 활용법은 [LazySequence.swift](https://github.com/apple/swift/blob/master/stdlib/public/core/LazySequence.swift) 를 참고하세요.
264 |
265 | ### 참고할 만한 비슷한 질문, 자료
266 |
267 | * [Swift: Properties](https://docs.swift.org/swift-book/LanguageGuide/Properties.html#)
268 | * [Lazy property's memory management](https://stackoverflow.com/questions/41478975/lazy-property-s-memory-management)
269 | * [What are lazy variables?](https://www.hackingwithswift.com/example-code/language/what-are-lazy-variables)
270 | * [Why and when to use lazy with Array in Swift?](https://stackoverflow.com/questions/51917054/why-and-when-to-use-lazy-with-array-in-swift)
271 |
272 |
--------------------------------------------------------------------------------
/contents/week-8.md:
--------------------------------------------------------------------------------
1 | # iOS 질문 모음 - 8
2 |
3 | ### Q.
4 |
5 | > UIWindow 가 무엇인가요?
6 |
7 | iOS 앱에서 UIWindow 가 무엇인가요??
8 |
9 | [질문 바로가기](https://www.quora.com/What-is-UIWindow-in-Swift-Does-a-single-view-controller-contain-multiple-windows)
10 |
11 | ### A.
12 |
13 | * 간단하게 말하자면, [UIWindow](https://developer.apple.com/documentation/uikit/uiwindow) 는 앱의 뷰 계층 구조에서 최상단에 고정되어 위치하며, 앱의 화면 콘텐츠에 대한 컨테이너 역할을 합니다. 앱의 뷰, 뷰 컨트롤러와 함께 작동하여 화면에 표시되는 뷰 계층 구조에 터치 이벤트를 전달하고, 화면 방향 변경과 같은 변경 사항을 관리합니다.
14 |
15 | 
16 |
17 | UIWindow 는 화면에 보이는 콘텐츠를 제공하지 않습니다. 화면에 보이는 모든 콘텐츠는 앱의 스토리보드에서 구성하는 [rootViewController](https://developer.apple.com/documentation/uikit/uiwindow/1621581-rootviewcontroller) 에서 제공합니다. UIWindow 의 역할은 UIKit 에서 이벤트를 수신하고, 관련된 이벤트를 루트 뷰 컨트롤러 및 관련 뷰에 전달하는 것입니다.
18 |
19 | * 대부분의 iOS 앱에서는 하나의 윈도우만 만들고 사용합니다. 앱이 외부 디스플레이 사용을 지원한다면, 해당 외부 디스플레이에 콘텐츠를 표시하는 추가 윈도우를 만들 수 있습니다. 외부 디스플레이 지원 외에 앱 사용 중 전화 수신과 같은 다른 상황들은 일반적으로 시스템에 의해 윈도우가 생성됩니다.
20 |
21 | * 윈도우는 [application(_:didFinishLaunchingWithOptions:)](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622921-application), [scene(_:willConnectTo:options)](https://developer.apple.com/documentation/uikit/uiscenedelegate/3197914-scene) 와 같은 앱의 수명주기 초기에 생성됩니다. 스토리보드를 사용하면 자동으로 [window](https://developer.apple.com/documentation/uikit/uiwindowscenedelegate/3198093-window) 프로퍼티가 생성되어 keyWindow 로 설정되고 화면에 표시됩니다.
22 |
23 | * 만약 스토리보드를 사용하지 않는다면, 스토리보드가 자동으로 해주었던 동작을 앱의 수명주기 초기에 다음과 같이 직접 코드로 작성해주어야 합니다.
24 |
25 | **iOS 13 이상**
26 |
27 | ```swift
28 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
29 | var window: UIWindow?
30 |
31 | func scene(_ scene: UIScene,
32 | willConnectTo session: UISceneSession,
33 | options connectionOptions: UIScene.ConnectionOptions) {
34 | guard let windowScene = (scene as? UIWindowScene) else { return }
35 |
36 | let window = UIWindow(windowScene: windowScene)
37 | window.rootViewController = ViewController()
38 | self.window = window
39 | window.makeKeyAndVisible()
40 | }
41 | }
42 | ```
43 |
44 | **iOS 13 미만**
45 |
46 | ```swift
47 | @UIApplicationMain
48 | class AppDelegate: UIResponder, UIApplicationDelegate {
49 | var window: UIWindow?
50 |
51 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
52 | window = UIWindow()
53 | window?.rootViewController = ViewController()
54 | window?.makeKeyAndVisible()
55 |
56 | return true
57 | }
58 | }
59 | ```
60 |
61 | * 앱 수명주기 초기에 윈도우를 생성할 때를 제외하면 윈도우에 접근할 일이 거의 없지만, 몇몇 작업에서는 윈도우를 활용하기도 합니다. 현재 기기에 표시되는 윈도우를 기준으로 하는 좌푯값이 필요할 때나([Converting Coordinates in the View Hierarchy](https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingViews/CreatingViews.html#//apple_ref/doc/uid/TP40009503-CH5-SW40)), 윈도우의 상태 변경에 따라 특정한 작업을 해주어야 할 때([Monitoring Window Changes](https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingWindows/NaN#//apple_ref/doc/uid/TP40009503-CH4-SW13)) 와 같은 상황에 관련 메소드를 활용하여 윈도우와 관련된 작업을 수행합니다.
62 |
63 | ### 참고할 만한 비슷한 질문, 자료
64 |
65 | * [Windows and Screens](https://developer.apple.com/documentation/uikit/windows_and_screens)
66 | * [Multiple Display Programming Guide for iOS: Understanding Windows and Screens](https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/WindowAndScreenGuide/WindowScreenRolesinApp/WindowScreenRolesinApp.html#//apple_ref/doc/uid/TP40012555-CH4-SW8)
67 | * [View Programming Guide for iOS: Windows](https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingWindows/CreatingWindows.html#//apple_ref/doc/uid/TP40009503-CH4-SW13)
68 | * [What is the purpose of UIWindow?](https://stackoverflow.com/questions/18282311/what-is-the-purpose-of-uiwindow)
69 | * [How to Properly Remove Main.Storyboard(for iOS 13+)](https://ioscoachfrank.com/remove-main-storyboard.html)
70 | * [Cocoa Application Competencies for iOS: Responder object](https://developer.apple.com/library/archive/documentation/General/Conceptual/Devpedia-CocoaApp/Responder.html)
71 |
72 | -----
73 |
74 | ### Q.
75 |
76 | > ScrollView 에서 contentOffset 과 contentInset 이 무엇인가요?
77 |
78 | ScrollView 에서 contentOffset 과 contentInset 이 무엇인가요?
79 |
80 | [질문 바로가기](https://stackoverflow.com/questions/33286574/what-exactly-are-contentoffset-contentinset-of-scrollview)
81 |
82 | ### A.
83 |
84 | * [contentOffset](https://developer.apple.com/documentation/uikit/uiscrollview/1619404-contentoffset) 은 전체 콘텐츠 뷰 영역을 기준으로 현재 화면에 보이는 스크롤 뷰 영역의 위치 좌표를 나타낸 값입니다.
85 |
86 | 
87 |
88 | 왼쪽 그림에서 파란 테두리가 스크롤 뷰의 영역, 검정 점선 테두리가 스크롤뷰의 콘텐츠 영역입니다. 사용자가 스크롤을 하면 화면에 보이는 콘텐츠의 영역이 변경되므로 contentOffset 의 값도 변경됩니다. contentOffset 의 값을 코드로 변경하면(슬라이더) 스크롤 뷰가 스크롤 되는 것과 같은 동작을 합니다.
89 |
90 | * [contentInset](https://developer.apple.com/documentation/uikit/uiscrollview/1619406-contentinset) 은 스크롤 뷰 가장자리와 콘텐츠 영역의 가장자리 사이에 스크롤 가능한 공간(패딩)을 추가합니다.
91 |
92 | 
93 |
94 | 상단과 하단의 inset 값을 조절한 만큼 콘텐츠가 더 스크롤 되는 것(하늘색 영역)을 확인할 수 있습니다.
95 |
96 | * 만약 글만으로는 잘 이해가 되지 않는다면, contentInset 과 contentOffset 값의 변화에 따른 UIScrollView 의 변화를 한눈에 보기 쉽게 만든 [샘플 앱](https://github.com/TTOzzi/InsetVsOffset)이 있으니 참고하세요!
97 |
98 | ### 참고할 만한 비슷한 질문, 자료
99 |
100 | * [Scroll View Programming Guide for iOS](https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/UIScrollView_pg/CreatingBasicScrollViews/CreatingBasicScrollViews.html)
101 | * [What does contentOffset do in a UIScrollView?](https://stackoverflow.com/questions/3339798/what-does-contentoffset-do-in-a-uiscrollview)
102 | * [What exactly are contentOffset, ContentInset and ContentSize of a UIScrollView?](https://levelup.gitconnected.com/what-exactly-are-contentoffset-contentinset-and-contentsize-of-a-uiscrollview-960207c75b88)
103 | * [Understanding the contentOffset and contentInset properties of the UIScrollView class](https://fizzbuzzer.com/understanding-the-contentoffset-and-contentinset-properties-of-the-uiscrollview-class/)
104 |
105 | -----
106 |
107 | ### Q.
108 |
109 | > 오토레이아웃을 설정할 때의 margin 이 무엇인가요?
110 |
111 | 스토리보드에서 오토레이아웃을 설정할 때 `Constrain to margins` 라는 옵션이 있는데 이것의 의미가 무엇인가요?
112 |
113 | [질문 바로가기](https://stackoverflow.com/questions/25807545/what-is-constrain-to-margin-in-storyboard-in-xcode-6)
114 |
115 | ### A.
116 |
117 | * [layoutMargins](https://developer.apple.com/documentation/uikit/uiview/1622566-layoutmargins) 란 뷰에 내용을 배치할 때 사용할 기본 간격입니다.
118 |
119 | * 기본적으로 UIView 는 top, bottom, right, left, 네 가장자리에 8 포인트의 여백을 가집니다. 인터페이스 빌더의 `Editor` -> `Canvas` -> `Layout Rectangels` 를 활성화해주면 뷰들에 기본 margin 이 설정되어 있는 것을 볼 수 있습니다.
120 |
121 | 
122 |
123 | * UIView 를 상속받는 모든 뷰들은 각기 다른 기본 여백을 가지고 있습니다. 이 여백은 오토레이아웃 제약조건을 설정할 때 `Constrain to margins` 옵션을 체크해주거나, margin 에 직접 제약조건을 설정할 때에만 배치에 영향을 줍니다.
124 |
125 | 

126 |
127 | 뷰 내부의 라벨을 뷰의 좌측 상단에 붙이는 레이아웃을 `Constrain to margins` 옵션을 체크하고 설정한 모습입니다. top, leading 의 간격을 0씩 설정했음에도 불구하고 UIView 의 기본 margin 값인 8씩 띄워져 있는 것을 확인할 수 있습니다.
128 |
129 | 

130 |
131 | 같은 제약조건을 `Constrain to margins` 옵션을 체크하지 않고 설정한 모습입니다. UIView 의 margin 에 영향을 받지 않고 top, leading 의 간격이 0씩 설정된 것을 확인할 수 있습니다.
132 |
133 | * 뷰의 `layoutMargins` 프로퍼티나, 스토리보드에서 `Size inspector` 의 `Layout Margins` 옵션에서 뷰의 margin 값을 수정할 수 있습니다.
134 |
135 | 
136 |
137 | `Size inspector` 에서 상위 뷰의 `Layout Margins` 옵션을 `Default` 에서 `Fixed` 로 바꿔주고, Left 와 Top 만 30으로 설정해 준 모습입니다. 설정해 준 값 대로 margin 이 바뀐것을 확인할 수 있습니다.
138 |
139 | iOS 11 부터는 [directionalLayoutMargins](https://developer.apple.com/documentation/uikit/uiview/2865930-directionallayoutmargins) 를 지원하여 left, right 같은 고정된 방향이 아닌 leading, trailing 으로도 margin 값을 설정해줄 수 있습니다. `Size inspector` 에서도 `Language Directional` 옵션으로 설정할 수 있습니다.
140 |
141 | * [preservesSuperviewLayoutMargins](https://developer.apple.com/documentation/uikit/uiview/1622653-preservessuperviewlayoutmargins) 라는 옵션도 있는데, 이 옵션을 활성화하면 옵션을 활성화한 뷰가 자신의 콘텐츠를 배치할 때 자신의 부모 뷰의 margin 까지 계산하여 배치하게 됩니다. 간단한 예시로 살펴보겠습니다.
142 |
143 |
144 |
145 | top 30, left 30, bottom 8, right 8 의 여백을 가지는 주황색 뷰와 기본값 8의 여백을 가지는 노란색 뷰입니다. 분홍색 점선이 주황색 뷰의 `layoutMargin` 이고 빨간색 점선이 노란색 뷰의 `layoutMargin` 입니다.
146 |
147 | 노란색 뷰에 라벨을 추가한 뒤, `Constrain to margins` 옵션을 체크하고 top, left 를 0씩 설정해보겠습니다.
148 |
149 |
150 |
151 | 라벨이 노란색 뷰의 margin 에 맞춰서 배치된 것을 확인할 수 있습니다.
152 |
153 | 여기서 노란색 뷰의 `preservesSuperviewLayoutMargins` 옵션을 활성화하면,
154 |
155 |
156 |
157 | 노란색 뷰의 margin 이 부모 뷰인 주황색 뷰의 margin 에 영향을 받아 변경되는 것을 확인할 수 있습니다.
158 |
159 | * `LayoutMargin` 을 정확하게 이해하고 사용한다면, 라벨이나 버튼에 패딩을 만들어 주고 싶을 때나 스택뷰에서 각각 다른 간격을 설정해주고 싶을 때 등 여러 방면에서 유용하게 사용할 수 있습니다. 좀 더 구체적인 설명과 활용 사례가 있는 발표자료와 영상이 있으니 [알아두면 유용한 iOS의 LayoutMargins를 소개합니다!](https://academy.realm.io/kr/posts/ios-layoutmargins/) 도 꼭 참고해보세요!
160 |
161 | ### 참고할 만한 비슷한 질문, 자료
162 |
163 | * [Positioning Content Within Layout Margins](https://developer.apple.com/documentation/uikit/uiview/positioning_content_within_layout_margins)
164 |
--------------------------------------------------------------------------------
/contents/week-9.md:
--------------------------------------------------------------------------------
1 | # iOS 질문모음 - 9
2 |
3 | ### Q.
4 |
5 | > @objc 의 의미가 무엇인가요?
6 |
7 | @objc 키워드의 의미가 무엇인가요?
8 |
9 | [질문 바로가기](https://stackoverflow.com/questions/30795117/when-to-use-objc-in-swift)
10 |
11 | ### A.
12 |
13 | * @objc 는 Swift 로 작성된 코드에서 Objective-C 의 API 와의 호환을 위해 사용합니다. target-action 패턴(Selector)을 활용하는 [Timer](https://developer.apple.com/documentation/foundation/timer), [NotificationCenter](https://developer.apple.com/documentation/foundation/notificationcenter) 나, keyPath 를 활용하는 [KVO](https://developer.apple.com/documentation/swift/cocoa_design_patterns/using_key-value_observing_in_swift), [KVC](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueCoding/index.html) 와 같은 Objc 기반 API 는 Objc 런타임을 통해 제공됩니다. Swift 4 이전엔 컴파일러가 Objc 런타임에 노출할 프로퍼티와 메소드를 자동으로 추론해줬지만, 추론하는 시기의 불분명함, 같은 이름(오버로딩)으로 인한 의도치 않은 충돌, 바이너리 크기 증가와 성능 저하 등의 이유([SE-0160: Limiting @objc inferece](https://github.com/apple/swift-evolution/blob/master/proposals/0160-objc-inference.md))로 Swift 4 부터는 @objc 를 명시하여 Objc 런타임에 노출할 프로퍼티와 메소드를 컴파일러에게 알려줘야 합니다.
14 |
15 | * target-action 패턴의 #selector
16 |
17 | ```swift
18 | class ViewController: UIViewController {
19 | @IBOutlet weak var button: UIButton!
20 |
21 | override func viewDidLoad() {
22 | super.viewDidLoad()
23 | button.addTarget(self, action: #selector(tappedButton(_:)), for: .touchUpInside)
24 | }
25 |
26 | @objc func tappedButton(_ sender: UIButton?) {
27 | print("tapped button")
28 | }
29 | }
30 | ```
31 |
32 | #selector 에 전달하는 함수가 @objc 로 선언되어있지 않으면 컴파일 에러가 발생합니다.
33 |
34 | * #keyPath
35 |
36 | ```swift
37 | class Person: NSObject {
38 | @objc var name: String
39 | @objc var friends: [Person] = []
40 | @objc var bestFriend: Person? = nil
41 |
42 | init(name: String) {
43 | self.name = name
44 | }
45 | }
46 |
47 | let gabrielle = Person(name: "Gabrielle")
48 | let jim = Person(name: "Jim")
49 | let yuanyuan = Person(name: "Yuanyuan")
50 | gabrielle.friends = [jim, yuanyuan]
51 | gabrielle.bestFriend = yuanyuan
52 |
53 | #keyPath(Person.name)
54 | // "name"
55 | gabrielle.value(forKey: #keyPath(Person.name))
56 | // "Gabrielle"
57 | #keyPath(Person.bestFriend.name)
58 | // "bestFriend.name"
59 | gabrielle.value(forKeyPath: #keyPath(Person.bestFriend.name))
60 | // "Yuanyuan"
61 | #keyPath(Person.friends.name)
62 | // "friends.name"
63 | gabrielle.value(forKeyPath: #keyPath(Person.friends.name))
64 | // ["Yuanyuan", "Jim"]
65 | ```
66 |
67 | #keyPath 로 접근하는 프로퍼티를 @objc 로 선언해주었습니다.
68 |
69 | ```swift
70 | @objcMembers class Person: NSObject {
71 | var name: String
72 | var friends: [Person] = []
73 | var bestFriend: Person? = nil
74 |
75 | init(name: String) {
76 | self.name = name
77 | }
78 | }
79 | ```
80 |
81 | 클래스 전체를 Objc 런타임에 노출되어야 할 때 클래스를 @objcMembers 로 선언하여 내부의 모든 프로퍼티와 메소드를 @objc 로 선언한 것과 같은 효과를 줄 수도 있습니다. 하지만 클래스 전체를 포함하다 보니 Objc 런타임에 필요하지 않은 프로퍼티나 메소드까지 포함할 수 있어 바이너리 크기가 증가하고 성능이 저하될 수 있습니다. Objc 런타임의 기능을 많이 사용하는 라이브러리 등을 제외한 대부분 코드에서는 @objc 를 사용하는 것을 권장합니다.
82 |
83 | * @IBAction, @IBOutlet 등의 Interface Builder 와 관련된 접두사들이나 @NSManaged, @GKInspectable 등의 접두사들은 내부적으로 @objc 로 구현되어 있어 따로 @objc 를 명시해주지 않아도 관련 기능들을 사용할 수 있습니다.
84 |
85 | ```swift
86 | class ViewController: UIViewController {
87 | @IBOutlet weak var button: UIButton!
88 |
89 | override func viewDidLoad() {
90 | super.viewDidLoad()
91 | button.addTarget(self, action: #selector(tappedButton(_:)), for: .touchUpInside)
92 | }
93 |
94 | @IBAction func tappedButton(_ sender: UIButton?) {
95 | print("tapped button")
96 | }
97 | }
98 | ```
99 |
100 | @IBAction 으로 선언된 메소드도 #selector 에 전달할 수 있는것을 확인할 수 있습니다.
101 |
102 | ### 참고할 만한 비슷한 질문, 자료
103 |
104 | * [Using Objective-C Runtime Features in Swift](https://developer.apple.com/documentation/swift/using_objective-c_runtime_features_in_swift)
105 | * [Swift: Attributes](https://docs.swift.org/swift-book/ReferenceManual/Attributes.html#)
106 | * [How can I deal with @objc inference deprecation with #selector() in Swift 4?](https://stackoverflow.com/questions/44390378/how-can-i-deal-with-objc-inference-deprecation-with-selector-in-swift-4)
107 | * [@selector() in Swift?](https://stackoverflow.com/questions/24007650/selector-in-swift)
108 |
109 | -----
110 |
111 | ### Q.
112 |
113 | > 자기 자신의 프로퍼티나 메소드에 접근할 때 항상 self 를 붙여줘야 하나요?
114 |
115 | 클래스, 구조체, 열거형의 내부에서 자기 자신의 프로퍼티나 메소드에 접근할 때 self 를 명시해줄 수도, 생략할 수도 있는데요. self 를 계속 명시해주는 것과 self 를 꼭 필요할 때만 쓰는 것 중 어떤 방법이 더 나은가요?
116 |
117 | [질문 바로가기](https://stackoverflow.com/questions/24215578/when-should-i-access-properties-with-self-in-swift)
118 |
119 | ### A.
120 |
121 | * self 를 항상 명시한다면, 생략하는 것보다 명확하게 자기 자신의 프로퍼티나 메소드를 사용한다는 의도를 표현할 수 있습니다. 반대로, self 를 필요할 때만 명시하는 경우 코드가 간결해지고, 컴파일러에서 클로저 내부에서의 self 접근에 self 의 명시를 강제하기 때문에, 클로저 캡처로 인한 순환 참조가 발생할 가능성이 있는 부분이 눈에 띈다는 장점이 있습니다.
122 |
123 | * 다음은 깃헙에서 star 가 많은 순서대로 나열한 회사들의 스타일 가이드입니다.
124 |
125 |
126 |
127 | 이 중 [raywenderlich](https://github.com/raywenderlich/swift-style-guide#use-of-self), [github](https://github.com/github/swift-style-guide#only-explicitly-refer-to-self-when-required), [linkedin](https://github.com/linkedin/swift-style-guide#3-coding-style), [airbnb](https://github.com/airbnb/swift#style) 에서는 컴파일러에서 필요로 할 때만 self 를 명시하고 [eure](https://github.com/eure/swift-style-guide#all-instance-properties-and-functions-should-be-fully-qualified-with-self-including-within-closures), [StyleShare](https://github.com/StyleShare/swift-style-guide#%ED%81%B4%EB%9E%98%EC%8A%A4%EC%99%80-%EA%B5%AC%EC%A1%B0%EC%B2%B4) 에서는 self 를 항상 명시한다고 합니다.
128 |
129 | * 자료를 찾아보면서 self 를 명시해야 하느냐에 대한 논쟁이 굉장히 많았습니다. 실제로 (거절당했지만)[SE-0009: Require self for accessig istance members](https://github.com/apple/swift-evolution/blob/master/proposals/0009-require-self-for-accessing-instance-members.md) 와 같은 제안도 있었습니다. self 명시는 정해진 정답이 없습니다. 팀에 속해있다면 팀 내에서 컨벤션을 정하여 맞추면 되고, 혼자라면 스스로 기준을 두고 결정하여 통일성만 유지한다면 어떤 방법으로 작성하든 상관없다고 생각합니다.
130 |
131 | * **Swift 5.3** 부터는 클로저 내부에서 `[self]`, `[unowned self]` 와 같이 캡처 의도를 명확하게 선언하거나(`[weak self]` 제외), 순환 참조가 발생하지 않는 값 유형의 캡처에 대해선 self 의 생략이 가능합니다. 순환 참조가 발생할 가능성이 있는 경우에만 self 를 붙여야 한다는 컴파일 에러가 발생합니다. [SE-0269: Increase availability of implicit `self` in `@escaping` closures when reference cycles are unlikely to occur](https://github.com/apple/swift-evolution/blob/master/proposals/0269-implicit-self-explicit-capture.md) 를 참고하세요. 개인적인 생각이지만, Swift 의 업데이트 방향을 봤을 때 애플에서는 self 를 생략하는 것을 권장하는 것 같습니다.
132 |
133 | ### 참고할 만한 비슷한 질문, 자료
134 |
135 | * [How to Use Correctly 'self' Keyword in Swift](https://dmitripavlutin.com/how-to-use-correctly-self-keyword-in-swift/)
136 | * [Clean code: whe to use "self." in Swift, and when not to](http://thebugcode.github.io/when-to-self-in-swift-and-when-not-to-2/)
137 |
138 | -----
139 |
140 | ### Q.
141 |
142 | > 코드로 오토레이아웃을 설정하고 싶어요.
143 |
144 | 코드로 오토레이아웃을 어떻게 설정하나요?
145 |
146 | [질문 바로가기](https://stackoverflow.com/questions/26180822/how-to-add-constraints-programmatically-using-swift)
147 |
148 | ### A.
149 |
150 | * 코드로 오토레이아웃을 설정하는 방법에는 여러 가지가 있습니다. 아래 사진처럼 현재 화면에 보이는 뷰 중앙에 가로, 세로 길이가 100인 뷰를 추가하는 코드를 예시로 들어보겠습니다. 다음 예시들은 모두 같은 레이아웃을 설정하는 코드입니다.
151 |
152 |
153 |
154 | * 코드로 설정하는 레이아웃은 크게 제약조건을 생성하는 부분과 제약조건을 활성화하는 부분으로 나뉩니다. 제약조건을 먼저 생성하고 생성한 제약조건을 활성화하는 방식으로 레이아웃을 설정합니다.
155 |
156 | ```swift
157 | override func viewDidLoad() {
158 | super.viewDidLoad()
159 | let squareView = UIView()
160 | squareView.backgroundColor = .systemIndigo
161 | view.addSubview(squareView)
162 |
163 | // autoResizingMask 를 제약조건으로 변환하지 않겠다는 것을 명시
164 | squareView.translatesAutoresizingMaskIntoConstraints = false
165 |
166 | // 레이아웃 제약조건 생성 코드 작성
167 | ...
168 |
169 | // 레이아웃 제약조건 활성화 코드 작성
170 | ...
171 | }
172 | ```
173 |
174 | 코드로 오토레이아웃을 설정해 줄 땐 반드시 설정해줄 뷰의 [translatesAutoresizingMaskIntoConstraints](https://developer.apple.com/documentation/uikit/uiview/1622572-translatesautoresizingmaskintoco) 를 false 로 설정해주어야 합니다. 기본적으로 코드로 생성한 뷰는 true 를 기본값으로 가지며 인터페이스 빌더에 뷰를 추가하면 자동으로 false 로 설정되지만, 우리는 코드로 오토레이아웃을 설정해줄 것이기 때문에 직접 false 로 설정해주어야 합니다.
175 |
176 | * 제약조건 생성
177 |
178 | * [NSLayoutConstraint 의 이니셜라이저](https://developer.apple.com/documentation/uikit/nslayoutconstraint/1526954-init)를 활용한 제약조건 생성
179 |
180 | ```swift
181 | let horizontalConstraint = NSLayoutConstraint(item: squareView, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0)
182 | let verticalConstraint = NSLayoutConstraint(item: squareView, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1, constant: 0)
183 | let widthConstraint = NSLayoutConstraint(item: squareView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100)
184 | let heightConstraint = NSLayoutConstraint(item: squareView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 100)
185 | ```
186 |
187 | NSLayoutConstraint 의 이니셜라이저는 제약조건 방정식을 그대로 코드로 표현한 형태입니다. 각각의 인자를 식으로 변환하면 `view1.attr1 multiplier × view2.attr2 + c` 와 같습니다.
188 |
189 | * [NSLayoutAnchor](https://developer.apple.com/documentation/uikit/nslayoutanchor) 를 활용한 제약조건 생성
190 |
191 | ```swift
192 | let horizontalConstraint = squareView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
193 | let verticalConstraint = squareView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
194 | let widthConstraint = squareView.widthAnchor.constraint(equalToConstant: 100)
195 | let heightConstraint = squareView.heightAnchor.constraint(equalToConstant: 100)
196 | ```
197 |
198 | NSLayoutConstraint 보다 가독성이 좋고 코드가 간결합니다. 공식문서에서는 코드로 오토레이아웃을 설정할 때 NSLayoutConstraint 의 이니셜라이저보단 NSLayoutAnchor 를 권장합니다. 이 둘의 가장 큰 차이점은 잘못된 제약조건을 설정하였을 때 나타납니다.
199 |
200 | ```swift
201 | let horizontalConstraint = NSLayoutConstraint(item: squareView, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1, constant: 0)
202 | ```
203 |
204 | squareView 의 centerX 를 view 의 centerY 에 맞추라는 제약조건입니다. NSLayoutConstraint 를 사용했을 땐 위와 같이 잘못된 제약조건을 설정하면 런타임에 예외를 발생시킵니다.
205 |
206 | ```swift
207 | // Cannot convert value of type 'NSLayoutAnchor' to expected argument type 'NSLayoutAnchor' 컴파일 에러 발생
208 | let horizontalConstraint = squareView.centerXAnchor.constraint(equalTo: view.centerYAnchor)
209 | ```
210 |
211 | 반면에, NSLayoutAnchor 를 사용했을 땐 제약조건의 대상이 되는 NSLayoutAnchor 의 타입체크가 이루어져 컴파일 에러를 발생시켜 개발자에게 잘못된 제약조건을 설정한 것을 인지시켜줍니다(하지만 완벽하게 방지해주진 않습니다. 런타임에 충돌이 발생할 수 있습니다.).
212 |
213 | * 제약조건 활성화
214 |
215 | * UIView 의 [addConstraints(_:)](https://developer.apple.com/documentation/uikit/uiview/1622523-addconstraint) 를 활용한 제약조건 활성화
216 |
217 | ```swift
218 | view.addConstraints([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
219 | ```
220 |
221 | 공식문서에서는 iOS 8 이상의 환경에선 addCostraints(_:) 를 직접 호출하는 것보다 isActive 를 활용하라고 합니다.
222 |
223 | * [NSLayoutConstraint 의 isActive](https://developer.apple.com/documentation/uikit/nslayoutconstraint/1527000-isactive) 를 활용한 제약조건 활성화
224 |
225 | ```swift
226 | horizontalConstraint.isActive = true
227 | verticalConstraint.isActive = true
228 | widthConstraint.isActive = true
229 | heightConstraint.isActive = true
230 | ```
231 |
232 | Bool 값을 할당하여 제약조건을 활성화, 비활성화할 수 있습니다.
233 |
234 | * [NSLayoutConstraint 의 active(_:)](https://developer.apple.com/documentation/uikit/nslayoutconstraint/1526955-activate) 를 활용한 제약조건 활성화
235 |
236 | ```swift
237 | NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
238 | ```
239 |
240 | 한 번의 호출로 여러 개의 제약조건을 활성화할 수 있습니다. 각 제약조건의 isActive 를 true 로 설정하는 것과 같습니다.
241 |
242 | * Visual format 언어를 활용한 방법([constraints(withVisualFormat:options:metrics:views:)](https://developer.apple.com/documentation/uikit/nslayoutconstraint/1526944-constraints))
243 |
244 | ```swift
245 | class ViewController: UIViewController {
246 |
247 | override func viewDidLoad() {
248 | super.viewDidLoad()
249 | let squareView = UIView()
250 | squareView.backgroundColor = .systemIndigo
251 | view.addSubview(squareView)
252 |
253 | squareView.translatesAutoresizingMaskIntoConstraints = false
254 | // 레이아웃 제약조건 생성 코드
255 | let views = ["view": view!, "squareView": squareView]
256 | let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:[view]-(<=0)-[squareView(100)]", options: .alignAllCenterY, metrics: nil, views: views)
257 | let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:[view]-(<=0)-[squareView(100)]", options: .alignAllCenterX, metrics: nil, views: views)
258 | // 레이아웃 활성화 코드
259 | // UIView 의 addConstrains(_:) 를 활용한 방법
260 | view.addConstraints(horizontalConstraints)
261 | view.addConstraints(verticalConstraints)
262 | // NSLayoutConstraint 의 active(_:) 를 활용한 방법
263 | NSLayoutConstraint.activate(horizontalConstraints)
264 | NSLayoutConstraint.activate(verticalConstraints)
265 | }
266 | }
267 |
268 | ```
269 |
270 | 애플에서 제공하는 Visual Format 언어를 활용해 문자열 형식으로 레이아웃을 표현합니다. 간단한 표현식만으로 한번에 여러 개의 제약조건을 생성할 수 있다는 장점이 있습니다. 하지만 표현의 완전성보다 시각화에 초점을 둔 기능으로 가로, 세로 비율 설정과 같이 설정하지 못하는 제약조건이 있다는 한계가 있고, 컴파일러가 Visual Format 언어의 유효성을 검사하지 않아 잘못된 제약조건을 설정하면 런타임에 예외가 발생합니다. 한 문장으로 여러 개의 제약조건을 표현할 수 있어 반환 값이 위의 예시들과는 달리 배열입니다. 그래서 활성화하는 코드도 조금 차이가 있습니다. 자세한 문법은 [Auto Layout Guide: Visual Format Language](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage.html#//apple_ref/doc/uid/TP40010853-CH27-SW1) 에서 확인하실 수 있습니다.
271 |
272 | ### 참고할 만한 비슷한 질문, 자료
273 |
274 | * [Auto Layout Guide: Programmatically Creating Constraints](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html#//apple_ref/doc/uid/TP40010853-CH16-SW1)
275 |
--------------------------------------------------------------------------------