CocoaPods로 나만의 프레임워크 배포하기

펄핏 Perfitt·2022년 3월 3일
4
post-thumbnail

안녕하세요, 펄핏의 iOS 개발자 Nick입니다. 이번 시간에는 신입 iOS 개발자의 1년간의 회고 포스팅에서 언급했던 SDK를 개발했던 경험을 토대로 CocoaPods에 라이브러리를 배포하는 방법을 공유하려 합니다.

🧰 CocoaPods?

CocoaPods는 iOS 오픈소스 라이브러리를 좀 더 편하게 관리해주는 패키지 관리자입니다.

iOS 패키지 관리자는 CocoaPods, Carthage, Swift Package Manager(SPM) 총 세 가지가 있지만 제가 이번 포스팅에 다룰 패키지 관리자는 CocoaPods입니다.

제가 CocoaPod로 SDK 배포하기로 했던 이유는 현재 Perfitt App에서 사용 중인 패키지 관리자도 CocoaPods이기 때문입니다. 아직 Carthage, SPM을 사용해본 적이 없어 기본 구동 방식에 대한 이해가 없었기 때문입니다.


📱 SDK를 개발하기위해 고려했던 사항들

가장 중요하게 생각했던 부분은 다른 프로젝트에 방해가 안 되도록 개발해야 한다고 생각합니다. 그렇기 때문에 SDK 개발할 때는 최대한 다른 라이브러리를 사용하지 않고 개발하고자 했습니다.

하지만 Perfitt에서 개발한 학습 모델을 활용하여 카메라에서 실시간 검증 기능을 개발하기 위해서는 TensorFlowLite를 사용해야 했습니다. 학습 모델을 사용하면서 소비되는 램의 사용량 또한 저의 고려사항에 추가되었는데요. 저는 TensorFlowLiteSwift를 SDK에 추가할 때 많은 어려움이 있었어요.


🏃‍♂️ CocoaPods Library 개발 A-Z

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 순으로 진행 하였습니다.

  1. What platform do you want to use? [ iOS / macOS ]
    • 만들고 싶은 플랫폼 선택
    • ex) iOS
  2. What language do you want to use? [Swift / ObjC ]
    • 제작 시 사용할 언어 선택
    • ex) Swift
  3. Would you like to include a demo application with your library? [ Yes / No ]
    • 이 프로젝트에 데모 앱을 만들지 안만들지의 여부
    • ex) Yes
  4. Which testing frameworks will you use? [ Quick / None ]
    • 테스트를 위한 프레임워크를 사용할지 선택
    • ex) None
  5. Would you like to do view based testing? [ Yes / No ]
    • 뷰 기반의 테스트를 진행할 것이지 선택
    • ex) Yes를 선택할 경우 Meta(Facebook) 에서 만든 스냅샷 기반 FBSnapshotTestCase를 포함해서 Project가 생성됩니다.

.podspec 이란?

위의 세 가지 스텝을 진행하게 되면 하나의 프로젝트가 생성되는데요. 제가 생각했을 때 가장 중요한 파일은 {library-name}.podspec 파일입니다. 이 파일의 명세가 정확히 되어있지 않으면 CocoaPods에 등록을 할 수 없기 때문입니다. 그러한 의미로 .podspec 내 작성되는 항목에 대해 알아보고자 합니다.

  • name 라이브러리의 이름을 정의 합니다.
  • version 라이브러리의 버전 (GitLab or GitHub의 Tag 이름과 동일해야 합니다. Tag 값의 매칭을 통해 배포가 이루어지기 때문입니다.)
  • summary 라이브러리의 간략한 설명을 작성합니다.
  • description 라이브러리의 자세한 설명 및 가이드 라인을 작성합니다.
  • homepage 라이브러리를 홍보 혹은 가이드 라인을 알려주는 사이트의 주소를 작성합니다. (현재는 회사 홈페이지)
  • license 라이브러리의 라이센스가 어떻게 되는지 작성합니다. (MIT or Apache)
  • author github or gitlap 계정 정보를 작성합니다. (닉네임, 이메일)
  • source 라이브러리가 저장되어 있는 GitLab or GitHub의 Remote 주소를 작성합니다.
  • ios.deployment_target 사용 가능한 미니멈 iOS Version을 작성합니다.
  • source_files 개발된 기능이 저장되어 있는 소스 파일의 경로를 작성합니다.
  • swift_version 컴파일된 Swift Version을 기입합니다.
  • frameworks 라이브러리를 만들면서 사용된 Framework를 작성합니다.
  • pod_target_xcconfig Pods Project Setting 추가할 설정들을 작성합니다.
  • user_target_xcconfig 추가되는 메인 프로젝트 설정들을 작성합니다.
  • info_plist pod info.plist에 설정 값을 작성합니다.
  • resource_bundles 라이브러리를 만들면서 사용되는 리소스들의 파일경로를 작성합니다. (파일을 bundle 파일로 만들어 제공 됩니다.)

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

✍️ SDK 제작

Pods/Development Pods/{library_name} 하위의 ReplaceMe.swift를 제거 해주세요.

이 섹션에서는 제가 SDK를 개발하면서 발생했던 상황들 중심으로 이야기를 풀어 나가려고 해요. 제가 사용한 방법보다 더 좋은 방법을 알고 계신 분이 계신다면 댓글로 알려주시면 감사하겠습니다.

1. Xib(Nib) 추가및 사용하기

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
    }
}

2. SDK에 TensorFlowLiteSwift 추가하기

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 }

🚀 CocoaPods 배포 방법

열심히 개발한 SDK를 CocoaPods에 업로드 해야 하는데요. 이 세션에서는 배포하는 방법에 대해서 설명해 드리려고 합니다.

  1. .podspec 검증
pod spec lint --allow-warnings
  1. CocoaPods 계정 등록
pod trunk register {user_email} {user_nick_name}
  1. CocoaPods 배포
pod trunk push --allow-warnings

마치며

배포 방법을 마지막으로 CocoaPods로 SDK를 개발한 경험을 토대로 나만의 프레임워크를 만드는 방법에 대해 설명해 드렸는데요. 이 프로젝트를 하면서 느꼈던 점은 앱을 개발하는 것과 마찬가지로 내가 만든 것을 사용하는 사람들의 입장에서 생각하며 개발을 진행해야 조금 더 좋은 프레임워크, 앱을 만들 수 있다는 것을 느낄 수 있었습니다.

앞으로는 더 고객 중심적으로 생각하고 행동해야겠다! 라는 다짐을 다시 해보며 글을 마칩니다. 모두 즐코하세요~ 👋

profile
Beyond The Size Limit

0개의 댓글