project/개발 업무

빌드 메시지 분석해서 빌드 시간 개선하기

lgvv 2024. 12. 3. 01:36

빌드 메시지 분석해서 빌드 시간 개선하기

 

프로젝트 빌드 메시지를 분석해서 빌드 시간 개선해보고자 함.

 

예제 파일

ArchitectureExample.zip
0.24MB

목차

  • 빌드 메시지 확인하는 방법
  • 프로젝트 구조 및 Home Package 확인하기
  • 1차 문제 분석 및 개선 방향성 잡기
    • Player Package 확인하기
    • Player Package 개선하기
    • 1차 개선 결과
  • 2차 문제 분석 및 개선 방향성 잡기
    • 문제 코드 영역 확인하기
    • HomeDetail을 HomeDetailInterface 모듈로 분리하기
    • 개선한 Home Package
    • 2차 개선 결과
  • 실행 가능한 앱(데모 앱) 구성하기
    • 실행 가능한 앱(데모 앱) 빌드 결과
  • 결과 한눈에 확인하기
    • 1차 개선
    • 2차 개선
    • 앱 빌드

 

빌드 메시지 확인하는 방법

좌측 인스펙터에 빨간색 이미지 영역 클릭

예제

 

빌드 이미지를 보면 순차대로 어떤 것들이 먼저 처리되는지 확인할 수 있음

    • 아래 그래프는 Home을 빌드했을 때의 결과

 

 

프로젝트 구조 및 Home Package 확인하기

좌측은 Package 구조, 우측은 Home에 Packge에 작성된 코드

Home 패키지 구조

 

1차 문제 분석 및 개선 방향성 잡기

문제 인식

  • Home에는 Player를 사용하고 있지 않지만, VideoPlayer, MusicPlayer가 함께 빌드되어서 불필요한 빌드시간이 존재

개선 방향성

  • Player에서 VideoPlayer의 인터페이스에 의존하게 변경
    • HomeDetail에서 MusicPlayer의 빌드를 직접하지 않을 수 있음


Player Package 확인하기

Player 패키지는 아래처럼 되어있음

  • VideoPlayer가 MusicPlayer에 의존
// swift-tools-version: 5.10
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "Player",
    products: [
        .library(
            name: "MusicPlayer",
            targets: ["MusicPlayer"]
        ),
        .library(
            name: "VideoPlayer",
            targets: ["VideoPlayer"]
        ),
    ],
    targets: [
        .target(
            name: "MusicPlayer"
        ),
        .target(
            name: "VideoPlayer",
            dependencies: [
                "MusicPlayer"
            ]
        )
    ]
)

 

Player Package 개선하기

인터페이스와 실제 구현체를 분리하고자 함

  • 인터페이스를 별도로 분리해 실제 구현 객체에 의존하지 않도록 변경
  • 이에 따라 VideoPlayer에서 다른 의존성 또한 빌드되지 않도록 하여 빌드 속도 개선

인터페이스 분리

 

Package 개선한 코드

  • 인터페이스에 의존하는 형태로 수정
// swift-tools-version: 5.10
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "Player",
    products: [
        .library(
            name: "MusicPlayer",
            targets: ["MusicPlayer"]
        ),
        .library(
            name: "VideoPlayerInterface",
            targets: ["VideoPlayerInterface"]
        ),
        .library(
            name: "VideoPlayer",
            targets: ["VideoPlayer"]
        ),
    ],
    targets: [
        .target(
            name: "MusicPlayer"
        ),
        .target(
            name: "VideoPlayerInterface"
        ),
        .target(
            name: "VideoPlayer",
            dependencies: [
                "MusicPlayer",
                "VideoPlayerInterface"
            ]
        )
    ]
)

 

 

1차 개선 결과

  • 인터페이스를 분리함으로써 불필요하게 빌드되던 MusicPlayer를 제거할 수 있었음.
  • 따라서 약간의 빌드 시간 감소

Home 개선 후

 

 

2차 문제 분석 및 개선 방향성 잡기

문제 인식

  • Home에는 Utils Package에 대한 의존성이 없지만 HomeDetail로 인해서 불필요한 빌드시간이 추가됨

개선 방향성

  • HomeDetail도 인터페이스로 분리
    • 이점: Utils의 빌드를 피할 수 있음


Home Package 확인하기

Home이 HomeDetail에 의존하고 있음

  • HomeDetail은 다양한 모듈들에 의존하는 상황
// swift-tools-version: 5.10
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "Home",
    platforms: [.iOS(.v15)],
    products: [
        .library(
            name: "Home",
            targets: ["Home"]
        ),
        .library(
            name: "HomeDetail",
            targets: ["HomeDetail"]
        ),
    ],
    dependencies: [
        .package(path: "../Player"),
        .package(path: "../Utils")
    ],
    targets: [
        .target(
            name: "Home",
            dependencies: [
                "Model",
                "HomeDetail"
            ]
        ),
        .target(
            name: "HomeDetail",
            dependencies: [
                "Model",
                .product(name: "VideoPlayerInterface", package: "Player"),
                .product(name: "KeyboardReadable", package: "Utils"),
                .product(name: "SceneDelegateReadable", package: "Utils"),
                .product(name: "AppDelegateReadable", package: "Utils")
            ]
        ),
        .target(
            name: "Model"
        )
    ]
)

 

문제 코드 영역 확인하기

예제는 VIPER를 사용하고 있고, HomeRouter 코드 확인

  • HomeDetail을 import하여 Builder를 직접 호출하고 있음
  • 따라서 HomeDetail에 필요한 의존성도 모두 함께 빌드가 됨.
import UIKit
import HomeDetail

protocol HomeRouterInterface {
    var navigationContoller: UINavigationController? { get }
    
    func pushToDetail()
}

class HomeRouter: HomeRouterInterface {
    var navigationContoller: UINavigationController?
    
    func pushToDetail() {
        let view = HomeDetailBuilder().build()
        
        navigationContoller?.pushViewController(view, animated: true)
    }
}

 

HomeDetail을 HomeDetailInterface 모듈로 분리하기

HomeInterface 추가

  • 외부에 노출할 필요한 인터페이스를 분리
import UIKit

public protocol HomeDetailInterface {
    func build() -> UIViewController
}

 

HomeDetail 코드 수정

  • HomeDetail이 HomeDetailInterface에 의존하도록 변경
import UIKit
import HomeDetailInterface

public struct HomeDetailBuilder: HomeDetailInterface {
    
    public init() {}
    
    public func build() -> UIViewController {
        let router = HomeDetailRouter()
        let interactor = HomeDetailInteractor()
        let presenter = HomeDetailPresenter(
            interactor: interactor,
            router: router
        )
        interactor.output = presenter
        
        let view = HomeDetailViewController(presenter: presenter)
        router.viewContoller = view
    
        return view
    }
}

 

 

개선한 Home Package

Home도 동일하게 Interface로 분리

  • 외부에 노출할 필요한 인터페이스를 분리
// swift-tools-version: 5.10
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "Home",
    platforms: [.iOS(.v15)],
    products: [
        .library(
            name: "Home",
            targets: ["Home"]
        ),
        .library(
            name: "HomeInterface",
            targets: ["HomeInterface"]
        ),
        .library(
            name: "HomeDetail",
            targets: ["HomeDetail"]
        ),
        .library(
            name: "HomeDetailInterface",
            targets: ["HomeDetailInterface"]
        ),
    ],
    dependencies: [
        .package(path: "../Player"),
        .package(path: "../Utils")
    ],
    targets: [
        .target(
            name: "HomeInterface"
        ),
        .target(
            name: "Home",
            dependencies: [
                "Model",
                "HomeDetailInterface",
                "HomeInterface"
            ]
        ),
        .target(
            name: "HomeDetailInterface"
        ),
        .target(
            name: "HomeDetail",
            dependencies: [
                "Model",
                "HomeDetailInterface",
                .product(name: "VideoPlayerInterface", package: "Player"),
                .product(name: "KeyboardReadable", package: "Utils"),
                .product(name: "SceneDelegateReadable", package: "Utils"),
                .product(name: "AppDelegateReadable", package: "Utils")
            ]
        ),
        .target(
            name: "Model"
        )
    ]
)


2차 개선 결과

인터페이스를 분리함으로써 HomeDetail에 걸린 의존성을 빌드하지 않음

  • 따라서 약간의 빌드 시간 감소

2차 개선 결과

 

 

실행 가능한 앱(데모 앱) 구성하기

실행 가능한 앱을 구성하기 위해서는 세부 모듈은 Interface에 의존하고 있더라도 앱을 실행하기 위해서는 구현체를 필요로 함

  • 앱을 실행하기 위해서는 구현체는 반드시 한번은 빌드가 되어야 해서 구현체를 가져도 상관없음
  • 즉, 작은 모듈에서는 인터페이스에 의존하여 개발하고, 데모 앱을 만들때는 구현체를 모두 포함시켜 빌드시킴
import UIKit
import VideoPlayerInterface
import VideoPlayer
import Home
import HomeInterface
import HomeDetailInterface
import HomeDetail

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        
        guard let windowScene = (scene as? UIWindowScene) else { return }
        
        window = UIWindow(windowScene: windowScene)
        window?.backgroundColor = .systemBackground
        
        let components = RootComponents()
        
        let builder: HomeBuilderInterface = HomeBuilder(
            dependency: components
        )
        let rootViewController = builder.build()
        
        window?.rootViewController = rootViewController
        
        window?.makeKeyAndVisible()
    }

}

final class RootComponents: HomeBuilderDepedency {
    lazy var homeDetail: HomeDetailInterface = {
        HomeDetailBuilder(dependency: HomeDetailDependency())
    }()
}

final class HomeDetailDependency: HomeDetailBuilderDepedency {
    lazy var videoPlayer: VideoPlayerInterface = {
        VideoPlayer()
    }()
}

 

 

실행 가능한 앱(데모 앱) 빌드 결과

구현체를 포함하여 필요한 모듈을 모두 빌드함

  • 따라서 필요한 모든 모듈을 빌드하기에 시간이 더 걸림

결과


결과 한눈에 비교하기

1차 개선

  • Player패키지를 Interface와 분리하여 HomeDetail 개선

1차 개선 결과

 

2차 개선

  • HomeDetail도 Interface에 의존하게 만들어 Home 수정
  • 해당 이미지엔 영향을 주진 않지만 Home도 외부 제공을 위해 인터페이스로 분리

2차 개선

 

앱 빌드

  • 데모 앱 혹은 실행가능한 앱을 위한 빌드

앱 빌드