iOS - 꼼꼼한 재은씨 Swift 기본편(1_앱의 구조 및 프레임워크, 뷰컨트롤러)

이한솔·2023년 11월 15일
0

iOS 앱개발 🍏

목록 보기
26/49

텍스트로 꼼꼼하게 읽어야 직성에 풀리는 지독한 문과인 나는 iOS도 끝내버리겠어!! 라는 각오로 책을 샀다.
정리를 안해두면 나중에 많이 생각이 안나기 때문에 내가 보기 위해 편하게 개념을 정리해두려고 한다.
(하지만 절대 안끝난다는 것을 알고 있다. 그 점이 좋아서 시작한 공부지만 가끔 눈물이 난다.. 😂)



프로젝트 구성과 스토리보드

.swift 확장자로 이루어진 클래스 파일

앱의 소스 코드를 구성하는 역할을 한다. 프로젝트를 생성하면 세 개의 클래스 파일이 기본으로 생성된다. 그 중 두개는 앱의 전반적인 생명 주기와 앱 수명 주기 이벤트를 관리하는 AppDelegate.swift와 iOS 13 이상에서 다중 창 및 다중 씬 환경에서 앱의 각각의 씬을 관리하는 SceneDelegate.swift 클래스이다.
나머지 한 개는 ViewController.swift 파일로 뷰컨트롤러를 구현한 클래스이다.

.storyboard 확장자로 이루어진 스토리보드 파일

유저 인터페이스를 종합적으로 구현하는 역할을 한다. Main.storyboard 파일은 앱의 사용자 인터페이스 설계를 담당하고 LaunchScreen.storyboard 파일은 앱을 실행하면 처음 나타나는 시작 화면(스플래시)를 구성하는데 사용된다.
스토리보드 파일은 기존의 nib이나 안드로이드 xml 방식 화면 구성과 달리, 앱에 사용되는 여러 화면을 하나의 파일에 모아 설계할 수 있도록 지원하는 UI 설계용 파일 형식이다.

💡 스토리보드를 사용하는 이유는?

앱의 화면 전체와 화면 사이 관계를 쉽게 파악할 수 있고, 세그웨이를 통해 화면 사이의 전환을 손쉽게 처리할 수 있다. 또한 실행 시 앱을 초기화하기 위해 필요한 여러가지 노력을 간단하게 줄여준다.



앱의 구조

앱은 우리가 작성하는 커스텀 코드와 시스템 프레임워크 사이에서 매우 복잡한 상호작용을 한다. 시스템 프레임워크는 iOS 기반의 앱이 실행되는 데 필요한 기반 환경을 제공하고 개발자는 커스텀 코드를 제공하여 원하는 기능과 앱의 형태를 구현한다.

엔트리 포인트와 앱의 초기화 과정

C 언어에 뿌리를 둔 모든 애플리케이션은 main() 함수로부터 시작된다. 이를 엔트리포인트(시작 진입점)이라고 한다.
오브젝티브-C 역시 C 언어에 기반하고 있기 때문에 iOS 앱도 main() 함수로부터 시작된다. 대신 Xcode 프로젝트를 생성하면 main() 함수가 자동으로 만들어지는데 여기에는 iOS 앱이 실행될 때 처리해야 할 내용이 작성되어 있기 때문에 직접 main() 함수를 건드릴 필요가 전혀 없다

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
      return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
      
      }
}

main() 함수가 하는 일은 실행 시 시스템으로부터 전달받은 두 개의 인자값과 AppDelegate 클래스를 이용해 UIApplicationMain() 함수를 호출하고, 그 결과로 UIApplication 객체를 반환한다. 생성된 UIApplication 객체는 UIKit 프레임워크에 속해있으므로 이후 앱 제어권은 UIKit 프레임워크로 이관된다.

main() 함수가 C 기반 애플리케이션의 엔트리 포인트라면 UIApplicationMain() 함수는 그 중에서도 iOS 앱에 속하는 부분의 엔트리 포인트라고 할 수 있다. 앱의 핵심 객체를 생성하는 프로세스를 핸들링하고, 스토리보드 파일로부터 앱의 유저 인터페이스를 읽어 들이고, 작성한 커스텀 코드를 호출해 줌으로써 앱 생성 초기에 필요한 설정을 구현할 수 있게 해준다. 이벤트를 입력받기 위한 이벤트 루프를 실행시키기도 한다.

UIApplicationMain() 함수가 생성하는 UIApplication 객체는 앱의 본체라고 할 수 있는 객체로 사실상 앱 그자체를 의미한다. 커스텀 코드나 객체들, 앱의 기능이라고 생각하는 모든 것들은 다 UIApplication에 포함되어 있는 하위 객체이다. 모바일 디바이스에 설치된 앱을 실행하면 초기 구동 과정을 거쳐 앱 프로세스가 메모리에 등록되는데, 이때의 앱 프로세스가 곧 UIApplication 객체라고 보아도 무방하다.
UIApplication 객체의 역할은 매우 다양하다. 이벤트 루프나 다른 높은 수준의 앱 동작을 관리할 뿐만 아니라 푸시 알림과 같은 특수한 이벤트를 우리가 정의한 커스텀 객체인 델리게이트에게 알려주기도 한다. 특별한 이유가 없을 때는 서브 클래싱 없이 그대로 사용한다. 서브클래싱 할 필요도 없고, 하기도 어렵기 때문이다.

💡 UIApplication 객체를 서브 클래싱 해야 한다면?

UIApplication 객체는 AppDelegate라는 대리 객체를 내세워서 커스텀 코드를 처리할 수 있도록 약간의 권한을 부여한다. AppDelegate는 UIApplication으로부터 위임받은 권한을 이용해 커스텀 코드와 상호작용하는 역할을 담당하고, 이를 통해 우리가 필요한 코드를 구현할 수 있도록 해준다.
(iOS 13 이상부터는 SceneDelegate 객체도 같이 사용된다.)

💡 AppDelegate 객체?

AppDelegate 객체는 iOS 애플리케이션 내에 오직 하나의 인스턴스만 생성되도록 시스템적으로 보장받는다. 게다가 앱이 처음 만들어질 때 객체가 생성되고, 앱이 실행되는 동안 계속 유지되다가 앱이 종료되면 함께 소멸해서 앱 전체의 생명 주기와 함께 한다. 이런 특성 때문에 AppDelegate 객체에 데이터를 저장하면 앱이 종료될 때까지 계속 데이터를 유지할 수 있다. 따라서 종종 앱의 초기 데이터 구조를 설정하기 위해 사용되기도 한다.

💡 앱이 실행되는 전체 과정을 정리해보면

  1. main() 함수를 실행한다.
  2. UIAppicationMain() 함수를 호출한다.
  3. UIApplication 객체가 생성된다.
  4. UIApplication 객체는 Info.plist 파일을 바탕으로 앱에 필요한 데이터와 객체를 로드한다.
  5. AppDelegate 객체를 생성하고 UIApplication 객체와 연결한다.
  6. 이벤트 루프를 만드는 등 실행에 필요한 준비를 한다.
  7. 실행 완료 직전, AppDelegate의 application(_:didFinishLaunchingWithOptions:) 메소드를 호출한다.
  8. 메소드 호출 후에 각 씬이 처음으로 생성될 때 동적으로 SceneDelegate 객체가 생성된다.

스위프트에서는 직접 UIApplicationMain()을 호출하여 델리게이트 클래스를 인자값으로 전달할 수 없으므로 앱 델리게이트 역할을 할 클래스에 UIApplicationMain 어노테이션을 걸어 표시해서 시스템에 델리게이트 클래스 정보를 전달한다.

현재는 @main 어노테이션으로 애플리케이션의 진입점을 표시한다.

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
    return true
}

AppDelegate Life Cycle



앱의 라이프 사이클

앱은 실행되는 동안 다양한 상태로 변화한다. 이때의 상태란 화면에 나타났거나, 화면으로부터 숨겨졌거나, 시작했거나 종료되었거나 하는 등을 의미한다.
앱의 상태변화는 운영체제가 처리하는 영역이다. 아이폰의 운영체제인 iOS는 시스템에서 발생하는 특정 상황에 맞게 앱의 상태를 변화시키고 제어한다.
예를 들면 사용자가 앱을 사용하는 도중에 전화가 오면 실행되던 앱은 꺼지듯이 화면에서 사라지고 그 자리를 전화 화면이 대신하고, 통화가 종료되면 기존의 앱이 화면에 나타나는데 이는 iOS가 전화라는 상황에 맞게 기존 앱의 상태를 변경한 것이다.

💡 iOS에서 앱이 가질 수 있는 상태 값

Not Running: 앱이 종료된 상태
Inactive: 앱이 전면에서 실행중이지만, 아무 이벤트를 받지 않고 있는 상태
Active: 앱이 전면에서 실행중이며, 이벤트를 받고 있는 상태
Background: 앱이 백그라운드에 있지만 여전히 코드가 실행되고 있는 상태, 대부분의 앱은 Suspended 상태로 이행하는 도중에 일시적으로 이 상태에 진입하지만 파일 다운로드, 업로드, 연산 처리 등 시간이 조금 더 걸릴 경우 이 상태로 잠깐 남아 있을 수도 있다.
Suspended: 앱이 메모리에 유지되지만 실행되는 코드가 없는 상태, 메모리가 부족해지면 iOS 시스템은 Foreground에 있는 앱의 여유 메모리 공간을 확보하기 위해 Suspended 상태의 앱들을 특별한 알림 없이 정리한다.

iOS 앱은 Not Running 상태에서 시작해서 Inactive, Active를 거친 다음, Suspended 상태를 마지막으로 다시 Not Running 상태로 돌아간다.
이를 앱의 라이프사이클이라고 한다. 앱의 실행 상태가 변화할 때마다 앱 객체는 AppDelegate에 정의된 특정 메소드를 호출한다. 이 메소드 내부에 적절한 커스텀 코드를 작성해서 원하는 작업이 실행되도록 할 수 있다.
예를 들면, 앱이 종료되기 전에 데이터를 저장하거나 앱이 백그라운드 상태로 내려가면 필요없는 메모리를 정리한다거나 할 수 있다.

메소드에 대한 자세한 내용은 해당 글을 참고하자!
AppDelegate Method



코코아 터치 프레임워크

프레임워크

사전적인 의미로는 어떤 것을 이루는 뼈대, 구조를 의미한다. 소프트웨어에서 사용하는 프레임워크는 애플리케이션 제작을 빠르고 편하게 할 수 있도록 미리 뼈대를 이루는 각종 코드를 제작하여 모아둔 것이다.

iOS

iOS는 애플이 개발해서 제공하는 임베디드 운영 체제다. 초기의 iOS에는 사용자가 개발한 앱을 추가할 수 없었고 애플에서 제공하는 기능과 시스템 앱만 사용가능했다. 2008년 06월 이후 iOS용 소프트웨어를 개발할 수 있는 도구인 SDK가 공개되면서 일반 사용자가 개발한 앱도 iOS에 설치하여 사용할 수 있게 되었다. iOS에서 제공하는 SDK는 네이티브 앱을 개발하고 설치하여 실행하고 테스트하는데 필요한 도구와 화면을 모두 포함하고 있다. SDK가 없으면 네이티브 앱을 개발할 수 없다.

💡 네이티브 앱?

네이티브 앱은 iOS 시스템 프레임워크를 기반으로 하고 스위프트나 오브젝티브-C 언어로 개발되며 iOS를 통해 직접 실행되는 앱이다. 네이티브 앱은 기기에 물리적으로 설치되기 때문에 디바이스가 비행기 모드일 때도 실행할 수 있다.

💡 웹 앱?

웹 앱은 사파리 브라우저를 통해 실행되는 앱으로 폰 갭(Phone Gap), 티타늄등의 개발 도구를 사용해 HTML 페이지와 CSS, 자바스크립트 등의 기술로 네이티브 앱과 유사한 UI, 기능을 제공하는 앱이다. 네이티브 앱에 비해 진입 장벽이 낮지만 사용상 제약이 많고 성능이 낮은 편이고 항상 네트워크를 통해 실행되므로 네트워크가 연결되지 않은 상태에서는 이용할 수 없다.

iOS는 기기의 하드웨어와 네이티브 앱 사이에서 중계자처럼 동작한다. 앱은 하드웨어와 직접 대화할 수 없으며 iOS에서 제공하는 시스템 인터페이스를 통해서만 커뮤니케이션 할 수 있다. 예를 들면 하드웨어의 블루투스 모듈을 작동시켜 기기를 연동할 때 앱이 직접 하드웨어 모듈에 접근해서 실행하는 것이 아니라 iOS에서 제공하는 블루투스용 인터페이스를 사용해서 간접적으로 실행하는 것이다. 하드웨어의 기능이 다양한 디바이스들을 범용적으로 제어하기 위해서다. 이렇게 하드웨어와 앱 사이를 중계해주는 iOS 인터페이스가 바로 코코아 터치 프레임워크이다. 코코아 터치 프레임워크는 코코아 프레임워크를 바탕으로 만들어졌다. 코코아 프레임워크의 주 프레임 워크는 AppKit 프레임워크코코아 터치 프레임워크이다. 코코아 터치 프레임워크Foundation 프레임워크를 공통으로 갖고 있고, 유저 인터페이스는UIKit 프레임워크이다.

Cocoa Touch Framework



프레임워크의 계층 관계

프레임워크들은 계층 관계를 이룬다. 주로 프로그래밍에 사용하는 것은 상위 레벨의 프레임워크들로 상위계층이란, 보다 사용자에 가깝고 구체적으로 구현되어 있어서 애플리케이션을 만들 때 가장 쉽게 사용할 수 있는 형태를 말한다. 이것을 구체화되어있다 라고 표현한다.
반면 하위계층에 속한 프레임워크는 추상적이면서 하드웨어 쪽에 더 가까워서 다루기에는 다소 번거롭다. 하지만 범용적이고 원척적이기 때문에 확장이 가능하다는 장점이 있다.
대부분의 경우에서 상위 프레임워크는 하위 프레임워크에 의존적이다. 상위 프레임워크에 필요한 기능을 모두 직접 구현하는 것이 아니라, 하위 프레임워크를 통해 구현된 기능에 구체적인 기능을 덧붙이는 것이다.
경우에 따라 상위 레벨의 프레임워크가 제공하지 않는 기능을 구현해야 할때는 하위 레벨의 프레임워크를 사용해 원하는 기능을 직접 구현하기도 한다.

iOS의 프레임워크 계층 구조


iOS의 프레임워크 계층은 하드웨어와 애플리케이션 사이를 중계하는 위치에 자리한다. 프레임워크 계층 중에서 가장 아래에 위치한 코어 OS 계층은 개념적으로 하드웨어와 연결되어 있으며, 최상단에 위치한 코코아 터치 계층은 애플리케이션과 연결된다. 하드웨어와 직접 커뮤니케이션할 수 없는 애플리케이션이 몇 줄의 코드를 통해 하드웨어 장치를 구동할 수 있는 것은 이같은 프레임워크 계층을 통해 간접적으로 하드웨어를 제어할 수 있기 때문이다.

일부 분야에서는 하드웨어와 직접 통신하는 프로그램 코드를 작성하기도 한다. 이를 임베디드 프로그래밍이라고 한다. 임베디드 프로그래밍에서는 하드웨어와 직접 통신하고 제어하기 위해 부품의 명세서나 데이터 시트가 필요하고, 이를 이용하여 적합한 전기 신호를 만들어서 하드웨어를 제어한다.

코어 OS 계층
커널, 파일 시스템, 네트워크, 보안, 전원 관리, 디바이스 드라이버 등이 포함되어 있다. iOS가 운영체제로서 기능을 하기 위한 핵심적인 영역이다.

코어 서비스 계층
이 계층에 속한 프레임워크들은 문자열 처리, 데이터 집합 관리, 네트워크, 주소록 관리, 환경 설정 등 핵심적인 서비스들을 제공한다. 또한 GPS, 나침반, 가속도 센서나 자이로스코프 센서와 같이 디바이스의 하드웨어 특성에 기반한 서비스도 제공한다.
대표적으로 파운데이션 프레임워크가 내부적으로 의존하는 코어 파운데이션 프레임워크파운데이션 프레임워크가 포함되어있다.

미디어 계층
코어 서비스 계층에 의존적이며, 상위 계층인 코코아 터치 계층에 그래픽 관련 서비스나 멀티미디어 관련 서비스를 제공한다.
코어 그래픽스, 코어 텍스트, 코어 오디오, 코어 애니메이션, AV파운데이션 등이 있다.

코코아 터치 계층
애플리케이션 프레임워크 계층이라고도 불리며 애플리케이션을 직접 지원하는 역할을 한다. iOS에 설치되고 실행되는 모든 애플리케이션은 코코아 터치 계층에서 제공하는 여러가지 기술이나 서비스를 이용하여 기능을 구현하고 동작한다. Game Kit, Map Kit, UserNotification, UIKit 프레임워크가 속해있다.



앱의 핵심 객체

화면을 구성하는 주요 객체

UIScreen: 기기에 연결되는 물리적인 화면을 정의하는 객체
UIWindow: 화면 그리기 지원 도구를 제공하는 객체
UIView: 그리기를 수행할 객체 세트

Window

윈도우는 iOS에서 디바이스의 스크린을 빈틈없이 채우기 위한 객체로, 항상 유저 인터페이스 표현 계층의 최상위에 위치한다. 뷰의 일종이지만 직접 콘텐츠를 가지지는 않으며 콘텐츠를 가진 뷰를 내부에 배치하여 화면에 출력하는 역할을 한다. 화면이 전환되더라도 윈도우 객체는 전환되지 않으며 단지 내부에 배치된 뷰의 콘텐츠만 변경된다.

View

는 콘텐츠를 담아 스크린상에 표시하고, 사용자의 입력에 반응한다. 윈도우의 일부를 자신의 영역으로 정의하고, 여기에 필요한 콘텐츠를 채워 넣어 스크린에 나타내는 동시에, 윈도우로부터 전달된 사용자의 입력에 반응하여 그에 맞는 결과를 처리한다. 하나 이상의 뷰들이 콘텐츠를 표현하면 윈도우는 모바일 디바이스의 스크린에 이를 표현한다. 뷰의 영역이 겹쳐질 경우 중첩된 형태로 표현되기도 한다.

ViewController

윈도우와 뷰 사이는 뷰컨트롤러를 통해 연결된다. 뷰컨트롤러는 뷰의 계층을 관리하여 윈도우에 전달하고, 모바일 디바이스에서 감지된 터치 이벤트를 윈도우로부터 전달받아 처리하는 역할을 한다. 윈도우는 뷰컨트롤러를 통해 뷰를 읽어 들여 표현할 뿐, 뷰를 직접 관리하지는 않는다.

윈도우 객체는 하나의 뷰컨트롤러를 루트 뷰컨트롤러로 지정하여 참조합니다. 나머지 뷰컨트롤러들은 루트 뷰컨트롤러의 관리 대상으로 연결되거나 다른 방식으로 이어지기도 하지만, 이들은 윈도우 객체의 직접적인 관리 대상이 아니고 윈도우 객체는 항상 루트 뷰컨트롤러만을 참조한다.

대부분의 뷰컨트롤러들은 각자 하나씩의 화면(씬)을 담당하여 콘텐츠를 표현하고 뷰를 관리한다. 씬을 담당하고 콘텐츠를 표시하는 뷰컨트롤러를 콘텐츠 뷰컨트롤러라고 한다. 내비게이션 컨트롤러나 탭바 컨트롤러처럼 씬을 표현하지않고 다른 뷰컨트롤러의 연결관계를 관리하는 컨트롤러는 컨테이너 뷰컨트롤러이다.

일반적으로 각각의 씬은 자신만의 뷰 계층구조를 가지고 있으며 뷰 계층 구조 최상위에는 루트뷰 또는 콘텐츠뷰라고 하는 하나의 뷰가 존재한다. 일반적으로 뷰는 다양한 크기를 가질 수 있지만 루트 뷰는 항상 화면 전체를 채울 수 있는 크기를 유지한다. 루트 뷰 내부에는 여러개의 서브뷰가 추가되는데 이런 서브 뷰들을 모아 하나의 전체 뷰를 구성하고, 뷰컨트롤러를 통해 이를 윈도우에 전달한다.



뷰컨트롤러의 상태변화와 생명주기

뷰컨트롤러의 생명주기는 장면(씬)의 전환과 복귀에 밀접하게 관련되어있다.

💡 뷰컨트롤러의 상태


Appearing: 뷰컨트롤러가 스크린에 등장하기 시작한 순간부터 등장을 완료하기 직전까지의 상태, 퇴장 중인 다른 뷰컨트롤러와 교차하기도 하며 이때 퇴장 중인 다른 뷰컨트롤러의 상태는 Disappearing이 된다.
Appeared: 뷰컨트롤러가 스크린 전체에 완전히 등장한 상태
Disappearing: 뷰컨트롤러가 스크린에서 가려지기 시작해서 완전히 가려지기 직전까지의 상태, 새로 등장할 뷰컨트롤러와 교차하기도 하며 이때 등장 중인 다른 뷰컨트롤러의 상태는 Appearing이 된다.
Disappeared: 뷰컨트롤러가 스크린에서 완전히 가려졌거나 퇴장한 상태

💡 호출되는 메서드

viewDidLoad(): 뷰 계층이 메모리에 로드된 직후 호출되는 메서드
1회 호출되며 메모리 경고로 뷰가 사라지지 않는 이상 다시 호출되지 않고 주로 뷰의 초기화 작업을 담당한다.
viewWillAppear: Appearing 상태에 호출되는 메서드
화면이 등장할 때마다 데이터를 갱신해주고 싶을 때 오버라이드해서 사용한다.
viewDidAppear: Appeared 상태에 호출되는 메서드
viewWillDisappear: Disappearing 상태에 호출되는 메서드
뷰를 생성하고나서 했던 행동들을 되돌리는 작업할 때나 작성 또는 선택된 정보들을 삭제되기 전에 저장해두는 작업할 때 사용한다.
viewDidDisappear: Disappeared 상태에 호출되는 메서드
뷰를 숨기는 것과 관련된 추가적인 작업을 할 때 사용한다.

0개의 댓글