화면전환은 어떻게 되는 걸까?

뷰 컨트롤러가 하는 거임

mvc 패턴에서 뷰 컨트롤러가 하는 기능(뷰의 동작을 제어)만 생각 해 봐도 쉽게 유추할 수 있다.

물론 나는 쉽게 유추하지 못하고 지금껏 뷰 객체 자체에 난리를 쳐 놓았다. 하하… 뷰 컨트롤러가 한다는 것도 사실 아직 정확히 구분이 되진 않지만, 결론적으로 다른 화면으로 이동하고자 할 때, 현재 화면에 표시된 뷰 컨트롤러에 의해 다른 뷰 컨트롤러가 호출된다는 것을 알게됐다.

뷰 컨트롤러가 아닌 다른 방식으로도 화면 전환이 된다. 뷰가 덮어씌워지기도 하고, 화면 전환용 네비게이션 컨트롤러나 세그웨이를 사용하기도 한다. 특수한 경우, 혹은 제한적으로 사용되는 듯 싶기에 우선 뷰 컨트롤러를 이용해 서로 다른 뷰를 호출하는 방식에 익숙해져야겠다.

화면전환 하는 방법

iOS에서 화면전환을 하는 방법엔 대표적으로 네 가지가 있다고 한다.

  1. 뷰 컨트롤러의 뷰 위에 다른 뷰를 덮어씌우기
  2. 뷰 컨트롤러에서 다른 뷰 컨트롤러를 호출하기
  3. 네비게이션 컨트롤러를 사용하기
  4. 화면 전환용 객체 세그웨이 사용하기

가장 일반적으로는 2번, 뷰 컨트롤러끼리의 전환을 사용하는 것 같다. 여기서 뷰들은 전환될때마다 삭제되고 생성되는 것이 아니라, 이전 화면과 현재 화면간에 참조가 이루어져 화면에 표시할 뷰만 남게 된다.

참조?

C 언어에서의 포인터처럼, 전환되는 뷰 간에는 참조가 일어난다.

A 뷰 컨트롤러와 B 뷰 컨트롤러가 있다고 가정할 때, A -> B 로 화면 전환을 할 때, A 뷰가 자기 자신을 삭제하는 대신, B 뷰 컨트롤러에게 ‘나 이제 없어져야됨’ 이라고 말을 하면, A 뷰를 참조하는 B 뷰 컨트롤러는 A 뷰를 밀어내고 B 뷰를 화면에 띄운다. 동일한 방식으로 B->A로의 화면 전환도 이루어진다. 이렇게 밀려난 뷰 컨트롤러 객체는 운영체제에 의해 해제된다.

그냥 단순하게 뷰가 바뀔 때마다 나 자신을 삭제하고 다른 애가 올라가면 되는 거 아닌가? 싶었는데, 그렇게 되면 계속 뷰를 새로 렌더링해야 하고, 현재 화면과 이동한 화면 간의 관계가 아예 없는 독립적인 형태라면 계속 ‘이전 뷰’에 대한 정보를 따로 저장하고 있어야 하는 점 때문에 참조 형식으로 구성된 것 같다.

화면전환의 특성

네비게이션 컨트롤러나 세그웨이 등과 같이 특정한 화면 전환 방식을 통해 화면을 전환하는 것과, 뷰 컨트롤러를 이용해 화면을 전환하는 것은 그 방법이 다르다. 당연한 말이지만.

이 뿐만 아니라, 각각의 방법으로 화면을 전환하고, 이전 화면으로 돌아갈 때도 그 방법이 다르다. 이게 무슨 말일까?

뷰 전환

그냥 뷰 컨트롤러를 호출하면 전환이 되나? 그렇지 않다. present(_:animated:) 메서드를 통해 뷰가 화면에 보여진다.

present 메서드는 UIViewController 클래스에 정의되어있고, 모든 뷰 컨트롤러는 UIViewController를 상속받기 때문에 해당 메서드를 사용할 수 있다.

present 메서드는 기본적으로 두 가지 인자를 받는다. 첫 번째 인자로 바뀔 뷰 컨트롤러 인스턴스가 들어오고, 두 번째 인자로 애니메이션을 줄 것인지에 대한 참/거짓 값이 들어온다.

여기서 세 번째 인자를 추가할 수 있는데, 화면 전환이 이루어졌을 때 실행하고자 하는 액션을 넣어 줄 수 있다. 이 때는 present(_:animated:completion:) 메서드를 사용한다.

화면 전환은 비동기 방식으로 처리된다.

비동기 방식? 프로그래밍 공부를 하면서 굉장히 많이 본 친구지만 제대로 알아본 적이 없는 친구기도 하다.

비동기 방식(Asynchronous)은 말 그대로 동시에 일어나지 않는 방식이다. 진행되고 있는 것에 대해 끝날 때까지 기다리지 않고, 다음 작업을 바로 수행하게 된다. 노드 사이의 작업을 맞추지 않아도 되므로 하나의 작업에 대한 결과가 나오기 전, 남는 시간동안 다른 작업을 할 수 있으므로 자원을 효율적으로 이용할 수 있다.

이와 달리 동기 방식(Synchronous)은 동시에 일어나는 방식으로, 요청에 대한 결과가 무조건 나와야 한다. 즉, 진행되고 있는 것이 끝날 때까지 기다리는 것이다. 결과가 도출될 때까지 기다려야 한다는 단점이 있지만, 비동기 방식에 비해 훨씬 간단하고 직관적이다.

화면 전환은 비동기 방식으로 처리된다. 그렇기 때문에, present 메서드를 사용하고 바로 그 다음 줄에 이어서 화면이 전환된 후에 일어나길 원하는 동작을 쓰면 내가 의도한 바와 다르게 작동할 수 있다. 즉, 화면 전환이 완료되는 시점과, 화면 전환이 완료된 후 일어날 액션의 시점이 역전될 수 있다.

이런 이유로, present의 세 번째 인자로 화면 전환이 완료됐을 때 진행되길 바라는 동작을 전달하는 것이다.

되돌아가기

앞서, 화면을 전환할 때와 되돌아갈 때의 방식이 다르다고 했다. 말 그대로, 사용하는 메서드가 다르다.

화면 전환을 할 때 사용했던 메서드가 present 였다면, 이전 화면으로 돌아갈 땐 dismiss 메서드를 사용한다.

dismiss(animated:)는 이전 화면으로 돌아가는 동작을 하므로, 뷰 컨트롤러 인스턴스를 받지 않는다. (이게 다 참조되어있다는 증거?!)

이전 화면으로 되돌아가는 것 역시 화면 전환이므로, 비동기 방식이다. 즉, dismiss(animated:completion:) 메서드를 사용해 두 번째 인자로 화면 전환이 완료됐을 때 하고자 하는 액션을 추가할 수 있다.

present, dismiss의 각 세 번째, 두 번째 인자로 들어가는 completion은 함수일 수도 있고, 클로저일 수도 있다.

여기서 잠깐!

앞서, 참조에 대해 이야기 할 때 뷰 컨트롤러는 화면 전환이 될 때 자기 자신을 없애는 대신 참조된 다른 뷰 컨트롤러에 의해 없어진다고 이야기했다. 그렇다면, dismiss 메서드를 사용할 때도 이 논리를 따라야 할 것이다.

self.presentingViewController?.dismiss(animated:)

와 같은 방식으로 메소드를 호출해야 한다. 단순히 self가 아닌 것에 유의하자.

여기서 잠깐 2!

그럼, dismiss를 쓰는 대신 present를 쓰면 안 되나?

일단 dismiss가 있는데 present를 쓸 필요는 없어보인다.

조금 더 합리적인 이유를 들자면, 사용되지 않는 메모리는 정리하는 것이 효율적인 메모리 이용, 효과적인 프로그램 실행에 도움이 된다. 그런데 present 메서드를 사용해서 계속 화면을 쌓아 올린다면, 중복된 화면이 해제되지 않고 메모리를 계속 점유하는 상황이 발생한다.

그러므로 중복된 화면 없이 운용할 수 있도록 나타낼 땐 present, 이전 화면으로 돌아갈 땐 dismiss를 순서에 맞게 잘 쓰도록 하자.

결론

뷰 컨트롤러에 의해 화면이 바뀐다. (물론 다른 방법도 있지만 대표적으로)

전환되는 화면 사이엔 참조 관계가 생기며, 화면이 전환되고 이전 화면으로 되돌아가는 방법은 다르다. present, dismiss.

참고

꼼꼼한 재은 씨의 Swift: 기본편 (이재은, 루비페이퍼)

잘못된 점이나 추가될 부분이 있다면 언제든 말해주세요!

댓글남기기