👋 들어가기 전
나만의 오픈소스를 만들어 다른 사람에게 도움이 될만한 경험을 제공하는게 꿈이었다.
현재 프로젝트를 하며 작지만 꽤나 귀찮은 불편함을 느꼈는데
이 아이디어를 나의 첫 오픈 소스 패키지로 활용하면 재밌겠다라는 생각이 들었다.
이번 시간에는 패키지를 만들며 겪는 과정과 어려움을 정리해보려한다.
드가자 ~!
✊패키지 생성하기
패키지 생성은 생각보다 어렵지 않았다.
Xcode -> File -> Packag를 순서대로 누르자.
이후 Library를 선택 후 Test 파일 여부와 Packag이름을 써주면..
짜잔 패키지 만들기 성공!
☝️구성
말했던 것 처럼 나의 첫 패키지는 굉장히 간단하기 때문에
패키지를 만들 때 거치는 과정이 굉장히 간단해서 구성을 살펴보는건 공식 패키지를 보고 배워보자.
우리가 살펴볼 공식 패키지는 Firebase로 결정했다.
왜냐하면 굉장히 많은 기능들을 다루는 패키지여서 살펴볼 수 있는 정보들이 많다.
Pacakge.swift
package.swift 파일은 패키지의 중요한 정보를 명시한다.
패키지 이름, 이용 가능한 platfrom, 외부 의존성등 과 같은 정보를 다음과 같이 명시한다.
내가 정리한건 간단하게 이 정도인데 추후 필요하면 더 추가하겠다.
여기서 흥미로운 점은 Package안에서 여러가지 target이 관린된다는 점이다.
Package로 모듈화 한다는 게 사실 이해가 안됐었는데 여기를 보니 바로 이해가 됐다.
또한 아래 products는 우리가 Firebase나 Rxswif를 설치할 때 선택하는 창과 관련이 있다는 것
let package = Package(
name: "패키지 이름",
platforms: [.지원플랫폼(버전), ...],
products: [ // 한개의 라이브러리에 여러개의 타겟 포함 가능
.library(name: "라이브러리 명", targets: ["타겟 명"]),
.library(name: "라이브러리 명", type: .dynamic, targets: ["타겟 명"]) // dynamic
],
dependencies: [ // 외부 의존성 정의
.package( url: "https://github.com/...",
"2.4.0" ..< "3.0.0" // 버전
),
],
targets: [ // 나의 패키지의 타겟 정의
.target(name: "MyLibrary"),
.target(
name: "MyLibrary2", // 타겟에 해당되는 의존성 정의
dependencies: [.product(name: "RxSwift", package: "rxswift")]
),
.testTarget(
name: "테스트 타겟이름",
dependencies: [...],
path: "타겟의 소스파일 경로",
exclude: "path에서 지정한 디렉터리 내에서 제외할 파일이나 디렉터리를 명시",
resources: [
.process("Assets"), // 리소스를 번들화하고 접근 가능하도록 처리
.copy("StaticFiles/README.md") // 리소스를 그대로 복사합니다(별도의 변환 없음).
] // 타깃이 사용하는 리소스 파일(예: 이미지, JSON 파일 등)을 정의
],
swiftLanguageVersions: [.v5] // 지원하는 swift version
...
)
Sources
다음은 Sources 폴더이다.
여기는 말 그대로 Source Code가 들어가며 만약 다양한 Target을 만들 때도 여기서 만들어준다.
Tests
말 그대로 Test를 할 수 있는 공간이다.
✌️Resouce 묶기
Target에 명시하기
내가 만들 패키지는 json 데이터가 필요한데 패키지에 Resource를 넣어보는 작업을 해보려한다.
먼저 json 파일을 넣고 target의 resource에 필요한 resource에 대한 것을 경로와함께 명시한다.
여기서 우리는 .process 또는 copy로 추가할 수 있는데
Zedd님의 블로그 내용을 정리하면
copy는 Data 즉, 모든 디렉토리까지 구조를 유지해서 번들에 저장하지만
process는 디렉토리 구조를 벗겨 번들 최상위에 놓는다.
그래서 디렉토리 구조를 유지하고 싶으면 copy 아니면 process를 쓰면 된다.
Bundle을 통해 찾기
먼저 나는 다음과 같이 Data 디렉토리 안에 있는 json 값들을 읽어오려 했지만
notFounded 에러를 만났다.
extension SwiftCountryKit {
private func load() throws -> [Country] {
guard let url = Bundle.main.url(forResource: "Data/countries", withExtension: "json") else {
SCLogger.printError("notFounded json")
throw DecodeError.notFounded }
do {
let data = try Data(contentsOf: url)
return try decoder.decode([Country].self, from: data)
} catch {
throw DecodeError.decodingFail
}
}
}
여기서 잘못된 점은 바로 forResource에 "Data/" 를 넣어준 실수이다.
분명 위에서 resources에 정의할 때 .process로 정의하면 디렉토리를 모두 벗겨
최상위 경로에 놓는다고 했다.
그러므로 Data/ 없이 아래와 같이 입력하자.
private func load() throws -> [Country] {
guard let url = Bundle.module.url(forResource: "countries", withExtension: "json") else {
SCLogger.printError("notFounded json")
throw DecodeError.notFounded }
do {
let data = try Data(contentsOf: url)
return try decoder.decode([Country].self, from: data)
} catch {
throw DecodeError.decodingFail
}
}
👍Test
간단하다 해당 패키지 루트 디렉토리에서 아래 커맨드를 통해 테스트를 진행한다.
import XCTest
@testable import SwiftCountryKit
final class SwiftCountryKitTests: XCTestCase {
func testExample() throws {
let countryKit = try SwiftCountryKit()
XCTAssertGreaterThan(countryKit.all.count, 180)
XCTAssertNotEqual(countryKit.all.count, 0)
}
}
swift test
180개 데이터 모두 잘 가져와서 성공하는 결과를 볼 수 있다.
📦 배포하기
깃 repo를 만든 후 연결해서 push 하면 끝
😀 소감 및 마무리
패키지를 만드는게 이렇게 신기하고 값진 경험이 될지는 상상도 못했다.
테스트 코드의 중요성을 다시 한번 느꼈다.
남에게 편리함과 즐거움을 주려고 만든 코드가 테스트 부재로 더 많은 짐을 남길 수 있는
위험을 인지하니 등골이 오싹했다.
패키지가 release되고 정리가 좀 된다면 이 포스팅 글 아래에 남겨놓겠다.
출처
'iOS' 카테고리의 다른 글
Localization (1) | 2024.12.28 |
---|---|
FirebaseCrashlytics 적용하기 with SPM (3) | 2024.12.26 |
[ 부스트 캠프 ] 채팅 기능을 위한 웹소켓 만들기 (1) | 2024.11.30 |
[부스트 캠프] 우리만의 네트워크 만들기 (0) | 2024.11.27 |