안녕하세요, 펄핏의 iOS 개발자 Nick입니다. 이번 시간에는 신입 iOS 개발자의 1년간의 회고 포스팅에서 언급했던 SDK를 개발했던 경험을 토대로 CocoaPods에 라이브러리를 배포하는 방법을 공유하려 합니다.
CocoaPods는 iOS 오픈소스 라이브러리를 좀 더 편하게 관리해주는 패키지 관리자입니다.
iOS 패키지 관리자는 CocoaPods, Carthage, Swift Package Manager(SPM) 총 세 가지가 있지만 제가 이번 포스팅에 다룰 패키지 관리자는 CocoaPods입니다.
제가 CocoaPod로 SDK 배포하기로 했던 이유는 현재 Perfitt App에서 사용 중인 패키지 관리자도 CocoaPods이기 때문입니다. 아직 Carthage, SPM을 사용해본 적이 없어 기본 구동 방식에 대한 이해가 없었기 때문입니다.
가장 중요하게 생각했던 부분은 다른 프로젝트에 방해가 안 되도록 개발해야 한다고 생각합니다. 그렇기 때문에 SDK 개발할 때는 최대한 다른 라이브러리를 사용하지 않고 개발하고자 했습니다.
하지만 Perfitt에서 개발한 학습 모델을 활용하여 카메라에서 실시간 검증 기능을 개발하기 위해서는 TensorFlowLite를 사용해야 했습니다. 학습 모델을 사용하면서 소비되는 램의 사용량 또한 저의 고려사항에 추가되었는데요. 저는 TensorFlowLiteSwift를 SDK에 추가할 때 많은 어려움이 있었어요.
SDK를 개발하기 위해서 선행해야 할 것들이 몇 가지 있는데요. 제가 개발하면서 정리했던 것을 공유하고자 합니다.
1. CocoaPods 설치
# cocoapods를 설치하기 위한 명령어
sudo gem install cocoapods
2. 배포를 하기 위한 Project 생성
# cocoapods libray project 생성 명렁어
pod lib create {library-name}
3. Project Setting
2번의 명렁어를 실행하게되면 라이브러리를 만들위한 다섯 가지 질문이 나오게 되는데요. 저 같은 경우에는 iOS, Swift, Yes, None, No 순으로 진행 하였습니다.
위의 세 가지 스텝을 진행하게 되면 하나의 프로젝트가 생성되는데요. 제가 생각했을 때 가장 중요한 파일은 {library-name}.podspec
파일입니다. 이 파일의 명세가 정확히 되어있지 않으면 CocoaPods에 등록을 할 수 없기 때문입니다. 그러한 의미로 .podspec
내 작성되는 항목에 대해 알아보고자 합니다.
SDK를 만들 때 사용했던 podspec 파일
Pod::Spec.new do |s|
s.name = '{library-name}'
s.version = '1.0.0'
s.summary = 'library-subtitle'
s.description = <<-DESC
'{라이브러리 상세 설명}'
DESC
s.homepage = 'https://www.perfitt.io/'
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'perfitt' => '{author-email}' }
s.source = { :git => '{library-github-repo.git}', :tag => s.version.to_s }
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
s.ios.deployment_target = '12.0'
s.source_files = '{library-name}/Classes/**/*'
s.swift_version = '4.2'
s.frameworks = 'UIKit', 'AVFoundation'
s.static_framework = true
s.dependency 'TensorFlowLiteSwift', '~> 2.2.0'
s.pod_target_xcconfig = {
'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64'
}
s.user_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64' }
s.info_plist = {
'CFBundleIdentifier' => 'org.cocoapods.{library-name}'
}
s.resource_bundles = {
'PerfittPartners_iOS' => ['{library-name}/**/*']
}
end
만든 SDK를 배포하기 위해서는 다른 개발자들이 다운로드 받을 수 있도록 GitHub 혹은 GitLab과 같은 외부 저장소에 프로젝트를 업로드 해놔야 하는데요.
일반적으로 GitHub Repository 만드는 것처럼 하면 됩니다. Library의 이름을 같이 가져가주는 게 제일 중요하다고 생각합니다. Repository를 만드셨다면 $ pod lib create
로 생성된 폴더 위치에서 터미널로 외부 저장소와 연결한 후 Push 해주시면 배포를 위한 기본 작업은 완료하게 됩니다.
git remote add origin {원격저장소 주소.git}
git push origin master
Pods/Development Pods/{library_name} 하위의 ReplaceMe.swift를 제거 해주세요.
이 섹션에서는 제가 SDK를 개발하면서 발생했던 상황들 중심으로 이야기를 풀어 나가려고 해요. 제가 사용한 방법보다 더 좋은 방법을 알고 계신 분이 계신다면 댓글로 알려주시면 감사하겠습니다.
Storyboard를 활용해도 되고 코드로 UI 구성을 해도 되지만 저는 Xib를 활용하여 VC를 관리하기로 했습니다. Code로 UI 구성을 하기에는 추후 관리하는 입장에서는 코드를 이해하는데 오랜 시간이 걸릴 것 같아 배제하였습니다. Storyboard를 사용하지 안은 이유는 각 VC 마다 UI 구현 파일이 있는 편이 명시적이라 생각했기에 Xib로 UI를 관리하기로 했습니다.
제가 골치 아팠던 부분은 xib 파일을 불러오는 과정이었습니다. 제가 착각한 부분은 pod 프로젝트에서 Bundle.main을 하게 되면 pod 프로젝트에 있는 번들 파일을 불러와 줄 거라 생각했는데 그게 아니라 pod install을 한 main project에 있는 번들 파일을 불러오기 때문에 xib 파일을 불러오지 못했었네요.
pod project에 추가한 xib 파일을 불러오기 위해서는 podspec에서 정의한 bundle 이름을 사용하여 번들을 불러오는 것 입니다. 저는 이 기능을 UIViewController에 extension을 추가하여 사용하고 있습니다.
extension UIViewController {
static func initViewController<T: UIViewController>(viewControllerClass: T.Type) -> T{
let podBundle = Bundle(for: PerfittPartners.self)
if let bundleURL = podBundle.url(forResource: "PerfittPartners_iOS", withExtension: "bundle") {
if let bundle = Bundle(url: bundleURL) {
let nib = UINib(nibName: "\(T.self)", bundle: bundle)
let vc = nib.instantiate(withOwner: nil, options: nil)
if let callViewController = vc.filter( { $0 is T } ).first as? T {
return callViewController
}
}
}
return UIViewController() as! T
}
}
TensorFlowLiteSwift는 학습된 모델을 모바일에 맞춰서 경량화한 모델을 iOS에서 사용하기 위해 만들어진 프레임워크입니다. 저는 위의 작업이 있었기 때문에 쉽게 해결할 수 있을 것이라 생각했습니다. 하지만 저는 매우 어이없는 실수를 저질렀죠…
resource를 추가하고 코드를 수정했다면 이 작업을 데모 앱에 반영하기 위해서 $ pod install
을 해서 변경된 내용을 업데이트해 줬어야 하는데 하지 않아서 몇 시간이나 잡아먹었는지 모르겠네요. 다들 이런 실수가 없으시길 바랍니다. 😃
let podBundle = Bundle(for: {최상위 class}.self)
guard let bundleURL = podBundle.url(forResource: "{podspec에서 정의 한 번들이름}", withExtension: "bundle") else {
debugPrint("pod bundle failed")
return nil }
guard let bundle = Bundle(url: bundleURL) else {
debugPrint("create bundle failed")
return nil }
guard let modelPath = bundle.path(forResource: {tensorflowlite model 이름}, ofType: {tfmodel}) else {
debugPrint("custom bundle failed to load the model file with name : \(tensorflowlite model 이름).")
return nil }
열심히 개발한 SDK를 CocoaPods에 업로드 해야 하는데요. 이 세션에서는 배포하는 방법에 대해서 설명해 드리려고 합니다.
.podspec
검증pod spec lint --allow-warnings
pod trunk register {user_email} {user_nick_name}
pod trunk push --allow-warnings
배포 방법을 마지막으로 CocoaPods로 SDK를 개발한 경험을 토대로 나만의 프레임워크를 만드는 방법에 대해 설명해 드렸는데요. 이 프로젝트를 하면서 느꼈던 점은 앱을 개발하는 것과 마찬가지로 내가 만든 것을 사용하는 사람들의 입장에서 생각하며 개발을 진행해야 조금 더 좋은 프레임워크, 앱을 만들 수 있다는 것을 느낄 수 있었습니다.
앞으로는 더 고객 중심적으로 생각하고 행동해야겠다! 라는 다짐을 다시 해보며 글을 마칩니다. 모두 즐코하세요~ 👋