본문 바로가기

iOS/SwiftUI

SwiftUI 기반 App 코드 분석

 

🔍 서론 

스위프트 공부를 시작하고 처음으로 쓰는 iOS 관련 블로그이다. 스위프트 문법도 익힐 겸 SwiftUI 프로젝트를 처음 만들었을 때 기본으로 생성되는 App.swift를 분석해보려고 한다. 공식적으로 제공되는 라이브러리 코드를 하나하나 뜯어보면서 분석하는 게 개발 실력 향상에 있어서 엄청나게 큰 도움이 되는 것을 알기에, 앞으로도 블로그에 글은 쓰지 못하더라도 처음 보는 라이브러리들은 꼭 한 번씩 코드를 분석해보려고 한다.

 

 

 

 💻 코드 분석

import SwiftUI

@main
struct HomeApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

 

프로젝트를 처음 만들면 자동으로 생성되는 App.swift 코드는 위와 같다. 먼저, 이 프로젝트의 진입점을 알리는 @main 키워드가 존재하고, App 프로토콜을 구현한 HomeApp이라는 구조체가 나와있다.  여기까지는 특별한 점이 없다. 이제 구조체 안에 있는 코드로 들어가 보겠다.

 

 

현재 body 연산 프로퍼티가 선언되어 있는데, 이 body 변수는 App 프로토콜에 적혀있는 body를 구현한 것이다. 아래 코드는 App 프로토콜의 일부분인데 body가 적혀있는 것을 볼 수 있다.

/// Swift infers the app's ``SwiftUI/App/Body-swift.associatedtype``
/// associated type based on the scene provided by the `body` property.
@SceneBuilder @MainActor var body: Self.Body { get }

 

 

그다음으로 이 연산 프로퍼티의 리턴 타입에 주목해야 된다. 현재 "some Scene"이라고 적혀있는데, 이는 스위프트 5.1 버전에서 추가된 불명확 타입 기능인 some을 사용한 문법이다. 여기서 some을 제거하면 컴파일 에러가 난다. 일반적으로 리턴 타입에 프로토콜이나 클래스를 적어 놓으면, 그 타입을 포함한 하위 타입을 리턴할 수 있다. 여기서 WindowGroup은 Scene 프로토콜을 구현한 구조체인데 왜 컴파일 에러가 나는 것일까?

 

그 이유는 associatedType에 있다.

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
public protocol Scene {

    /// The type of scene that represents the body of this scene.
    ///
    /// When you create a custom scene, Swift infers this type from your
    /// implementation of the required ``SwiftUI/Scene/body-swift.property``
    /// property.
    associatedtype Body : Scene

    /// The content and behavior of the scene.
    ///
    /// For any scene that you create, provide a computed `body` property that
    /// defines the scene as a composition of other scenes. You can assemble a
    /// scene from built-in scenes that SwiftUI provides, as well as other
    /// scenes that you've defined.
    ///
    /// Swift infers the scene's ``SwiftUI/Scene/Body-swift.associatedtype``
    /// associated type based on the contents of the `body` property.
    @SceneBuilder @MainActor var body: Self.Body { get }
}

 

위의 코드는 Scene 프로토콜의 코드인데, associatedtype이 선언된 것을 알 수 있다. associatedtype은 프로토콜 버전의 제네릭 같은 기능인데, 타입을 프로토콜에서 미리 지정하는 게 아니라 이 프로토콜의 구현체에서 지정하는 기능이다. 아무튼, 이 associatedtype으로 인해 Scene을 구현한 구조체에서 어떤 객체를 리턴하는지 외부에서 알 수가 없다. 이 경우, 반환될 타입이 명확하지 않으므로 불명확 타입 기능인 some을 적어주면 문제가 해결된다.

 

 

또 눈여겨봐야 될 곳은 아래의 부분이다. 

WindowGroup {
    ContentView()
}

 

언뜻 보면 무슨 문법이 사용된 건지 싶다. 하지만 WindowGroup이 생성자인 것을 안다면, 바로 이해가 될 것이다. 그런데 생성자인데 소괄호가 존재하지 않는다. 어떻게 된 것일까? 그 비밀은 바로 "후행 클로저"에 있다. 스위프트 문법에 따르면, 함수나 메서드의 마지막 전달인자로 위치하는 클로저는 소괄호를 닫은 후에 작성해도 되고, 심지어 소괄호를 생략할 수도 있다. 생성자도 마찬가지다. 즉 위의 코드를 풀어서 작성하면 아래와 같다.

WindowGroup ( { ContentView() } )

 

물론 여기서 더 풀어서 작성할 수도 있지만, 이 코드가 생성자인 것만 이해하면 되므로 더 이상의 작성은 생략하겠다. 안에 들어있는 ContentView() 코드는 프로젝트 생성 시 기본으로 생성되는 ContentView 구조체를 생성해서 반환한다.

 

 

이렇게 App.swift 분석을 끝냈다. 아직 스위프트에 미숙하므로 이후에 놓친 부분이나 이상한 부분을 발견하면 계속 수정해 나가겠다.