본문 바로가기

iOS+/Swift

Swift 명령어와 Swift Package

개요

이 글은 스위프트와 스위프트 패키지에 대한 글이다

스위프트는 컴파일러, REPL, 패키지 관련 다양한 기능들을 제공해준다

우선은 자주 사용되는 스위프트 명령어를 정리하고, 패키지 구성에 대해 정리한다

마지막으로 정리한 내용을 바탕으로, 실제 패키지들을 간단하게 분석해본다

 

Swift 명령어

더 많은 내용은 swift -h로 확인할 수 있고, 다음 요소에 대해서만 정리한다

  • swiftswiftc
  • swift repl
  • swift package
    • swift runswift build
    • swift sdk install

swift와 swiftc

swift에 파일명을 전달하면, 컴파일하고 실행한다

swift hello.swift

이때 swiftc와 옵션을 통해 마치 gcc로 c파일을 컴파일 하듯이 사용할 수 있다

swiftc hello.swift -o hello

더 다양한 옵션들은 swiftc -h로 확인할 수 있다

swift repl

Swift 대화형 인터프리터라고 볼 수 있다

swift package

스위프트 패키지는 크게 executable과 library로 존재한다

  • executable - CLI 같은 것을 만들 때 사용
  • library - 일반적으로 import 해서 사용하는 것을 만들 때 사용

정적 라이브러리와 동적 라이브러리

library는 타입으로 정적과 동적이 존재한다

  정적 라이브러리 동적 라이브러리
빌드 시점의 동작 앱의 소스코드와 라이브러리 코드가 실행 파일에 전부 병합 라이브러리 참조만 실행 파일에 포함
런타임의 동작 추가적인 라이브러리 로딩 필요 없음 필요한 라이브러리를 로딩
앱의 실행 파일 크기 커짐 작아짐
로딩 속도 향상됨 느려질 수 있음

Swift Package로 라이브러리를 생성 시, 설정에 dynamic을 타입으로 전달하지 않으면

기본적으로 정적라이브러리다

swift run과 swift build

swift run은 스위프트 패키지(executable)를 실행하는 명령어다(인자도 넘길 수 있다)

swift build를 사용하면, 패키지는 executable일 때는 실행가능한 파일이 되고, library일 때는 바이너리로 컴파일 된다

swift sdk install

크로스 컴파일을 수행하는 데 필요한 도구와 라이브러리를 시스템에 설치하는데 사용된다

 

Swift Package

패키지 생성

swift package init --name 패키지명 --type 패키지타입

스위프트 패키지는 다음과 같이 swift package init 으로 생성 가능하다

 

패키지 구조

스위프트 패키지를 생성하면, 처음 구조는 다음과 같다

./
├── Package.swift
└── Sources/
    └── main.swift
  • Package.swift - 패키지에 관한 내용이 존재
  • Sources/ - 패키지의 소스파일들이 존재

executable의 시작점

만약 패키지타입이 executable이라면, 시작점이 필요하다

다음과 같이 두 가지 방법으로 시작점을 설정할 수 있다

  1. 파일명(main.swift)을 기준으로 실행 시작
  2. @main을 붙여서 진입점을 설정

 

Package.swift

Package.swift는 패키지 구조에서 최상단에 위치하는 파일로, 해당 파일에서 Package를 정의한다

final public class Package {

    /// The name of the Swift package.
    ///
    /// If the name of the package is `nil`, Swift Package Manager deduces the name of the
    /// package using its Git URL.
    final public var name: String

    /// The list of minimum versions for platforms supported by the package.
    @available(_PackageDescription 5)
    final public var platforms: [SupportedPlatform]?

    /// The default localization for resources.
    @available(_PackageDescription 5.3)
    final public var defaultLocalization: LanguageTag?

    /// The name to use for C modules.
    ///
    /// If present, the Swift Package Manager searches for a `<name>.pc` file to
    /// get the required additional flags for a system target.
    final public var pkgConfig: String?

    /// An array of providers for a system target.
    final public var providers: [SystemPackageProvider]?

    /// The list of targets that are part of this package.
    final public var targets: [Target]

    /// The list of products that this package vends and that clients can use.
    final public var products: [Product]

    /// The list of package dependencies.
    final public var dependencies: [Package.Dependency]

    /// The list of Swift versions with which this package is compatible.
    final public var swiftLanguageVersions: [SwiftVersion]?

    /// The C language standard to use for all C targets in this package.
    final public var cLanguageStandard: CLanguageStandard?

    /// The C++ language standard to use for all C++ targets in this package.
    final public var cxxLanguageStandard: CXXLanguageStandard?

    /// Initializes a Swift package with configuration options you provide.
    ///
    /// - Parameters:
    ///   - name: The name of the Swift package, or `nil` to use the package's Git URL to deduce the name.
    ///   - products: The list of products that this package makes available for clients to use.
    ///   - dependencies: The list of package dependencies.
    ///   - targets: The list of targets that are part of this package.
    /// ...
    @available(_PackageDescription 5.3)
    public init(name: String, defaultLocalization: LanguageTag? = nil, platforms: [SupportedPlatform]? = nil, pkgConfig: String? = nil, providers: [SystemPackageProvider]? = nil, products: [Product] = [], dependencies: [Package.Dependency] = [], targets: [Target] = [], swiftLanguageVersions: [SwiftVersion]? = nil, cLanguageStandard: CLanguageStandard? = nil, cxxLanguageStandard: CXXLanguageStandard? = nil)
}

Package 클래스는 PackageDescription 모듈에 정의되어있는데, Package를 보면 여러 프로퍼티를 가지지만

그중 핵심인 name, products, targets에 대해서만 정리하면 다음과 같다

name

name은 패키지의 이름이다

products

products는 패키지가 생성하는 라이브러리나 실행 가능한 프로그램을 정의한다

실행파일은 보통 그냥 실행파일을 실행하니까 products를 설정안해도 별상관없겠지만,

라이브러리는 이렇게 정의를해야 다른 패키지나 애플리케이션에서 이 라이브러리를 임포트하여 사용할 수 있다

  • .library - 라이브러리는 두 가지 타입(정적/동적)으로 존재하며, 기본 값은 정적라이브러리다

targets

targets는 패키지의 타겟을 나타내는데, 패키지 내의 소스코드를 컴파일하여 모듈을 생성하는 역할이다

스위프트 패키지에서는 다음 세 가지 타입의 타겟을 지원한다

  • .target - 패키지 내의 소스코드를 컴파일하여 모듈을 생성한다(Path로 위치 지정 가능)
  • .testTarget - 테스트 실행코드 및 테스트 대상 의존성도 지정가능하다(Path로 위치 지정 가능)
  • .binaryTarget - 원격 또는 로컬 파일 시스템 경로로 바이너리 아티팩트를 참조할 수 있다

또한 @main을 통해 진입점 표시 시, .excutableTarget을 통해 타겟을 설정해야한다

 

Swift Package의 Package.swift 분석

정리한 내용들을 바탕으로 실제 패키지에서 Package.swift를 보고 어떤 패키지인지 분석해보면 다음과 같다

1) Hello package

import PackageDescription

let package = Package(
    name: "Hello",
    targets: [
        .executableTarget(
            name: "Hello"),
    ]
)
  • @main으로 진입점을 지정한 excutable 패키지다

2) Alamofire package

https://github.com/Alamofire/Alamofire

import PackageDescription

let package = Package(name: "Alamofire",
                      platforms: [.macOS(.v10_13),
                                  .iOS(.v12),
                                  .tvOS(.v12),
                                  .watchOS(.v4)],
                      products: [
                          .library(name: "Alamofire", targets: ["Alamofire"]),
                          .library(name: "AlamofireDynamic", type: .dynamic, targets: ["Alamofire"])
                      ],
                      targets: [.target(name: "Alamofire",
                                        path: "Source",
                                        exclude: ["Info.plist"],
                                        resources: [.process("PrivacyInfo.xcprivacy")],
                                        linkerSettings: [.linkedFramework("CFNetwork",
                                                                          .when(platforms: [.iOS,
                                                                                            .macOS,
                                                                                            .tvOS,
                                                                                            .watchOS]))]),
                                .testTarget(name: "AlamofireTests",
                                            dependencies: ["Alamofire"],
                                            path: "Tests",
                                            exclude: ["Info.plist", "Test Plans"],
                                            resources: [.process("Resources")])],
                      swiftLanguageVersions: [.v5])
  • library 패키지다
  • products로 인해, 외부에서 import Alamofire 가 가능하다
  • 해당 패키지에서 Source/ 에 패키지의 구현내용이 존재한다
  • 테스트스위트는 Alamofire 타겟의 코드에 접근할 수 있다(dependencies)
  • AlamofireDynamic은 동적 라이브러리다

3) sendbird-chat-sdk-ios

https://github.com/sendbird/sendbird-chat-sdk-ios

import PackageDescription

let package = Package(
    name: "SendbirdChatSDK",
    platforms: [.iOS(.v12)],
    products: [
        .library(
            name: "SendbirdChatSDK",
            targets: ["SendbirdChatSDK"]
        ),
    ],
    dependencies: [],
    targets: [
        .binaryTarget(
            name: "SendbirdChatSDK",
            url: "<https://github.com/sendbird/sendbird-chat-sdk-ios/releases/download/4.19.6/SendbirdChatSDK.xcframework.zip>",
            checksum: "3213b2f1c2a8ab32fdb6aeb615100cb2c175be4a7568c68fe9dbdbaaca576c02"
        ),
    ]
)
  • library 패키지다
  • products로 인해, 외부에서 import SendbirdChatSDK 가 가능하다
  • targets이 .binaryTarget → 해당 주소에서 얻은 바이너리 파일이 패키지에 이용된다
    • 즉, 내부 구현은 알 수 없다

 

마무리

가장 기초적인 내용이지만 한 번 정리해두면 좋을 것 같은 생각이 들어서 정리해봤는데

패키지 관련 부분에 대한 정리는 추후 라이브러리를 분석해야 하는 경우 도움이 많이 될 것 같다

'iOS+ > Swift' 카테고리의 다른 글

Transferable  (0) 2024.09.08