[iOS] 새로생긴 viewIsAppearing 그래서 언제쓰는건데?

Youth·2024년 2월 25일
2

TIL

목록 보기
16/21

주변에서도 WWDC는 iOS개발자라면 꾸준히봐야한다~라는 말도 자주듣고 솔직히 behind the scene이 들어간 영상들은 정말정말 중요한 정보와 개념들이 담겨져 있습니다. 사실 영어라서 진입장벽이 높다는점만빼면 어떤 개념을 공부하고 싶을때 공식문서 + WWDC를 0순위로 두고 보는게 맞는거같다는 생각을 요즘들어 하고있는것같습니다

하지만 영어실력이 부족한저는 다른분들이 잘 정리해주신 블로그글과 함께 WWDC영상과 공식문서를 병행하고있습니다ㅎ...

이번 포스팅 주제는 영상자체에 대한 내용이라기보다는 WWDC 2023 What’s new in UIKit라는 영상에서 소개된 새로운 개념중에서 흥미로운 주제가 있어서 아티클을 작성하게 되었습니다

새로운 ViewController 생명주기의 등장

👨🏻‍⚖️
Q. 뷰컨트롤러의 생명주기를 순서대로 말씀해주실수있나요?
💁🏻
A. viewDidLoad, viewWillAppear, viewDidAppear, viewWillDisappear, viewDidDisappear가 있습니다!
👨🏻‍⚖️
(허허 이 친구 새로운 개념이 도입되었는데 아직 모르나보군)

네...면접때 이런 상황이 발생하기 전에 공부하고 가면 좋을거같은 주제이기도합니다

WWDC23에서 소개가 되기전에는 보통 ViewController의 생명주기는 viewDidLoad, viewWillAppear, viewDidAppear, viewWillDisappear, viewDidDisappear가 맞았습니다

그런데 WWDC에서 iOS 13버전부터 적용되는 하나의 새로운 생명주기를 발표했습니다

바로 viewIsAppearing입니다
좀 새롭죠. 원래 생명주기는 did와 will을 써서 ~하기전, ~한후 이런느낌이 강했는데 is ~ing형으로 뭔가 진행중인듯한 느낌의 네이밍을 가진 생명주기가 도입되었습니다

그리고 네이밍에서 알수있듯이 viewIsAppearing은 viewWillAppear과 viewDidAppear사이의 생명주기이기도합니다

그러니까 누군가 생명주기에대해 물어본다면 이제는 viewWillAppearviewDidAppear사이에 viewIsAppearing을 포함해서 말해야 정답인거죠

아니 근데 WWDC23에 나왔으면 iOS버전이 너무 높은거아닌가요...?

라고 생각하실수도있지만 아무래도 viewcontroller의 생명주기는 iOS자체의 중요한 속성이다보니 iOS13부터 적용된다고 하네요 그러면 아마도 지금 개발하고있는 거의 대부분의 앱애서 바로사용가능한 생명주기일것같습니다

왜 하필 viewIsAppearing일까?

viewIsAppearing이 viewWillAppear과 viewDidAppear사이에 있다는말은 분명히 이 사이에 새로운 생명주기를 추가해야할 이유가있었을겁니다

모든 기술에는 등장배경이 명확할테니까요
기존에는 UI의 업데이트를 viewWillAppear에서 했습니다

그렇다면 viewIsAppearing이라는 생명주기가 등장하게된 이유를 추측해볼 수 있습니다.

기존에 UI업데이트를 viewWillAppear에서 했는데 좀 별로였나? viewWillAppear에서 업데이트하면 무슨 문제라도 있나??

viewWillAppear가 호출된 다음에 1과 2의 블럭에 있는 동작들이 실행되는데요

1번 블럭을 보면 View added to hierarchy라고 되어있고
2번 블럭을 보면 View laid out by superview, traits updated라고 되어있습니다

우선 view added to hierarchy는 앱의 window라는 화면에 실제로 viewController의 view가 add된다는 뜻입니다. 그리고 View laid out by superview, traits updated는 superview에 의해(window일까요 이게?) viewcontroller의 view의 배치가되고 traits가 update된다는 뜻입니다

trait에대해서 잠깐설명하고 넘어가면 공식문서에는 화면의 size나 display의 비율 그리고 layout의 방향등을 포함하는 개념이라고 합니다

그러면 위의 내용을 종합해봤을때
우리는 늘 UI의 업데이트를 viewWillAppear해서 했었는데 viewWillAppear가 불리는 시점에서는 window에 viewcontroller의 view가 올라가기도 전이고 화면의 크기는 어떤지 비율은 어떤지 방향은어떤지가 하나도 정해지지 않은 상태에서 UI를 업데이트 해왔던거죠

UI를 업데이트하려면 적어도 화면의 크기가 얼마인지 비율이 얼마인지가 중요하죠. 대부분 우리는 autolayout으로 UI의 layout을 잡으니까요. 꼭 autolayout이 아니더라도 화면자체의 속성이 정해지지 않은 상황에서 UI를 업데이트하면 우리가 constraints를 제대로 잡지않았을때 UI가 이상하게 보이는것처럼 어떤 문제가 생길지 모릅니다

그래서 그럼 UI를 업데이트할때 잠재적인 문제가 발생하지 않으려면 적어도 viewcontroller의 view가 window에는 올라가야하고 기본적으로 trait들이 결정되어있어야하는구나라는 생각이 들게됩니다

그래서 애플에서는 viewcontroller의 view가 hierarchy에 add되고 superview가 view의 배치를 끝내고 업데이트된 trait collections를 가지고 있는 시점중에 가장 빠른시점에 viewIsAppearing이라는 생명주기를 추가했습니다

viewIsAppearing에 대해서 애플은 view가 보일때 UI를 업데이트하기 최적의 장소라고 이야기합니다

근데 그냥 viewDidAppear에서 하면안되나요?

viewDidAppear의 경우엔 화면이 보이는 애니메이션인 transition animation이후에 호출되기때문에 유저가봤을때 UI의 업데이트가 늦는거처럼 보일수있기때문에 UI업데이트의 경우엔 viewIsAppear가 조금더 적당한 위치라고 이야기합니다

공식문서에도 viewWillAppear이랑 viewIsAppearing의 차이에대해서도 표로정리되어있는데요

애니메이션과 함께 추가할 수 있는 전환 코디네이터는 viewWillAppear시점에서만 사용가능하기때문에 전환애니메이션을 사용해야하면 viewWillAppear에서 사용해야한다고 합니다. 그리고 사실상 view의 layout이나 특성 계층구조가 필요없는 작업의 경우에도 viewWillAppear에서 사용하면되는데 예시로 notification center의 등록은 view와 관계없기때문에 해당 생명주기에서 등록해주는걸 권장하고 있습니다(등록 해제는 viewDidDisappear에서 하라고하네요~)

UI의 업데이트는 viewIsAppearing에서...?

사실 애플에서 신나게 이렇게 이야기를 해도 대체 어떨때 이걸써야되는거지...?라는 생각이들수있습니다
그래서 이번에는 제가 마주한 문제를 하나 가져와보도록하겠습니다

UITableView에서 reloadData는 의미상으로는 UI를 업데이트하는게 맞지만 업데이트라기보다는 아얘새로운 뷰를 그리는 동작입니다. 오히려 업데이트한다라는 의미에 가까운건 diffableDatasource를 썼을때 snapshot을 통해서 기존 데이터와 다른부분을 찾아서 업데이트해주는 동작이 애플에서 말하는UI의 업데이트에 가깝다고 할 수 있습니다(제가 말씀드릴 문제가 reloadData상황에서는 문제가 되지 않습니다)

이런간단한 기능이 있는 앱이있다고 해보겠습니다
A라는 viewController에서 1~8까지의 숫자를 표시해주는 cell이 있고 버튼을 누르면 navigation push로 B라는 viewController로 넘어가고 B에 버튼을 누르면 delegate로 A의 tableview의 cell을 하나씩 지우는 예시입니다

BViewController에서는 아래와같은 delegate변수를 가지고 있고

protocol updateDelegate: AnyObject {
    func remove()
}

AViewController가 대리자로 위임받아서 해당 메서드를 구현해줍니다

var datas = ["1", "2", "3", "4", "5", "6", "7", "8"]

extension AViewController: updateDelegate {
    func remove() {
        self.datas.remove(at: 0)
        applySnapshot(data: self.datas)
    }
}

BViewController에서 버튼을 누르면 AViewController가 메모리에서 해제되지는 않았기때문에 해당 remove라는 메서드가 실행될겁니다 datas에 0번째 값을 지워주고 남은데이터를 가지고 tableview의 UI를 업데이트시켜줍니다

그러면 대충 예상대로 UI가 업데이트되지만 터미널이 경고를 날려줍니다

경고창을 잘읽어보면 첫문장에서 왜 이런 경고를 보여주는지알수있습니다. UITableView이나 그 상위 뷰 중 하나가 윈도우에 추가되지 않은 상태에서 레이아웃을 갱신했을 때 발생하는 경고인것같습니다. 이런 상황에서는 뷰의 정확한 정보(예: 테이블 뷰의 바운드, 트레이트 컬렉션, 레이아웃 마진, 안전한 영역 여백 등) 없이 뷰 내부의 뷰들이 로드되고 레이아웃을 수행하게 될 수 있으며, 이로 인해 버그가 발생할 수 있습니다. 또한, 불필요한 레이아웃 패스로 성능 오버헤드가 발생할 수 있는거죠

결국은 메모리에는 남아있으나 viewcontroller의 view가 window에 올라가지도 않았는데 UI를 업데이트하면 안됩니다~하고 알려주는 경고입니다

뭔가 어디서 많이 들어본 내용이죠
아마 viewIsAppearing의 존재를 몰랐다면 저는 당연히 이 경고를 보고 viewWillAppear에서 UI를 업데이트시켰을겁니다

var datas = ["1", "2", "3", "4", "5", "6", "7", "8"]

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    applySnapshot(data: self.datas)
}

extension AViewController: updateDelegate {
    func remove() {
        self.datas.remove(at: 0)
    }
}

data를 remove하는거자체는 UI와 상관없으니까 그대로 두고(그게아니어도 이쪽에 두는게 맞습니다) UI를 업데이트시키는 applySnapshot메서드를 viewWillAppear에다 두면

이전에는 BViewController에서 버튼이 눌리는 순간 경고가떳다면 이번에는 다시 AViewController로 돌아오기위해 back버튼을 눌러서 viewWillAppear가 호출되는순간 같은 경고를 내보내줍니다

하지만 우리는 이제 viewIsAppearing이라는 생명주기에대해서 알고 이런 UI업데이트는 viewIsAppearing에서해야 예상치못한 버그나 성능저하가 없다는걸 알고있습니다

applySnapshot(data: self.datas)을 viewIsAppearing에 옮겨주니까 아무런 경고가 뜨지않는걸 확인할 수 있습니다

diffableDatasource처럼 UI가 데이터의 변경에따라 업데이트되는경우에 viewIsAppearing을 사용해야하는 경우가 생길수있을것같습니다

마무리

좋은 예시였는지는 모르겠지만 프로젝트를 하다가 저 경고를 보게되었고 경고를 읽어보니 이전에 공부했던 viewIsAppearing이 떠올라서 업데이트하는 시점을 viewIsAppearing으로 바꿨다가 해결이되어서 신기하기도하고 이런 내용과 예시는 공유하면 좋겠다라는생각이들어서 아티클로 작성하게 되었습니다

처음에는 diffableDatasource가 아니라 간단하게 reloadData를 통한 예시코드를 작성해봤는데 이게 reloadData를 하면 저 경고가 뜨지 않더군요... 왜일까 고민을해봤을때 업데이트보다는 아얘새로운 뷰를 그리는거라 그런게아닐까라는 생각이 들었습니다

아무래도 diffableDatasource의 경우에는 바뀐부분만 자연스럽게 바꿔주는 snapshot을 이용하기때문에 전체뷰를 새로그리는 reloadData랑 다르다고 생각했습니다(더 좋은 의견이있으시면 댓글로남겨주세요 ㅎㅎ...)

생명주기는 정말 기초적이고 중요한 개념이라고 생각했습니다. 처음에는 정말 생명주기에대한 공부를 하려고 구글링을하다가 난생처음보는 viewIsAppearing이 있길래 뭔가하고 보다보니 이렇게 아티클까지 쓰고있네요... 아무래도 순서가 좀 뒤바뀐것같지만 다음포스팅은 ViewController의 생명주기에대해서 deep dive해보는 글이될것같습니다:)

다음에도 더 좋은글로 찾아뵙겠습니다
그럼 20000~

24.02.25추가

Chatgpt가 말해주는 경고발생이유

gpt가 말하기를 레이아웃을 갱신했을때라고 이야기를하는데 snapshot의 경우엔 기존과 바뀐부분의 차이만큼 레이아웃의 변화를 주는방식이고 reload는 뷰를 갱신한다기보다는 아얘새로그려주는 동작이라 이런 차이가난거라고 생각을 했습니다

그래서 왜 reloadData를 했을때는 경고가 발생하지 않았을지 물어보니까 아래와같이 답변을 해줬습니다

reloadData를 호출하면 테이블 뷰의 데이터를 새로 고쳐서 다시 로드하게 됩니다. 이 때 레이아웃이나 표시와 관련된 변경사항들은 뷰의 상태가 아닌 테이블 뷰 내부에서 처리되므로 위에서 언급한 경고가 나타나지 않을 수 있습니다.

reloadData의 경우엔 tableview내부에서 처리가되는거라 window에 올라간지여부와 상관없을수있다라는 답변이었네요...뭔가 이해는 잘안되지만 말이죠...

UITableViewDiffableDataSource를 사용하면 reloadData()를 호출하지 않고도 테이블 뷰를 업데이트할 수 있지만, 여전히 데이터를 변경하는 동안에는 해당 경고가 발생할 수 있습니다. 이는 데이터를 변경할 때 테이블 뷰의 레이아웃이나 표시와 관련된 변경사항이 발생할 수 있기 때문입니다. 데이터를 변경할 때는 테이블 뷰의 레이아웃이나 표시와 관련된 변경사항이 올바르게 처리되려면 테이블 뷰가 윈도우에 추가되어 있어야 합니다. 그렇지 않으면 경고가 발생할 수 있습니다.

결론적으로 gpt는 reloadData는 tableview내부에서 동작하는 방식이라 window에 올라갔는지 여부와 상관이없지만 diffabledatasource의 경우엔 데이터변경에따른 UI의 갱신이기때문에 window에 올라갔는지 여부가 중요할수있다는 말인것같습니다..

결국은 reloadData와 diffable의 snapshot을 통해 UI가 바뀌는 메커니즘이 달라서 발생한 차이인것같습니다(내부적으로는 코드를 모르니까 모르지만요)

그냥 diffable을 쓸때 snapshot을 업데이트해주는 타이밍은 viewIsAppearing을 하는게 조금이라도 문제가 발생할 가능성이 적다~정도로 넘어가면될것같습니다 ㅎㅎ

profile
AppleDeveloperAcademy@POSTECH 1기 수료, SOPT 32기 iOS파트 수료

7개의 댓글

comment-user-thumbnail
2024년 2월 27일

reloadData랑 snapshot 차이는 아마 말씀해주신 게 맞는 것 같습니다

viewIsAppearing의 진가는 CGRect로 직접 계산해서 프레임을 구성하는 UI에 있지 않을까 생각해요

AutoLayout으로 하면 View의 사이즈가 정해지고 알아서 세팅이 되잖아요?
만약 AutoLayout 기반으로 배치된 컴포넌트의 CGRect를 받아서 추가적인 UI 작업을 해야한다면
CGRect 위치 받아오는 메서드가 비용이 꽤나 든다고 WWDC에서 그러더라고요 (그래서 자주 쓰지 말라고)

이전까지 직접 계산한 CGRect로 UI를 구성한다면 viewDidAppear에다가 넣어야 하고
뷰가 뜬 다음에 계산해서 그리니까 사용자가 이상하다고 느낄지도 모르는데
이걸 viewIsAppearing에 넣으면 사용자에게 보이기 이전에 계산 다해서 그려진 상태에서 사용자에게 보여줄 수 있지 않을까 싶습니다

직접 테스트한 건 아니라 그냥 뇌피셜입니다 ㅋㅋ

1개의 답글
comment-user-thumbnail
2024년 3월 1일

우와! 작성해주신 글 내용 전개가 그냥 술술 읽히는 것 같아요!

어떤 생각의 흐름으로 작성하신건지 쉽게 따라갈 수 있었던 것 같아요! 게다가 저도 몰랐었던 기본적인 메서드인 viewWillAppear가 호출되는 시점에 뷰의 라이프 사이클 상태에 대해서 새로 정확하게 알 수 있었던 계기가 되었던 것 같습니다.

그리고 위 댓글을 작성해주신 shintwl님께서 언급해주신대로 viewIsAppearing 메서드를 가장 적합한 용례는 뷰의 위치와 크기가 정해진 이후 시점이 가장 적절한 것으로 생각되어 CGRect를 받아서 추가적인 UI 작업을 해야할 때가 가장 적절한 시점 중 한 곳일 것 같습니다.

다시한번 좋은 글 너무 감사합니다!

1개의 답글
comment-user-thumbnail
2024년 3월 2일

다양한 상황들과 파트별로 나눠주신 덕분에 완전 쉽게 읽고 이해할 수 있었네요!
더군다나 중요한 시점 관련 글이어서 그런지 차근차근 읽게 되었습니다~
좋은 글 감사드려요! 🙌🏻

적어주신 것처럼 viewWillAppear 이후에 호출되는 시점이다보니 큰 차이는 없어보였지만
viewIsAppearing은 호출되는 횟수가 다르다는 점이 좀 신기했던 거 같아요!
viewWillAppear은 여러번 호출이 되는 경향이 있지만 view가 담고 있는 컴포넌트 등
명확한 값이 없는 반면, viewIsAppearing은 값을 가지고 있고 한번만 호출된다는게 뭔가 독특해보인다랄까요..?

1개의 답글