2024. 12. 27. 22:06ㆍSwift, iOS Foundation
1. Swift Package와 SPM (Swift Pacakge Manager)
Swift Package는 Swift에서 모듈식으로 코드를 관리하고 배포할 수 있도록 제공하는 개념입니다.
Swift Package는 단순히 이번 글에서 만들고자 하는 라이브러리뿐만 아니라, 프레임워크, 실행가능한 모듈로서 분리, Swift 매크로 등 다양한 코드 묶음을 만들 수 있는데요.
이렇게 작은 묶음으로써 코드를 프로젝트 내에 분리하는 이유는 그 무엇보다 "다른 여러 프로젝트에서 재사용되는 코드를 효율적(의존성 관리, 버전 관리 등)으로 활용할 수 있다는 점"이 가장 큽니다.
*흔히 사용하던 서버 통신 라이브러리 Alamofire와 Moya, 이미지 캐싱 라이브러리 Kingfisher, AutoLayout을 도와주는 SnapKit 등이 모두 Package에 해당된다고 떠오르네요!
그리고 이 Swift Package들을 프로젝트에 추가, 관리하고 생성, 배포, 통합까지 하는데 사용할 수 있는 Apple의 공식 툴이 바로 SPM (Swift Package Manger)입니다.
(항상 Xcode에서 라이브러리 추가하는 것만 사용해서 몰랐는데.. 생성/배포/통합 툴 역시도 SPM이 할 수 있는 거였다니...)
*옛날에는 CocoaPods라던지 Carthage 같은 다양한 의존성 관리 도구를 통해 라이브러리를 관리했었는데, 지금은 거의 SPM을 Apple에서 공식적으로 지원하는 1st party라는 점 때문에 많이 쓰는 것 같아요..!
**그리고 이 Swift Package Manager 역시 오픈소스로 공개되어 있다는 점도 신기하네요...!
아무튼 이번 글에서는 이 SPM을 통해 직접 Swift Package를 만들고, 배포까지 해보는 과정에 대해 소개해보고자 합니다.
어떤 라이브러리를 만들고자 하는지, 패키지 코드는 어떤 식으로 작성하는지, 패키지 코드에서 신경 써야 할 부분은 무엇인지, 자동화 CI/CD는 어떻게 연결하는지 등의 내용은 차차 다음 글들에서 쭉 이어가 볼게요!
2. Package 배포 과정 (Publishing Package)
자 그럼 이제 본격적으로 직접 Package를 만들기 위해서 Xcode를 들어가 봅시다.
*Swift Package를 만든다는 것은 외부 배포용 모듈을 만든다는 개념일 수도 / 내부 코드를 분리하기 위한 모듈을 만든다는 개념일 수도 있답니다!
상단바에 있는 File-Package를 누르면, 새로운 Swift Package 파일을 생성할 수 있어요.
이렇게 Xcode를 이용하지 않고 Command Line Tool을 이용한다면, swift package init 명령어로도 생성할 수 있습니다.
아무튼 저는 라이브러리를 만들 거니까 라이브러리를 선택해주고,
항상 프로젝트를 만들던 것처럼 파일 위치, 테스트 방법 (XCTest, Swift Testing), 이름 등을 정해주면 생성이 완료됩니다.
생성된 파일 구조를 간단하게 살펴보겠습니다.
📁 Package.swift : SPM이 Package를 빌드하고 의존성을 관리하는 데 사용되는 패키지의 핵심 파일 (name, products, targets 등이 기본적으로 설정되어 있음) [아래에서 해당 파일은 더 자세하게 뜯어볼게요 ^__^]
📦 Sources : 패키지에서 사용되는 실제 소스 코드가 포함되는 공간
📦 Tests : 패키지의 테스트 코드를 포함하고 있는 공간 (Swift 6부터는 기존 XCTest와 더불어 Swift Testing도 지원하고 있음)
결국 정리해 보면, 패키지로 활용될 코드는 Sources 부분에 작성해주고 / Tests 부분에 테스트 코드를 작성해 코드가 정상적으로 돌아가는지 확인해주고 / Package를 통해 사용된 모듈 등의 의존성 관리를 잘 작성해주면 됩니다.
내부 코드에 대한 더 자세한 설명은 다음 글에서 이어집니다 ^__^
자 코드를 다 작성했다고 가정하고, 외부(Git)로 배포를 진행해 볼게요!
일반적으로 GitHub에 레포를 만들고, Clone 받아서 CLI나 GUI 등으로 Add-Commit-Push 과정을 거쳐도 물론 되지만, WWDC19에서는 Xcode를 통해 바로 배포하는 과정을 설명하고 있어 해당 과정을 따라가볼까 합니다.
우선, 새로운 Git Repository를 Local에 생성해 줍시다.
Xcode 상단 Intergrate (구버전은 Source Control) - New Git Repository로 접근하면, 쉽게 만들 수 있어요!
현재는 Local에 Repository가 만들어진 상태이니, Remote에도 Repository를 만들어주겠습니다.
Xcode 좌측 (내비게이션 패널이라고 하나요?) 두 번째 탭 - Repository로 들어오면, 현재 새롭게 만들어진 Git Repository를 볼 수 있습니다.
Repository를 우측키로 접근하면, New "설정한 Package 이름" Remote... 버튼을 눌러 생성창을 띄워줄게요!
그럼 우리가 GitHub에서 생성할 때처럼 Repository Name, Desciption, Public/Private 선택, Remote Name을 지정할 수 있습니다.
다 작성을 했으면, Create 버튼을 눌러줄까요?
미리 자신의 GitHub Account를 Xcode 연동시켜 두면, 해당 계정 Remote에 Repository가 바로 올라갈 수 있도록 될 겁니다.
잘 올라갔는지 확인해 봅시다!
마찬가지로 우측키를 누르면 "View on GitHub"를 통해 직접 깃허브 사이트로 접속할 수 있는데요. 아래 우측에 보이는 것처럼 생성한 신규 레포지토리가 잘 만들어졌군요!
직접 해당 패키지를 SPM으로 불러와서 사용할 수 있는지도 확인해 보겠습니다!
해당 깃허브 링크를 불러와서 원하는 SPM을 사용해서 프로젝트의 의존성으로 해당 패키지를 추가해주면,
만들어준 라이브러리를 import 해서 다른 일반적인 라이브러리처럼 사용할 수 있다는 것을 확인할 수 있습니다! 간단하군요 :)
3. Swift Package 배포 이후, 버전 관리를 위해 알아야 하는 개념 (feat. Semantic Versioning)
하지만, 이렇게 만들어진 패키지는 보통 자체 프로젝트에 사용하기보다 / 오픈소스로 배포하고, 여러 사람들의 Contribution을 유도하는 것이 주목적이라고 볼 수 있습니다.
그런 점에서 처음 만들고 배포하는 과정보다 / 이를 유지하고 보수하며, 버전 관리하는 노력이 더 중요하다고 볼 수 있는데요!
우리가 평소 사용하는 Commit, Push, Pull, Fetch, Stage, Tag, New Branch 등의 Git 기능도 위에서 설명한 것처럼 Xcode에서 기본적으로 활용할 수 있습니다.
하지만, 이 중에서도 특히 Tag와 관련된 "버전 관리"는 "Semantic Versioning"이라고 부르는 만국 공통의 규칙이 있어 자세하게 살펴보고 넘어가고자 합니다!
Semantic Versioning은 버전 번호 (1.0.0과 같은)를 통해 소프트웨어의 변경 내용을 체계적으로 나타내는 규칙입니다.
이와 같은 버전 규칙을 사용하는 이유는 소프트웨어 버전 관리의 통일성을 부여하고,
해당 소프트웨어를 사용하는 다른 사용자에 대해 버전을 보고 업데이트의 영향성/현재 진행 수준을 파악할 수 있도록 가이드라인을 제공해주기 위함이라고 하는데요. Swift Package도 역시 소프트웨어이기 때문에 해당 규칙을 따라야겠죠?
총 5가지의 버전 규칙을 정리해보겠습니다.
우선 버전 번호 (0.0.0) 세 자리 숫자에 해당하는 Major, Minor, Patch Version update에 대해 알아보도록 할게요!
✔️ 1.x.x (Major Version) : 기존 API에 대한 (호환되지 않는 / 혹은 이에 준하는) 큰 변화가 발생했을 때
-> 존재하는 타입에 대한 이름 변경 (renaming existing type), 메서드 삭제 및 수정 (remove method, changing method's signature), 버그 수정에 대한 기존 호환 불가 (including backwards incompatible bug fixes) 등
✔️ x.1.x (Minor Version) : 기존 API에 대해 호환 가능한 변화가 발생했을 때
-> 새로운 메서드나 타입의 추가 같은 수정 (adding a new methods or type)이 해당
✔️ x.x.1 (Patch Version) : 주로 버그 수정에 해당하는 변화가 발생했을 때
-> 버그 수정이 기존 호환성에 영향을 미치지 않는 경우 (making backwards compatible bug fixes)가 해당
이 3가지의 기본 원칙 말고도 추가적인 2개의 규칙이 더 있습니다.
아래 두 규칙은 많이 사용하는 편은 아니고, 초기나 테스트 버전에서 활용되니 참고로 알고 있으면 좋을 것 같아요🙂
✔️ 0.x,y (Major Version Zero) : 초기 개발 단계에서 안정성을 보장하지 않는 버전 (정식 출시라고 하기에는 부족!)
✔️ x.x.x-beta.1 (Prerelease Version) : 정식 릴리즈 전 테스트 목적으로 배포하는 버전
• alpha : 초기 개발 단계, 기능이 불완전할 수 있다는 것을 전제하는 테스트
• beta : 기능이 대부분 구현된 상태에서 버그 테스트 진행
• rc (Release Candidate) : 정식 릴리즈 후보 버전, 중요한 버그가 없으면 해당 버전으로 바로 릴리즈
4. Swift Package 더 자세하게 뜯어보기 (Package Manifest API, Package Dependencies)
그럼 위에서 간단하게 넘어갔던 Package.swift 파일에 대해서 조금 더 자세하게 살펴볼게요.
*Package.swift 파일은 SPM이 Package를 빌드하고 의존성을 관리하는 데 사용되는 패키지의 핵심 파일이라고 위에서 소개했습니다!
**이건 여담이긴 한데, Tuist에서 ProjectDescription을 작성하는 것도 이것과 유사하니까! 지금 잘 이해하면, 나중에 이해하는데도 도움이 될 거 같군요 :)
우선, 파일 상단 부분을 보겠습니다.
swift-tools-version: 버전의 형태로 파일 가장 상단 부분에 주석의 형태로 서있는 것은 해당 패키지를 빌드하기 위해 Swift Compiler가 요구하는 minimum 버전을 명시하는 것으로, SPM이 사용하는 버전이라고 생각하면 됩니다.
그리고 그 아래에는 해당 Package 파일을 사용하기 위한 라이브러리 PackageDescription을 import 해주고 있는 모습입니다.
자 이제 작성된 다섯 가지 부분을 집중해서 보죠! 각 부분의 내용을 순서대로 요약하면 아래와 같습니다.
1. name: 패키지 이름 -> 패키지를 import 할 때의 식별자로 사용
2. platforms: 패키지가 지원하는 플랫폼과 최소 OS 버전 (.iOS .macOS .tvOS .watchOS .visionOS 등이 있겠네요!)
3. products: 패키지가 외부에 제공하는 라이브러리(.library) 혹은 실행 가능한 파일(.executable) 정의
4. dependencies: 패키지에서 사용하는 외부 라이브러리
5. targets: 소스 코드(.target)와 테스트 타겟(.testTarget)을 포함하는 패키지의 빌드 단위를 의미
⚠️ [targets 작성 시 주의 사항] 4.dependencies에서 불러온 라이브러리를 사용하는 경우 target 혹은 testTarget의 파라미터로 dependencies를 Array에 함께 추가해줘야 합니다!
특히, Package Dependencies 부분을 WWDC에서 강조하고 있어 조금 주목해서 이해하고 넘어가고자 합니다.
*해당 부분은 Package 코드가 외부 라이브러리를 의존하고 있을 때 / 예를 들어, SnapKit으로 AutoLayout을 잡는 패키지 내의 코드가 있다면 이 패키지는 SnapKit 라이브러리에 대해 의존성을 갖고 있는 것이죠.
Dependencies의 package는 패키지 URL과 버전으로 지정합니다. (source URL and a version requirement)
아래는 버전을 지정하는 방법에 있어 다양한 네 가지 예시를 보여주는 코드입니다.
- from : 지정한 버전 이상
- .upToNextMajor(from:) : 지정한 버전 이상, 다음 메이저 버전 미만으로 지정 (2.0.0 이상 ~ 3.0.0 이전까지 사용)
- .upToNextMinor(from:) : 지정한 버전 이상, 다음 마이너 버전 미만으로 지정 (2.0.0 이상 ~ 2.1.0 이전까지 사용)
- .exact(:) : 특정 버전으로 지정 (이 방법은 권장되지 않음! -> it can lead to more conflicts in package craft)
.package(url: "https://github.com/mini-min/MiniKit", from: "2.0.0")
.package(url: "https://github.com/mini-min/MiniKit", .upToNextMajor(from: "2.0.0"))
.package(url: "https://github.com/mini-min/MiniKit", .upToNextMinor(from: "2.0.0"))
.package(url: "https://github.com/mini-min/MiniKit", .exact("2.0.0"))
버전 지정이 아닌 방식 (nonversion-vased requirements)으로 패키지를 불러오는 방법도 있습니다.
특정 브랜치를 기준으로 패키지를 가져오는 .branch 방식과 / 특정 커밋을 기준으로 패키지를 불러오는 .revision 방식이 그것이죠.
.package(url: "https://github.com/mini-min/MiniKit", .branch("main"))
.package(url: "https://github.com/mini-min/MiniKit", .revision("85cfe06"))
자 여기까지가 Swift Package의 개념과 배포 과정, 그리고 함께 알아두면 좋을 기본 지식까지 정리한 내용입니다!
다음 글에서는 package 소스 코드의 구성을 포함하는 실제 라이브러리 코드 작성 과정을 이어가보도록 하죠 :) 고생 많았습니다☺️
'Swift, iOS Foundation' 카테고리의 다른 글
[WWDC24] What's new in Swift - Swift 6 새로운 기능 살펴보기 (0) | 2024.07.13 |
---|---|
[iOS] 내가 서버 통신(Networking) 진짜 알기 쉽게 정리해서 올려줄게 (HTTP, JSON, REST API) (2) | 2023.11.13 |
[iOS] iOS 화면을 구성하는 파일, Nib와 Xib 개념 정리해보기 (1) | 2023.10.08 |
[Foundation] UserDefaults를 사용해서 데이터를 전달하는 방법 (0) | 2021.12.17 |
[iOS] 내가 보려고 정리하는 Coding Convention (feat. Style Share) (0) | 2021.08.17 |