배경색이 변경되는 앱에 이어 이번엔 태양계 행성 정보를 알려주는 앱을 만들어 보겠습니다.
앱을 만들 땐 항상 앱 기획 과정이 필요합니다. 이번 앱은 어떻게 만들어볼까요? 제가 생각한 기능은 이렇습니다.
- 행성 이미지가 있고 이미지를 누르면 행성에 대한 정보를 알 수 있다.
- 옆으로 넘기면서 행성을 탐색할 수 있다.
- 애니메이션이 살짝 있으면 좋을 것 같다.
사실 앱 기획서를 보게 되면 이렇게 단순하지 않고 더 상세히 기술되어 있습니다. 하지만 간단한 앱을 만드는 만큼 기획도 간단하게 했습니다.
먼저 SolarSystem 프로젝트를 만들었습니다.
이번에도 Main.storyboard를 이용해 UI 작업을 할 것이고, 각각의 Interface에 적절한 ViewController를 할당할 것입니다.
프로젝트를 만들면 Main.storyboard에 하나의 Scene이 있고 그 Scene은 ViewController라는 class로 설정되어 있습니다.
그럼 class는 무엇일까요?
class를 알기 전에 먼저 객체 지향 프로그래밍에 대해 알면 좋습니다. 객체지향 프로그래밍은 프로그래밍 패러다임 중 하나입니다. 객체지향 프로그래밍에선 프로그램을 이루는 코드가 하나하나의 역할을 담당하는 객체로 이뤄져 있다고 생각합니다.
하나하나의 역할을 담당한다는 것이 무엇일까요? 예를 들어 물을 끓이는 프로그램을 만든다고 생각해보겠습니다. 물을 끓이기 위해선 가스레인지, 주전자가 필요합니다. 가스레인지는 불을 다루고 주전자는 물을 다루게 됩니다. 반대가 될 순 없죠. 이처럼 각각은 각자의 역할을 수행합니다. 여기서 역할을 담당하는 각각을 우리는 객체라고 부르며 그 객체를 만들기 위해 우리는 class라는 키워드를 사용합니다.
클래스는 이렇게 특정 역할을 하는 묶음으로 선언합니다.
클래스를 사용할 땐 소괄호를 사용해서 생성합니다. 생성은 생성자를 통해 이뤄지는데 아직 자세하게 다루진 않겠습니다.
이후 클래스에서 선언한 함수를 만들어 놓은 값을 통해 사용할 수 있습니다.
그렇다면 ViewController class는 어떤 역할을 하길래 class로 선언했을까요?
ViewController의 역할은 보이는 화면에 대한 전반적인 기능 담당입니다. 화면이 보이고 사라질 때까지 ViewController가 담당하게 됩니다.
클래스 이름을 변경해보겠습니다. SolarSystemViewController가 좋겠습니다. 파일 이름과 클래스의 이름이 같지 않아도 되지만 일반적으로 파일 이름과 클래스 이름이 같게 합니다.
여기서 끝이 아닙니다. Main.storyboard의 Scene에 설정된 ViewController도 변경해줘야 화면을 담당하는 ViewController가 변경된 것입니다.
앱을 실행시키면 빈 화면이 나오게 됩니다. 여기서 궁금한 점이 생기실 수 있습니다.
“왜 SolarSystemViewController가 첫 번째 화면이지?”
바로 Main.storyboard가 실행될 때 첫 번째 화면이 SolarSystemViewController이기 때문입니다.
Main.storyboard의 SolarSystemViewController Scnene을 보면 화살표가 설정되어 있습니다.
이 화살표가 의미하는 것은 Initial View Controller입니다. 즉, 어떤 스토리보드가 시작될 때 첫 번째 화면을 가리키고 있는 것입니다.
그럼 Main.storyboard가 왜 첫 번째 storyboard일까요?
바로 이곳에서 Main Interface (첫 번째 인터페이스)를 Main.storyboard로 설정했기 때문입니다.
그럼 다시 SolarSystemViewController로 이동해보겠습니다.
여기 viewDidLoad()라는 함수가 있습니다. 앞에 override와 함수 내부의 super.viewDidLoad()는 우선 신경 쓰지 않겠습니다.
좀 전에 ViewController는 화면이 보이고 사라질 때까지 담당한다고 설명했는데요, viewDidLoad도 그 역할을 수행하는 함수입니다. 앱에서 화면을 그리고 이제 사용자에게 보일 준비가 됐을 때 자동으로 viewDidLoad()를 수행합니다. 이 함수 내부에 print(“viewDidLoad”)를 작성하고 앱을 실행해보겠습니다.
아래 콘솔에 “viewDidLoad”가 출력되는 것을 확인할 수 있습니다.
이처럼 화면이 준비됐을 때 ViewController는 자동으로 viewDidLoad 함수를 호출하는 것을 알 수 있습니다.
viewDidLoad 뿐 아니라, ViewController는 View의 생명주기(Life Cycle)를 관리합니다.
뷰의 생명 주기와 관련된 함수들은 나중에 알아보도록 하겠습니다.
그럼 단순하게 OOViewController라고 class 이름을 설정했다고 View의 생명주기를 다룰 수 있을까요?
아닙니다. OOViewController 뒤에 붙어있는 :UIViewController를 주목해주세요.
Main.storyboard에서 SolarSystemViewController로 설정해줬던 것을 지워보면 UIViewController라는 문구가 보입니다. 즉, 이곳에는 UIViewController 타입만 들어올 수 있습니다. (Int, String처럼 class도 타입이 될 수 있습니다.)
UIViewController는 무엇일까요? SolarSystemViewController를 보면 코드 상단에 import UIKit이 있습니다. UIKit은 애플이 만든 Framework인데요, Framework은 특정 코드 집합으로 생각하면 좋을 것 같습니다. 여기서 UIKit은 UI와 관련된 class들의 모임입니다.
UIViewController는 화면 UI를 담당하기에 UIKit에 속해있습니다. 그리고 외부에서 만든 것이니 import 키워드를 붙여 특정 Framework를 사용하겠다고 선언한 것입니다.
그렇다면 OOViewController가 UIViewController랑 타입이 같아야 이 자리에 입력이 될 수 있겠죠?
이곳에선 상속이란 개념이 사용됩니다.
지구에 대한 설명을 담당하는 지구 클래스가 있다고 가정하겠습니다.
태양계 행성엔 지구만 있는 것이 아니라 여러 행성이 모여있죠.
이렇게 8개의 행성이 태양계를 이루고 있습니다.
만약 태양계 행성들의 모든 설명(description)을 출력하고 싶다면 어떻게 해야 할까요?
물론 하나하나의 class를 생성해서 함수를 사용할 수 있습니다. 그렇지만 코드를 많이 줄이는 방법이 있습니다.
바로 반복문을 사용하는 것인데요. 반복문에 대해서 먼저 알아보겠습니다.
1부터 100까지 출력해야 한다고 생각해보겠습니다. print(“1”), print(“2”).. 이렇게 출력하는 것은 조금 힘들 것 같습니다. 10000까지 출력해야 한다고 하면.. 정말 힘들 것 같습니다. 이렇게 무언가를 반복해서 처리해야 할 때 반복문을 사용하면 좋습니다.
반복문은 이런 형태로 사용합니다. 1~100까지 출력이라면… 을 사용해서 범위를 지정해주고 이름을 붙여 하나씩 중괄호 안에 사용이 가능합니다.
X…Y 이 X이상 Y이하였다면 X..<Y는 X이상 Y미만의 범위를 정할 때 사용합니다. 이렇게 범위를 정하면 이 예제에선 99까지 출력됩니다.
반복문을 사용하는 두 번째 방법으론 forEach라는 함수가 있습니다. 이 함수는 Swift가 제공하는 함수인데요, 범위에서 사용할 수 있습니다. 뒤에 변수 in 용법은 우선 넘어가도 좋습니다.
우리는 이전 글에서 범위를 다루는 어떤 것을 배운 적이 있습니다. 바로 배열입니다.
배열은 같은 타입으로 이루어진 모임이었던 것을 기억하시나요? 여기 색상을 담고 있는 문자열 배열이 있습니다. 배열에 있는 것을 하나하나 출력하고 싶다면 어떻게 해야 할까요?
이렇게 배열의 인덱스 범위를 사용해서 출력할 수 있습니다.
마찬가지로 이렇게 forEach를 사용해서 출력할 수 있습니다.
forEach를 사용하면 인덱스 접근이 아닌 하나하나의 color를 바로 사용할 수 있습니다. 인덱스 접근에 유의해야 하는 배열에선 forEach를 사용하는 것이 좋습니다.
그렇다면 행성들도 배열 안에 넣고 반복문을 사용해서 출력하면 코드량을 줄일 수 있을 것 같습니다.
배열에 값을 모아두기 위해선 큰 제약 조건이 있습니다. 바로 같은 타입이어야 한다는 점입니다.
이 8개의 행성은 클래스 이름이 다릅니다. 클래스 이름이 다르다는 의미는 타입이 다르다는 것과 같습니다.
그럼 어떻게 같은 타입으로 다룰 수 있을까요? 이런 경우 상속이라는 방법을 사용할 수 있습니다.
상속은 이런 문법으로 사용할 수 있는데요, 이제 Planet이라는 배열을 만들고 그 안에 행성들을 넣을 수 있게 됐습니다. 어떻게 이게 가능할까요?
바로 earth is planet이기 때문이죠. 상속 관계는 객체 간의 is 관계가 성립되는데요. 실제로 타입을 비교할 수 있는 is를 사용해서 확인이 가능합니다.
‘금성은 행성이다’는 말이 되지만 ‘행성은 금성이다’라고는 말할 수 없습니다.
그렇기 때문에 false가 출력됩니다.
이런 관계가 성립되기 때문에 Planet 배열에는 Planet을 상속받은 행성들이 들어갈 수 있는 것입니다.
자, 그럼 이제 배열을 순회하며 description 함수를 실행해보겠습니다.
확인해 보려고 했는데 description 함수가 없습니다. 이유는 Planet class엔 description 함수가 없기 때문입니다.
그럼 description 함수를 추가해보겠습니다.
추가하자마자 에러가 발생하는 것을 확인할 수 있습니다.
이유는 Planet과 다른 class가 상속 관계에 있기 때문입니다.
상속 관계에서 같은 이름의 함수를 사용하려면 override 키워드를 통해 함수를 재정의해줘야 합니다.
이제 배열을 순회하며 description 함수를 실행해보겠습니다.
이제 조금 이해가 되시나요? 상속을 사용하면 기본이 되는 클래스를 확장하거나 기능 재정의를 할 수 있습니다.
그렇다면 다시 SolarSystemViewController로 돌아가 보겠습니다.
이제 상속 관계가 눈에 보이시나요? viewDidLoad가 자동으로 호출되는 이유는 화면을 담당하는 클래스가 UIViewController이고 시스템은 내부적으로 UIViewController의 viewDidLoad() 함수를 호출한 것입니다. 그렇지만 우리가 OOViewController가 이 화면을 담당한다, 그리고 UIViewController를 상속한다고 했기 때문에 우리가 재정의한 viewDidLoad가 불리는 것입니다.
super가 궁금하실 수 있을 것 같습니다. super는 부모 클래스를 의미합니다. 상속 관계를 부모-자식 관계라고도 합니다. 여기선 부모 클래스가 UIViewController이기 때문에 super는 UIViewController를 의미하겠죠.
이렇게 override를 통해 기능을 확장할 땐 부모 클래스가 선언한 함수도 함께 불러주는 것이 일반적입니다.
우선, 이 정도로 이해하고 넘어가도 좋습니다.
설명하다 보니 중요한 내용이 많이 나왔습니다. 우선 이 글의 내용을 이해하고 앱을 만드는 것이 좋을 것 같습니다.
다음 글에서 이제 본격적으로 태양계 행성 정보 앱을 만들어 보겠습니다.
고맙습니다.
윤민섭님의 브런치에 게재한 글을 편집한 뒤 모비인사이드에서 한 번 더 소개합니다.