Swift 프로젝트의 의존성 관리: SPM 활용 💫

콘텐츠 대표 이미지 - Swift 프로젝트의 의존성 관리: SPM 활용 💫

 

 

2025년 3월 기준 최신 정보로 알아보는 Swift Package Manager의 모든 것!

📱 Swift 개발자라면 꼭 알아야 할 의존성 관리의 핵심! 📱

안녕하세요, Swift 개발자 여러분! 오늘은 iOS/macOS 앱 개발할 때 정말 중요한 의존성 관리 도구인 Swift Package Manager(SPM)에 대해 깊이 파헤쳐 볼게요. 코드 몇 줄 작성하다가 "아 이거 라이브러리 써야 하는데..." 하고 고민했던 경험, 다들 있으시죠? ㅋㅋㅋ 그럴 때마다 의존성 관리 도구의 소중함을 느끼게 되는 거 아니겠어요? 😎

2025년 현재, SPM은 이제 iOS/macOS 개발의 필수 도구가 되었어요. CocoaPods, Carthage 같은 도구들도 있지만, Apple이 직접 만든 SPM의 인기가 날로 높아지고 있답니다! 재능넷에서도 Swift 관련 재능을 거래할 때 SPM 활용 능력이 중요한 스킬로 자리잡고 있어요.

🧩 목차

  1. SPM이란 무엇인가요?
  2. SPM vs 다른 의존성 관리 도구들
  3. SPM 시작하기: 기본 설정 및 사용법
  4. 패키지 추가 및 관리하기
  5. 자신만의 패키지 만들기
  6. SPM의 고급 기능 활용하기
  7. 실전 프로젝트에서의 SPM 활용 사례
  8. SPM 관련 문제 해결 및 팁
  9. SPM의 미래와 전망
  10. 결론 및 요약
SPM Swift 프로젝트 완성된 앱 Swift Package Manager 워크플로우 SPM으로 의존성을 관리하여 효율적인 Swift 앱 개발 프로세스 구축

1. SPM이란 무엇인가요? 🤔

Swift Package Manager(SPM)는 Swift 코드의 배포와 의존성을 관리하기 위한 Apple의 공식 도구예요. 2016년에 처음 소개된 이후로 꾸준히 발전해왔고, 2025년 현재는 정말 강력한 기능들을 제공하고 있답니다.

SPM의 핵심 개념은 간단해요:

  1. 패키지(Package): 소스 코드와 매니페스트 파일(Package.swift)을 포함하는 코드 모음
  2. 의존성(Dependency): 프로젝트에서 사용하는 외부 패키지
  3. 타겟(Target): 빌드할 모듈, 테스트 등을 정의
  4. 제품(Product): 타겟에서 빌드된 라이브러리나 실행 파일

SPM은 진짜 개발자 친화적이에요! 🥰 복잡한 설정 없이도 바로 사용할 수 있고, Xcode와의 통합도 완벽하게 이루어져 있어서 iOS/macOS 개발자들에게 정말 편리하답니다. "아 의존성 관리 너무 귀찮아..." 했던 분들도 SPM을 쓰면 "와 이거 진짜 쉽네?" 하실 거예요. ㅋㅋㅋ

💡 알고 계셨나요?

SPM은 Swift 3.0부터 Swift 언어와 함께 제공되기 시작했어요. 2025년 현재는 Swift 6.x와 함께 더욱 강력해진 기능들을 제공하고 있답니다. 특히 최근에는 바이너리 의존성 지원, 리소스 번들 관리 등 정말 유용한 기능들이 추가되었어요!

2. SPM vs 다른 의존성 관리 도구들 🥊

iOS/macOS 개발에서 의존성 관리 도구라고 하면 SPM 말고도 CocoaPods, Carthage 같은 친구들이 있죠. 2025년 현재, 이 도구들 간의 차이점과 각각의 장단점을 비교해볼게요!

SPM의 장점 👍

  1. Apple의 공식 지원 - Xcode와의 완벽한 통합
  2. 별도의 설치 과정이 필요 없음 (Swift와 함께 제공)
  3. 빠른 빌드 속도와 효율적인 캐싱
  4. 분산형 의존성 관리 (중앙 저장소에 의존하지 않음)
  5. Swift 6.x에서 추가된 강력한 새 기능들

SPM의 단점 👎

  1. Objective-C 전용 라이브러리 지원이 제한적
  2. 일부 고급 사용자 정의 기능이 다른 도구에 비해 제한적

CocoaPods vs SPM

CocoaPods는 오랫동안 iOS 개발의 표준 의존성 관리 도구였어요. 하지만 2025년에는 많은 개발자들이 SPM으로 이동하고 있어요. CocoaPods는 중앙 집중식 저장소를 사용하고, Podfile이라는 별도의 설정 파일이 필요해요. 반면 SPM은 Xcode 프로젝트에 직접 통합되고, 별도의 설치나 설정 파일이 필요 없답니다.

CocoaPods의 장점은 방대한 라이브러리 생태계와 오랜 역사로 인한 안정성이에요. 하지만 빌드 시간이 길고, 프로젝트 구조를 변경한다는 단점이 있죠.

Carthage vs SPM

Carthage는 "분산형" 의존성 관리자로, 프로젝트 구조를 변경하지 않는다는 철학을 가지고 있어요. SPM과 비슷한 접근 방식이지만, 별도로 설치해야 하고 Cartfile이라는 설정 파일이 필요해요.

Carthage의 장점은 프로젝트 구조를 건드리지 않는다는 점이지만, 수동 설정이 많이 필요하고 통합 과정이 복잡하다는 단점이 있어요.

결론적으로, 2025년 현재 SPM은 Apple의 지속적인 개선으로 대부분의 iOS/macOS 프로젝트에서 가장 추천되는 의존성 관리 도구가 되었어요. "어떤 의존성 관리 도구 써야 할지 고민된다면 일단 SPM부터 시작해보세요!"라고 재능넷의 많은 iOS 개발자들이 조언하고 있답니다. 😊

의존성 관리 도구 비교 Swift Package Manager Apple 공식 지원 Xcode 통합 별도 설치 불필요 빠른 빌드 속도 ObjC 지원 제한적 CocoaPods 방대한 라이브러리 안정적인 생태계 별도 설치 필요 느린 빌드 속도 프로젝트 구조 변경 Carthage 구조 변경 없음 분산형 관리 별도 설치 필요 수동 설정 필요 복잡한 통합 과정 2025년 현재, SPM은 대부분의 iOS/macOS 프로젝트에서 가장 추천되는 의존성 관리 도구입니다.

3. SPM 시작하기: 기본 설정 및 사용법 🚀

자, 이제 SPM을 실제로 사용해볼 차례예요! SPM은 Xcode에 기본으로 통합되어 있어서 정말 쉽게 시작할 수 있답니다. 2025년 기준 Xcode 16.x를 사용한다고 가정하고 설명할게요.

Xcode에서 SPM 사용하기

1. 새 프로젝트에서 SPM 사용하기

  1. Xcode를 열고 새 프로젝트를 생성합니다.
  2. 프로젝트 생성 후, 왼쪽 프로젝트 네비게이터에서 프로젝트 파일을 선택합니다.
  3. "Package Dependencies" 탭을 클릭합니다.
  4. "+" 버튼을 클릭하여 새 패키지를 추가합니다.
  5. 패키지의 GitHub URL을 입력하고 "Add Package" 버튼을 클릭합니다.

진짜 간단하죠? ㅋㅋㅋ 이게 SPM의 매력이에요! 🥰

2. 기존 프로젝트에 SPM 추가하기

기존 프로젝트에 SPM을 추가하는 방법도 거의 동일해요:

  1. 프로젝트 파일을 선택하고 "Package Dependencies" 탭으로 이동
  2. "+" 버튼을 클릭하여 새 패키지 추가
  3. 패키지 URL 입력 및 버전 선택
  4. 타겟에 라이브러리 추가

🔍 패키지 URL 예시

대부분의 Swift 패키지는 GitHub에 호스팅되어 있어요. URL 형식은 다음과 같습니다:

https://github.com/사용자이름/저장소이름.git

예를 들어, Alamofire를 추가하려면:

https://github.com/Alamofire/Alamofire.git

패키지 버전 관리

SPM에서는 세마틱 버전 관리(Semantic Versioning)를 사용해요. 패키지를 추가할 때 다음과 같은 버전 옵션을 선택할 수 있어요:

  1. Up to Next Major: 다음 메이저 버전 전까지 자동 업데이트 (예: 5.0.0 ~ 5.9.9)
  2. Up to Next Minor: 다음 마이너 버전 전까지 자동 업데이트 (예: 5.1.0 ~ 5.1.9)
  3. Range: 특정 버전 범위 지정
  4. Exact: 정확한 버전 지정
  5. Branch: 특정 브랜치 사용
  6. Commit: 특정 커밋 사용

"어떤 버전 옵션을 선택해야 할지 모르겠다!" 싶으시면, 보통은 'Up to Next Minor'가 안전한 선택이에요. 이렇게 하면 버그 수정은 자동으로 받을 수 있지만, 호환성을 깨는 변경사항은 피할 수 있거든요. 👍

💻 실습: 첫 번째 SPM 의존성 추가하기

인기 있는 네트워킹 라이브러리인 Alamofire를 추가해볼게요:

  1. Xcode에서 프로젝트를 열고 프로젝트 파일 선택
  2. "Package Dependencies" 탭 클릭
  3. "+" 버튼 클릭
  4. 검색창에 https://github.com/Alamofire/Alamofire.git 입력
  5. "Up to Next Minor" 버전 규칙 선택
  6. "Add Package" 클릭
  7. 타겟 선택 화면에서 Alamofire를 추가할 타겟 선택
  8. "Add Package" 클릭하여 완료

짜잔! 🎉 이제 프로젝트에서 Alamofire를 사용할 수 있어요. 다음과 같이 import하여 사용하면 됩니다:

import Alamofire

// 네트워크 요청 예시
AF.request("https://api.example.com/data").responseJSON { response in
    debugPrint(response)
}

정말 쉽죠? ㅋㅋㅋ 이렇게 SPM을 사용하면 외부 라이브러리를 빠르고 간편하게 추가할 수 있어요!

4. 패키지 추가 및 관리하기 📦

이제 SPM을 사용해서 다양한 패키지를 추가하고 관리하는 방법을 더 자세히 알아볼게요. 2025년 현재 정말 많은 Swift 라이브러리들이 SPM을 지원하고 있어요!

인기 있는 Swift 패키지들

SPM으로 쉽게 추가할 수 있는 인기 있는 Swift 패키지들을 소개할게요:

  1. Alamofire: 네트워킹 라이브러리
  2. SwiftyJSON: JSON 파싱 라이브러리
  3. Kingfisher: 이미지 다운로드 및 캐싱 라이브러리
  4. SnapKit: 오토레이아웃 DSL
  5. Realm: 모바일 데이터베이스
  6. SwiftUI-Introspect: SwiftUI 뷰 접근 라이브러리
  7. Lottie: 애니메이션 라이브러리
  8. Firebase: Google의 앱 개발 플랫폼

여러 패키지 동시에 관리하기

실제 프로젝트에서는 여러 패키지를 동시에 사용하는 경우가 많아요. SPM은 이런 복잡한 의존성 관계도 쉽게 관리해줍니다.

패키지 간 의존성 충돌이 발생하면 어떻게 될까요? SPM은 자동으로 의존성 해결 알고리즘을 사용하여 가능한 한 모든 패키지가 함께 작동할 수 있는 버전을 찾아줍니다. 만약 해결할 수 없는 충돌이 있다면 오류 메시지를 표시하고, 어떤 패키지 간에 충돌이 있는지 알려줘요.

패키지 업데이트하기

패키지를 최신 버전으로 업데이트하는 방법은 정말 간단해요:

  1. Xcode의 "File" 메뉴에서 "Packages" > "Update to Latest Package Versions" 선택
  2. 또는 프로젝트 네비게이터에서 "Package Dependencies"를 우클릭하고 "Update Packages" 선택

특정 패키지만 업데이트하고 싶다면:

  1. 프로젝트 설정의 "Package Dependencies" 탭으로 이동
  2. 업데이트하려는 패키지 선택
  3. 버전 규칙 옆의 "Update" 버튼 클릭

패키지 제거하기

더 이상 필요하지 않은 패키지를 제거하는 방법도 간단해요:

  1. 프로젝트 설정의 "Package Dependencies" 탭으로 이동
  2. 제거하려는 패키지 선택
  3. "-" 버튼 클릭

패키지를 제거하면 해당 패키지에 의존하는 다른 패키지가 있는지 SPM이 자동으로 확인하고, 필요한 경우 경고를 표시해줍니다. 이런 세심한 기능들 덕분에 의존성 관리가 정말 편리해졌어요! 😊

인기 Swift 패키지 생태계 SPM Alamofire SwiftyJSON Kingfisher SnapKit Realm SwiftUI Introspect Lottie Firebase Swift Package Manager로 쉽게 관리할 수 있는 인기 라이브러리들

5. 자신만의 패키지 만들기 🛠️

지금까지는 다른 사람들이 만든 패키지를 사용하는 방법을 알아봤어요. 이제는 나만의 Swift 패키지를 만드는 방법을 알아볼게요! 자신의 코드를 패키지로 만들면 여러 프로젝트에서 재사용하기 쉽고, 다른 개발자들과 공유할 수도 있답니다.

새 패키지 생성하기

Xcode에서 새 패키지를 만드는 방법은 다음과 같아요:

  1. Xcode 메뉴에서 "File" > "New" > "Package..." 선택
  2. 패키지 이름과 저장 위치 지정
  3. 패키지 타입 선택 (Library, Executable, Plugin 등)
  4. "Create" 버튼 클릭

이렇게 하면 기본 구조를 가진 새 패키지가 생성돼요:

MyPackage/
├── .gitignore
├── .swiftpm/
├── Package.swift
├── README.md
├── Sources/
│   └── MyPackage/
│       └── MyPackage.swift
└── Tests/
    └── MyPackageTests/
        └── MyPackageTests.swift

Package.swift 파일 이해하기

Package.swift는 패키지의 매니페스트 파일로, 패키지의 구성과 의존성을 정의해요. 기본적인 Package.swift 파일은 다음과 같아요:

// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "MyPackage",
    platforms: [
        .iOS(.v15),
        .macOS(.v12)
    ],
    products: [
        .library(
            name: "MyPackage",
            targets: ["MyPackage"]),
    ],
    dependencies: [
        // 여기에 의존성 추가
        .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.8.0"),
    ],
    targets: [
        .target(
            name: "MyPackage",
            dependencies: ["Alamofire"]),
        .testTarget(
            name: "MyPackageTests",
            dependencies: ["MyPackage"]),
    ]
)

각 섹션의 의미는 다음과 같아요:

  1. swift-tools-version: 패키지를 빌드하는 데 필요한 Swift 버전
  2. name: 패키지 이름
  3. platforms: 지원하는 플랫폼 및 최소 버전
  4. products: 패키지가 제공하는 라이브러리나 실행 파일
  5. dependencies: 패키지가 의존하는 외부 패키지
  6. targets: 패키지의 빌드 대상(모듈)

패키지에 코드 추가하기

이제 Sources/MyPackage 디렉토리에 Swift 파일을 추가하여 패키지의 기능을 구현할 수 있어요. 예를 들어, 간단한 문자열 유틸리티 함수를 추가해볼게요:

// Sources/MyPackage/StringUtils.swift
import Foundation

public extension String {
    /// 문자열의 첫 글자만 대문자로 변환합니다.
    func capitalizeFirstLetter() -> String {
        return prefix(1).capitalized + dropFirst()
    }
    
    /// 문자열에서 공백을 제거합니다.
    func removeWhitespace() -> String {
        return components(separatedBy: .whitespaces).joined()
    }
}

public 키워드를 사용하여 함수나 타입을 외부에 노출시킬 수 있어요. public으로 선언되지 않은 항목은 패키지 내부에서만 사용 가능합니다.

패키지 테스트하기

Tests 디렉토리에 테스트 코드를 작성하여 패키지의 기능을 테스트할 수 있어요:

// Tests/MyPackageTests/StringUtilsTests.swift
import XCTest
@testable import MyPackage

final class StringUtilsTests: XCTestCase {
    func testCapitalizeFirstLetter() {
        XCTAssertEqual("hello".capitalizeFirstLetter(), "Hello")
        XCTAssertEqual("world".capitalizeFirstLetter(), "World")
    }
    
    func testRemoveWhitespace() {
        XCTAssertEqual("hello world".removeWhitespace(), "helloworld")
        XCTAssertEqual("swift package manager".removeWhitespace(), "swiftpackagemanager")
    }
}

테스트를 실행하려면 Xcode의 Product > Test 메뉴를 사용하거나, 터미널에서 swift test 명령을 실행하면 돼요.

패키지 배포하기

패키지를 완성했다면, GitHub 같은 Git 저장소에 업로드하여 다른 사람들과 공유할 수 있어요:

  1. GitHub에 새 저장소 생성
  2. 로컬 패키지 디렉토리를 Git 저장소로 초기화: git init
  3. 변경사항 커밋: git add . && git commit -m "Initial commit"
  4. 원격 저장소 추가: git remote add origin [저장소 URL]
  5. 변경사항 푸시: git push -u origin main

이제 다른 개발자들이 당신의 패키지를 SPM으로 쉽게 추가할 수 있어요! 🎉

💡 패키지 버전 관리 팁

패키지를 배포할 때는 세마틱 버저닝(Semantic Versioning)을 따르는 것이 좋아요:

  1. MAJOR: 호환성을 깨는 변경사항
  2. MINOR: 이전 버전과 호환되는 새 기능 추가
  3. PATCH: 버그 수정

Git 태그를 사용하여 버전을 관리하세요: git tag 1.0.0 && git push --tags

자신만의 패키지를 만들어 재능넷에서 공유하면 다른 개발자들에게 큰 도움이 될 수 있어요! 특히 iOS/macOS 개발 관련 재능을 거래할 때, 자신만의 패키지 라이브러리를 보유하고 있다면 더 높은 가치를 인정받을 수 있답니다. 😊

6. SPM의 고급 기능 활용하기 🔍

이제 SPM의 기본적인 사용법을 마스터했으니, 더 고급 기능들을 살펴볼게요! 2025년 현재 SPM은 정말 다양한 고급 기능을 제공하고 있어요.

조건부 의존성

플랫폼이나 Swift 버전에 따라 다른 의존성을 사용해야 할 때가 있죠. SPM에서는 조건부 의존성을 설정할 수 있어요:

// Package.swift
import PackageDescription

let package = Package(
    name: "MyPackage",
    platforms: [.iOS(.v15), .macOS(.v12)],
    products: [
        .library(name: "MyPackage", targets: ["MyPackage"]),
    ],
    dependencies: [
        .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.8.0"),
        .package(url: "https://github.com/apple/swift-log.git", from: "1.5.0"),
    ],
    targets: [
        .target(
            name: "MyPackage",
            dependencies: [
                .product(name: "Alamofire"),
                .product(name: "Logging", condition: .when(platforms: [.ios, .macOS])),
            ]),
        .testTarget(
            name: "MyPackageTests",
            dependencies: ["MyPackage"]),
    ]
)

이 예제에서는 iOS와 macOS 플랫폼에서만 Logging 의존성을 사용하도록 설정했어요.

리소스 번들 관리

SPM은 이제 리소스 파일(이미지, JSON, 폰트 등)을 패키지에 포함시킬 수 있어요. 이는 2025년 현재 SPM의 가장 유용한 기능 중 하나예요!

// Package.swift
import PackageDescription

let package = Package(
    name: "MyUIKit",
    platforms: [.iOS(.v15)],
    products: [
        .library(name: "MyUIKit", targets: ["MyUIKit"]),
    ],
    targets: [
        .target(
            name: "MyUIKit",
            resources: [
                .process("Resources/Images"),
                .process("Resources/Fonts"),
                .copy("Resources/Data/config.json")
            ]),
    ]
)

리소스에는 두 가지 처리 방식이 있어요:

  1. .process: 리소스를 처리하고 최적화 (이미지 등)
  2. .copy: 리소스를 그대로 복사

패키지에서 리소스에 접근하는 방법은 다음과 같아요:

import Foundation
import MyUIKit

// 번들에서 이미지 로드
if let imageURL = Bundle.module.url(forResource: "logo", withExtension: "png") {
    // 이미지 사용
}

// JSON 파일 로드
if let configURL = Bundle.module.url(forResource: "config", withExtension: "json") {
    let data = try Data(contentsOf: configURL)
    // JSON 데이터 처리
}

바이너리 의존성

2025년 SPM은 바이너리 형태의 의존성도 지원해요. 소스 코드를 공개하지 않고도 패키지를 배포할 수 있는 기능이죠!

// Package.swift
import PackageDescription

let package = Package(
    name: "MyApp",
    platforms: [.iOS(.v15)],
    products: [
        .library(name: "MyApp", targets: ["MyApp"]),
    ],
    dependencies: [
        .package(url: "https://example.com/analytics-sdk.git", from: "1.0.0"),
    ],
    targets: [
        .target(
            name: "MyApp",
            dependencies: [
                .product(name: "AnalyticsSDK"),
            ]),
    ]
)

바이너리 패키지는 XCFramework 형식으로 제공되며, 소스 코드 없이도 여러 플랫폼과 아키텍처를 지원할 수 있어요.

플러그인 시스템

SPM은 빌드 프로세스를 확장할 수 있는 플러그인 시스템도 제공해요. 이를 통해 코드 생성, 문서화, 린팅 등의 작업을 자동화할 수 있어요.

// Package.swift
import PackageDescription

let package = Package(
    name: "MyApp",
    platforms: [.iOS(.v15)],
    products: [
        .library(name: "MyApp", targets: ["MyApp"]),
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.3.0"),
    ],
    targets: [
        .target(name: "MyApp"),
    ]
)

이 예제에서는 Swift-DocC 플러그인을 사용하여 API 문서를 자동으로 생성해요.

로컬 패키지 개발

다른 패키지를 개발하면서 동시에 테스트해야 할 때는 로컬 패키지 경로를 사용할 수 있어요:

// Package.swift
import PackageDescription

let package = Package(
    name: "MyApp",
    dependencies: [
        .package(path: "../MyLocalPackage"),
    ],
    targets: [
        .target(
            name: "MyApp",
            dependencies: ["MyLocalPackage"]),
    ]
)

이 기능은 여러 패키지를 동시에 개발할 때 정말 유용해요! 로컬에서 변경사항을 바로 테스트할 수 있거든요.

이런 고급 기능들을 활용하면 SPM으로 정말 복잡한 의존성 관리도 효율적으로 할 수 있어요. 특히 대규모 프로젝트에서는 이런 기능들이 개발 생산성을 크게 향상시킬 수 있답니다! 👍

🌟 SPM 사용 시 꿀팁!

  1. Package.resolved 파일을 Git에 커밋하세요 - 팀원 모두가 동일한 의존성 버전을 사용할 수 있어요.
  2. 의존성 문제가 발생하면 Xcode의 "File" > "Packages" > "Reset Package Caches"를 시도해보세요.
  3. 큰 프로젝트는 모듈화하여 여러 패키지로 나누면 빌드 시간을 단축할 수 있어요.
  4. Package.swift 파일에 주석을 잘 작성하여 의존성의 용도를 문서화하세요.
  5. 특정 커밋이나 브랜치를 참조하는 것보다 태그된 버전을 사용하는 것이 안정적이에요.

이런 팁들을 활용하면 SPM으로 의존성 관리를 더 효율적으로 할 수 있어요! 😊

7. 실전 프로젝트에서의 SPM 활용 사례 💼

지금까지 SPM의 다양한 기능들을 살펴봤는데요, 이제 실제 프로젝트에서 SPM을 어떻게 활용하는지 몇 가지 사례를 통해 알아볼게요!

사례 1: 모듈화된 iOS 앱 아키텍처

대규모 iOS 앱을 개발할 때 모듈화 아키텍처를 구현하는 데 SPM이 정말 유용해요. 앱을 여러 모듈로 나누면 빌드 시간을 단축하고 코드 재사용성을 높일 수 있답니다.

예를 들어, 쇼핑 앱을 다음과 같이 모듈화할 수 있어요:

  1. AppCore: 앱의 핵심 기능 및 공통 유틸리티
  2. NetworkLayer: API 통신 관련 코드
  3. UIComponents: 재사용 가능한 UI 컴포넌트
  4. ProductCatalog: 상품 목록 및 상세 화면
  5. ShoppingCart: 장바구니 기능
  6. UserProfile: 사용자 프로필 관리

각 모듈을 별도의 SPM 패키지로 만들고, 메인 앱에서 이들을 의존성으로 추가하면 돼요. 이렇게 하면 각 모듈을 독립적으로 개발하고 테스트할 수 있어요.

// 메인 앱의 Package.swift
import PackageDescription

let package = Package(
    name: "ShoppingApp",
    platforms: [.iOS(.v15)],
    products: [
        .library(name: "ShoppingApp", targets: ["ShoppingApp"]),
    ],
    dependencies: [
        .package(path: "../AppCore"),
        .package(path: "../NetworkLayer"),
        .package(path: "../UIComponents"),
        .package(path: "../ProductCatalog"),
        .package(path: "../ShoppingCart"),
        .package(path: "../UserProfile"),
    ],
    targets: [
        .target(
            name: "ShoppingApp",
            dependencies: [
                "AppCore",
                "NetworkLayer",
                "UIComponents",
                "ProductCatalog",
                "ShoppingCart",
                "UserProfile"
            ]),
    ]
)

"와, 이렇게 모듈화하니까 진짜 개발이 편해졌어요!" 라는 말을 재능넷의 iOS 개발자 커뮤니티에서 자주 들을 수 있어요. 특히 여러 명이 함께 개발할 때 각자 담당 모듈에 집중할 수 있어 효율적이죠. ㅎㅎ

사례 2: 크로스 플랫폼 프레임워크 개발

iOS, macOS, watchOS, tvOS 등 여러 Apple 플랫폼에서 동작하는 크로스 플랫폼 프레임워크를 개발할 때도 SPM이 매우 유용해요.

// 크로스 플랫폼 프레임워크의 Package.swift
import PackageDescription

let package = Package(
    name: "AnalyticsKit",
    platforms: [
        .iOS(.v15),
        .macOS(.v12),
        .watchOS(.v8),
        .tvOS(.v15)
    ],
    products: [
        .library(name: "AnalyticsKit", targets: ["AnalyticsKit"]),
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-log.git", from: "1.5.0"),
    ],
    targets: [
        .target(
            name: "AnalyticsKit",
            dependencies: [
                .product(name: "Logging", package: "swift-log")
            ]),
        .testTarget(
            name: "AnalyticsKitTests",
            dependencies: ["AnalyticsKit"]),
    ]
)

플랫폼별 코드는 조건부 컴파일을 사용하여 구현할 수 있어요:

import Foundation

public struct Analytics {
    public static func trackEvent(_ name: String) {
        // 공통 코드
        
        #if os(iOS)
        // iOS 전용 코드
        #elseif os(macOS)
        // macOS 전용 코드
        #elseif os(watchOS)
        // watchOS 전용 코드
        #elseif os(tvOS)
        // tvOS 전용 코드
        #endif
    }
}

이렇게 하면 하나의 코드베이스로 여러 플랫폼을 지원할 수 있어요! 👍

사례 3: 서버 사이드 Swift 프로젝트

SPM은 iOS 앱뿐만 아니라 서버 사이드 Swift 프로젝트에서도 널리 사용돼요. Vapor나 Kitura 같은 Swift 웹 프레임워크는 SPM을 기본 의존성 관리 도구로 사용하고 있답니다.

// Vapor 프로젝트의 Package.swift
import PackageDescription

let package = Package(
    name: "MyAPI",
    platforms: [.macOS(.v12)],
    dependencies: [
        .package(url: "https://github.com/vapor/vapor.git", from: "4.76.0"),
        .package(url: "https://github.com/vapor/fluent.git", from: "4.8.0"),
        .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.7.0"),
    ],
    targets: [
        .executableTarget(
            name: "App",
            dependencies: [
                .product(name: "Vapor", package: "vapor"),
                .product(name: "Fluent", package: "fluent"),
                .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"),
            ]),
        .testTarget(
            name: "AppTests",
            dependencies: [
                .target(name: "App"),
                .product(name: "XCTVapor", package: "vapor"),
            ]),
    ]
)

서버 사이드 Swift 개발은 2025년 현재 점점 더 인기를 얻고 있어요. iOS 개발자가 백엔드도 Swift로 개발할 수 있다니, 정말 편리하죠? ㅋㅋㅋ

사례 4: 내부 라이브러리 관리

큰 회사나 팀에서는 내부 라이브러리를 SPM으로 관리하는 경우가 많아요. 비공개 Git 저장소를 사용하여 회사 내부에서만 사용하는 코드를 패키지로 만들어 관리할 수 있답니다.

// 내부 라이브러리를 사용하는 Package.swift
import PackageDescription

let package = Package(
    name: "CompanyApp",
    platforms: [.iOS(.v15)],
    dependencies: [
        // 공개 패키지
        .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.8.0"),
        
        // 회사 내부 패키지 (비공개 저장소)
        .package(url: "https://github.company.com/internal/UIKit.git", from: "2.0.0"),
        .package(url: "https://github.company.com/internal/Analytics.git", from: "1.5.0"),
    ],
    targets: [
        .target(
            name: "CompanyApp",
            dependencies: [
                "Alamofire",
                "CompanyUIKit",
                "CompanyAnalytics"
            ]),
    ]
)

비공개 저장소에 접근하기 위해서는 SSH 키나 인증 토큰을 설정해야 할 수도 있어요.

이런 실전 사례들을 보면 SPM이 얼마나 다양한 상황에서 유용하게 활용될 수 있는지 알 수 있어요! 여러분의 프로젝트에도 이런 패턴을 적용해보세요. 😊

모듈화된 iOS 앱 아키텍처 메인 앱 AppCore NetworkLayer UIComponents ProductCatalog ShoppingCart UserProfile 외부 의존성 (Alamofire, SwiftyJSON, Kingfisher 등) 각 모듈은 독립적인 Swift Package로 관리되며, 필요에 따라 서로 의존할 수 있습니다.

8. SPM 관련 문제 해결 및 팁 🛠️

SPM을 사용하다 보면 가끔 문제가 발생할 수 있어요. 2025년 현재 개발자들이 자주 마주치는 문제들과 그 해결 방법을 알아볼게요!

일반적인 문제와 해결 방법

1. 패키지 해결 실패

"Unable to resolve package dependencies" 오류가 발생할 때:

  1. Xcode 메뉴에서 "File" > "Packages" > "Reset Package Caches" 선택
  2. Xcode를 재시작
  3. 인터넷 연결 확인
  4. 패키지 URL이 올바른지 확인
  5. Git 저장소에 접근 권한이 있는지 확인 (비공개 저장소의 경우)

"이거 진짜 짜증나는 오류인데, 대부분 캐시 리셋으로 해결돼요. ㅋㅋㅋ" - 재능넷 iOS 개발자

2. 버전 호환성 문제

패키지 간 버전 충돌이 발생할 때:

  1. Package.resolved 파일 확인하여 어떤 패키지들이 충돌하는지 파악
  2. 특정 패키지의 버전 범위 조정 (더 제한적인 범위로)
  3. 일부 패키지를 업데이트하거나 다운그레이드
// 버전 범위 제한 예시
dependencies: [
    .package(url: "https://github.com/Alamofire/Alamofire.git", "5.6.0"..<"5.8.0"),
]

3. 빌드 시간이 너무 오래 걸림

SPM 패키지가 많아지면 빌드 시간이 길어질 수 있어요:

  1. 자주 사용하지 않는 패키지 제거
  2. 바이너리 의존성 사용 고려
  3. Xcode의 "File" > "Packages" > "Resolve Package Versions" 실행하여 Package.resolved 파일 업데이트
  4. 프로젝트를 모듈화하여 증분 빌드 활용

4. 특정 브랜치나 커밋 사용 시 문제

브랜치나 커밋을 참조할 때 발생하는 문제:

  1. 가능하면 태그된 버전 사용 (더 안정적)
  2. 브랜치 이름이나 커밋 해시가 정확한지 확인
  3. 원격 저장소가 최신 상태인지 확인
// 브랜치 참조 예시
dependencies: [
    .package(url: "https://github.com/example/package.git", .branch("develop")),
]

// 커밋 참조 예시
dependencies: [
    .package(url: "https://github.com/example/package.git", .revision("abcdef12345")),
]

SPM 성능 최적화 팁

SPM을 더 효율적으로 사용하기 위한 팁들을 알아볼게요:

  1. 의존성 최소화: 꼭 필요한 패키지만 추가하고, 사용하지 않는 패키지는 제거하세요.
  2. 정확한 버전 범위 지정: 너무 넓은 버전 범위는 해결 시간을 늘릴 수 있어요.
  3. Package.resolved 파일 공유: 팀원들과 이 파일을 공유하여 동일한 의존성 버전을 사용하세요.
  4. 로컬 캐싱 활용: 자주 사용하는 패키지는 로컬에 캐시되어 있어 오프라인에서도 사용 가능해요.
  5. 모듈화 전략 수립: 큰 프로젝트는 여러 패키지로 나누어 빌드 시간을 단축하세요.