klioop for iOS

SwiftUI, 선언적 뷰의 의미 본문

SwiftUI

SwiftUI, 선언적 뷰의 의미

klioop2@gmail.com 2021. 5. 17. 18:21

https://developer.apple.com/videos/play/wwdc2019/216/

2019 WWDC SwiftUI Essetionals 의 일부를 정리해봤습니다.

맥락상 해석 하기 어려운 부분이나 모르는 부분은 원문의 일부를 그대로 가져왔습니다.

 

SwiftUI 에서 커스텀 뷰를 View protocol 을 따르는 struct 으로 만드는 이유를 살펴봅시다.

 

UIKit 에서 view 는 UIView 를 상속받는 클래스 객체 였습니다.

그래서 UIKit 의 custom view 는 alpha 와 backgroundColor 같이

UIView superclass 에서 정해진, view 가 가져야 하는 여러 속성들을 상속 받아서

그대로 저장하고 있습니다. (UIView defines storage for commom view properties like alpha and backgroundColor)

 

SwiftUI 에서는 어떨까요?  SwiftUI 에서는 뷰가 가지는 여러 속성들을 modifier 로 분리합니다.

각각의 modifier 는 자신이 꾸미는 뷰를 바탕으로 새로운 뷰를 만들죠.

그리고 이는 뷰의 여러 속성들을 저장하는 공간(storage)이 뷰 hierarchy (or view tree) 로 분산된다는 것을 의미합니다. 

UIKit view 처럼 각각의 뷰가 기본적으로 모든 공통 속성들을 저장하고 있는 것과는 다릅니다.

 

다시 말하면 SwiftUI 에서 뷰는 매우 가볍습니다!

각각의 뷰가 필요한 속성들만 저장하는 공간을 가지면 되니까요.

 

이러한 방식으로 뷰를 만드려면 뷰는 클래스가 아니라, 그저 프로토콜인 것이 훨씬 말이 됩니다(a lot makes sense). 

왜냐하면 각 뷰는 모든 뷰가 공통적으로 가져야 하는 속성들을 저장하는 템플릿 역할을 더 이상 수행하지 않아도 되기 때문입니다!

 

그렇다면 뷰 프로토콜은 정확히 무엇을 할까요?

 

먼저 뷰의 개념적인 정의를 다시 떠올려 볼까요? 

뷰는 UI 의 한 조각입니다(View defines a piece of UI). 그리고 우리는 작은 조각 뷰들로 더 큰 뷰를 만듭니다(compose together smaller views to build bigger views) .

그리고 이것이 뷰 프로토콜이 하는 일의 전부입니다!

 

뷰 프로토콜은 뷰 hierarchy 의 일부를 정의합니다.

그리고 그 뷰에 이름을 줍니다(struct 으로 만드는 커스텀 뷰의 이름인듯?). 

그것으로 우리는 더 큰 뷰를 구성하거나 해당 뷰를 앱의 다른 부분에서 재사용 할 수도 있습니다.

 

또한, 각 커스텀 뷰(e.g. struct nameOfView: View { var body: some view { } } )

그저 다른 뷰들을 자신의 body 속성으로 감싸는 뷰 입니다(encapsulation).

다른 뷰들의 contents 를 나타내면서 말이죠.

이 커스텀 뷰를 만드는데 필요한 모든 input 들은 그것의 속성으로 정의됩니다. 

 

정리하자면 뷰 프로토콜은 그저 하나의 body 속성을 정의하는 것입니다. 

그리고 body 는 다른 종류의 뷰를 반환하구요.

 

여기에서 한 가지 의문점이 듭니다.

이거 일종의 재귀(recursive) 아니야? 🤔

커스텀 뷰를 만들었는데 그것의 body 는 다른 뷰를 반환하면 다시 다른 뷰의 body 가 있을 거고

그 body 는 또 다른 뷰를 반환 할 수도 있는데..

어떻게 끝나는 거지??

 

영원히 지속되지 않고 끝나는 이유가 있습니다. 

그것은 바로 SwiftUI 가 많은 원시(primitive) 뷰들을 제공하기 때문인데요.

 

원시 뷰는 다음을 만족합니다.

 

  1. 원시 뷰는 그들 고유의 어떠한 contents 도 가지지 않는다.
    (다른 뷰들의 contents 를 보여 줄 수 있는 body 같은 속성을 가지지 않는다는 의미 같습니다.)
  2. 원시 뷰는 다른 뷰들의 기반이 되는 atomic building blocks 이다.

Text 뷰가 이에 해당합니다. Image 뷰도 원시 뷰 입니다.

drawing 을 위한 Color 와 Shape 도 원시 뷰 입니다.

layout 원시 뷰인 Spacer 도 있습니다! 

(아, 그러면 커스텀 뷰 body 에서 또 다른 커스텀 뷰를 계속해서 호출해도

마지막 커스텀 뷰 body 에 원시 뷰들만 있다면 

더 이상 지속되지 않겠네요)

 

여기까지 우리는 SwiftUI 에서 커스텀 뷰가 왜 class 가 아닌 struct 으로 정의되는지 이해했습니다.

자, 그러면 SwiftUI 에서 뷰가 어떻게 선언적으로 정의될 수 있는지 알아봅시다.

 

뷰가 선언적으로 정의된다는 것은 SwiftUI 의 뷰는

더 이상 persistent objects that we update over time using imperative event-based code 가 아니라는 의미 입니다.

(UIKit 에서 새로운 data 가 추가되거나 삭제될 때 마다 tableView 를 reload 해줘야 하는 맥락 같은데 정확한 의미는 모르겠네요ㅜㅜ )

 

대신에, SwiftUI 뷰는 그것의 input 들의 함수로써 선언적으로 정의됩니다.  

따라서 뷰의 input 들 중 하나라도 변하면, SwiftUI 는 해당 뷰의 body 속성을 다시 호출합니다. 

그리고 바뀐 input 을 반영한 뷰를 가져옵니다(call body property again to fetch an updated version of our view).

(아하, 마치 함수가 input 값에 따라 다른 결과값을 반환 하는 것과 같네요!

그래서 함수를 선언하듯 뷰도 일종의 함수로써 선언해서 만든다고 이해하면 될 것 같아요😌)

 

이렇게 할 수 있는 이유는 우리가 직접 persistent render state 를 관리하지 않아도 되기 때문입니다.

우리는 그저 body 안의 데이터 값을 뷰가 렌더링에 이용할 수 있도록 계속해서 만들어주기만 하면 됩니다. 

그러면 SwiftUI는 우리를 대신해서 바뀌어야 하는 뷰의 부분을 알아서 그려줄 것 입니다(You can let SwiftUI generate the necessary changes between those two versions on your behalf.).

 

선언적으로 작성한 코드에서는 새로운 wrpper 뷰를 추가적으로 만드는 일은 사실상 비용이 들지 않습니다.

뷰 전문가 SiwftUI 가 알아서 최적화하기 때문입니다. 

 

그러므로 더 이상 뷰를 위한 코드를 잘 조직화 하는 것과 최고의 성능 사이에서 고민하지 마세요.