Camp Mobile 밴드개발캠프 허혁

Java로 개발할 때 좋은 점 중 하나가 의존성 관리 도구인 Maven이나 Ivy의 존재였습니다. 여러 가지 라이브러리의 버전이나 의존적인 라이브러리를 표시해 주고 배포, 설치 및 제거가 쉬워 소스 코드를 공유해서 작업하는 경우에 많은 도움이 되었습니다. Objective-C로 주 개발 언어를 바꾸고 Xcode를 주 개발 도구로 사용하면서는 그런 좋은 경험을 할 수 없다는 것에 대한 아쉬움이 컸습니다.

그러던 중 Objective-C에서 사용할 수 있는 의존성 관리 도구인 CocoaPods를 사용하기 시작했는데 편리할 뿐만 아니라 좋은 라이브러리를 쉽게 찾을 수 있었습니다. CocoaPods의 역할은 의존성 관리 도구에만 한정되지 않습니다. 검색 기능을 사용해 어떤 오픈소스가 있는지 알 수 있고, 필요로 하는 라이브러리에 대한 정보를 얻을 수 있으며, 반대로 자신의 오픈소스를 홍보할 수도 있습니다.

이 글에서는 CocoaPods가 무엇인지 소개하고 CocoaPods를 사용해 오픈소스를 관리하는 방법을 간략하게 살펴보겠습니다.

CocoaPods란?

Objective-C를 이용한 OS X용 앱이나 iOS용 앱을 만들 때 오픈소스를 많이 사용한다. 이러한 오픈소스를 관리하는 방법으로는 압축 파일로 소스를 받아서 직접 프로젝트에 넣는 방법과 개발하고 있는 저장소에 오픈소스 저장소를 링크시켜 프로젝트에 넣는 방법이 있다. 첫 번째 방법은 버전 업데이트가 어렵다는 단점이 있다.

두 번째 방법은 해당 오픈소스의 의존성 라이브러리가 중복되어 로딩될 수 있는 문제가 있는다. 특히, 네임스페이스를 지원하지 않는 Objective-C의 특성상 이와 같은 경우 컴파일이 되지 않는다. 대표적으로 예로 Facebook의 iOS SDK에는 SBJson 라이브러리가 포함되어 있다. 그런데 이를 사용하려는 프로젝트에서 기존 SBJson 라이브러리를 사용하고 있었다면 사용하고 있던 SBJson 라이브러리를 제거해야 한다. 이후 Facebook에서는 포함되어 있던 SBJson 라이브러리 이름을 모두 변경해서 기존 SBJson 라이브러리를 사용하고 있던 프로젝트에 적용해도 이름이 충돌하지 않도록 수정했다.

이 두 가지 단점을 모두 제거할 수 있는 세 번째 방법이 바로 CocoaPods이다. CocoaPods를 이용하면 라이브러리 간 의존성 체크 및 라이브러리 버전 관리, 지원 OS 버전과 라이브러리 설정이 쉽다. CocoaPods에서 밝히는 CocoaPods의 목표는 오픈소스 라이브러리를 좀 더 쉽게 찾고 사용할 수 있게 중앙 집중화된 환경을 제공하는 것이다. 또한 Objective-C 의존성 관리를 위한 기본적인 기능들을 만족시키고자 하지만 한창 개발 중인 프로젝트이기 때문에 아직까지는 부족한 점들이 있다. 아직 모든 의존성 문제를 해결하지는 못하고 있으며 몇몇 규모가 큰 오픈소스는 포팅의 어려움 때문에 CocoaPods를 지원하지 못하는 경우도 있다. 하지만 많은 오픈소스가 점차 진입하고 있고 유사한 기능의 오픈소스도 쉽게 진입할 수 있기 때문에 적절하게 골라서 사용해야 한다.

cocoapods.org(http://cocoapods.org/) 사이트에서 CocoaPods에 관련된 문서와 온라인 라이브러리 검색을 제공하지만, 개발은 GitHub(https://github.com/cocoapods)에서 이루어지고 있기 때문에 위키(https://github.com/CocoaPods/CocoaPods/wiki)에서도 정보를 얻을 수 있다.

초기 아이디어와 수작업으로 연동하기

CocoaPods의 초기 아이디어는 어느 블로그의 글(http://blog.carbonfive.com/2011/04/04/using-open-source-static-libraries-in-xcode-4)에서 시작되었다. Xcode 4에서 새로 도입된 기능이 워크스페이스(workspace)인데 이를 이용해서 오픈소스를 정적 라이브러리로 연결시켜 사용하면 편하다는 내용이다. CocoaPods를 쓰지 않더라도 이 순서를 따라 모듈을 분리해서 사용하면 편리한 점이 많다. 필자는 레거시 코드와 인하우스 코드를 별도 프로젝트로 분리해서 사용하고 있기 때문에 무엇을 점차 제거해야 할지 인지하기 편하며 변경의 확산을 필자가 개발하는 프로젝트에 한정시키는 효과를 누리고 있다.

오픈소스를 연결시켜 사용하기 전에 미리 알아 두면 좋은 용어는 워크스페이스와 스키다.

  • 워크스페이스: 여러 프로젝트를 담을 수 있는 공간이다. 여러 프로젝트에서 사용하는 코드를 어떻게 공유할지 정의할 수 있다. 모든 Xcode 프로젝트는 하나의 디렉터리에서 빌드되며 서로 참조할 수 있다. 묵시적 의존성을 찾기 위해서 빌드 디렉터리 안의 파일을 검사한다. 워크스페이스의 각 프로젝트는 여전히 각자 고유의 아이덴티티를 가지고 있다.
  • 스키마: 워크스페이스나 프로젝트에는 스키마를 지원하는데, 이는 Xcode 3의 Active Target(빌드 타겟), Build Configuration(빌드 순서), Executable Setting(빌드가 끝났을 때 할 작업)을 대체한다. 사용하는 프로젝트의 적절한 스키마를 코드 저장소를 통해 공유하거나 공유하지 않을 수 있다.

워크스페이스를 이용한 정적 라이브러리와 프로덕트 프로젝트 분리

Xcode 4에서 정적 라이브러리와 프로덕트 프로젝트를 분리하는 방법은 다음과 같다.

    1. 기존의 워크스페이스가 없다면 새로 생성한다.
      빈 워크스페이스를 하나 만들거나 다음 그림처럼 워크스페이스로 저장하기(Save As Workspace) 기능으로 기존에 사용하던 프로젝트를 워크스페이스로 만든다. 확장자가 .xcworkspace인 파일이 만들어지면 성공이다.

e8f183efc278f2ad88c876f94841ef9b.png

그림 1 워크스페이스 생성

    1. 프로젝트 파일을 워크스페이스에 추가한다.
      기존의 프로젝트 파일을 넣고 다음 그림과 같이 신규로 정적 라이브러리를 만들 프로젝트 파일을 지정한다. 프로덕트 프로젝트 파일이 정적 라이브러리 프로젝트에 대한 의존성을 갖게 되는데 동일 계층 레벨에 존재해도 무관하고 라이브러리 프로젝트가 프로덕트 프로젝트에 참조를 가질 필요도 없다.

dc1236dae63a444c71083c10ac33fe34.png

그림 2 프로젝트 파일 추가

    1. 프로덕트 빌드 타겟에 의존성을 추가한다.
      프로덕트 빌드 타겟을 선택한 후 다음 그림과 같이 Link Binary With Libraries 빌드 단계에서 정적 라이브러리를 추가한다.

5e0b080aa14ee352a1512a7c9fdf938c.png

그림 3 빌드 타겟에 의존성 추가

    1. 정적 라이브러리의 헤더 파일을 추가한다.
      프로덕트 빌드 타겟이 정적 라이브러리의 헤더 파일을 찾을 수 있도록 다음 그림과 같이 연결해야 한다. Building Settings에서 User Header Search Paths를 찾아서 $(BUILT_PRODUCTS_DIR)을 입력하고 Recursive 항목을 선택하면 워크스페이스의 공유 빌드 디렉터리에서 연결 가능한 헤더 파일을 찾을 수 있다.
      User Header Search Paths는 헤더 파일을 따옴표를 사용하는 import 문으로 찾고, Header Search Paths는 <>를 사용하는 import 문으로 찾는다.

87c96329b66bda3762ef2a761efc4214.png

그림 4 헤더 파일 추가

    1. 프로젝트 스키마를 설정한다.
      Xcode는 프로덕트 프로젝트와 정적 라이브러리 프로젝트 간의 암시적 의존성을 분석해 자동으로 스키마를 설정할 수 있다. 그런데 자동으로 잘 설정되지 않으면 다음 그림과 같이 스키마 타겟 빌드 항목에서 프로덕트 프로젝트 빌드 전에 정적 라이브러리 프로젝트 타겟을 추가하여 명시적으로 스키마를 설정할 수 있다.

d8a0f13d08917e83d385552a6270417a.png

그림 5 프로젝트 스키마 생성

    1. 정적 라이브러리의 설정 값을 프로덕트에서 인지할 수 있도록, 다음과 같이 정적 라이브러리에서 추가한 설정 값을 기준으로 확장한 설정 파일을 프로덕트 타겟으로 설정한다.

532458eba361109c34a7e94287712de1.png

그림 6 프로덕트 타겟 설정

  1. Build Phases에서 이용해서 정적 라이브러리 프로젝트에서 추가한 리소스들을 복사한다.

9430fe2674bb5490b22b809a220b6473.png

그림 7 리소스 설정

정적 라이브러리 만들기

오픈소스 라이브러리 관리뿐만 아니라 인하우스 코드 정리, 공통 모듈 정리, 추가 라이브러리 정리 등의 용도로 정적 라이브러리를 만들어 연결시키는 방법도 알아 두면 좋다.

    1. 적절한 네임스페이스를 설정한다.
      사용자 코드와 라이브러리 코드 간에 이름이 충돌하지 않도록 클래스, 프로토콜, 함수, 상수에 대해 적절한 접두어(prefix)를 붙여야 한다.
    2. 빌드 타겟을 만든다.
      링크할 정적 라이브러리를 만들 빌드 타겟을 추가한다.
    3. 외부에 공개할 헤더 파일을 설정한다.
      다음 그림과 같이 헤더 파일에 이름을 잘 붙이고 그룹으로 묶으면 인덱스 파일을 만들 때 편하다. 이렇게 하면 어떤 인터페이스가 라이브러리로 제공되고 어떤 클래스의 상세 구현이 어떻게 되어 있는지 인지하기 쉽다.
      파일 속성 창의 타겟 멤버십 항목에서 public으로 설정할 수 있다. 공개 헤더만 import해서 사용할 수 있어야 한다.

그림 8 공개할 헤더파일 지정

    1. 설치 디렉터리를 설정한다.
      정적 라이브러리 빌드 타겟은 워크스페이스의 일부분이기 때문에 워크스페이스의 설치 규칙을 따른다. 정적 라이브러리 빌드 프로덕트는 Xcode 설정에 있는 경로나 빌드 타겟에 설정된 경로를 기준으로 위치를 결정해서 설치된다. 사용자 설정을 변경할 수는 없기 때문에 모든 경우에 잘 동작하게 하려면 정적 라이브러리를 이미 알고 있는 위치에서 찾을 수 있도록 Installation Directory 항목을 $(BUILT_PRODUCTS_DIR)로 설정해야 한다. 그리고 iOS 라이브러리가 /usr/local/lib에 실수로 저장되는 것을 피하기 위해서 Skip Install 항목을 Yes로 설정한다.

8326d4e0b392b871bd81240acc0592a5.png

그림 9 설치 디렉터리 설정

    1. 퍼블릭 헤더 경로를 설정한다.
      Public Headers Folder Path$(TARGET_NAME)으로 설정하여 워크스페이스에서 공유하는 빌드 디렉터리 안의 위치를 지정하게 한다. 그리고 프로덕트 프로젝트에서 이를 읽어 들일 수 있도록 User Header Search Paths를 설정한다.

0a46cfa07f4c9973f97f568286b17ef7.png

그림 10 퍼블릭 헤더 경로 설정

이와 같은 수동 작업이 반복되는 것을 스크립트로 만들기 시작한 것이 CocoaPods이다.

CocoaPods의 발전 과정

처음 CocoaPods가 프로젝트를 시작할 때 경쟁 프로그램이 없는 것은 아니었다. Haskell로 된 kit(https://github.com/nkpart/kit)라는 툴이 있었다. 또한 CocoaPods의 이력을 보면 2011년 8월 12일 첫 커밋에서 프로젝트 소개 페이지만 커밋되었는데, 사용하던 유틸리티를 프로젝트로 공개한 것이 아니라 검증된 아이디어를 깔끔하게 정리하려는 목적이 더 컸던 것 같다. 일단 시작된 프로젝트는 기본적인 동작을 위한 뼈대를 만들었고, iOS 지원, 리소스 추가 기능이 덧붙여졌다. 이후 Xcode 프로젝트 파일 자동 생성을 위한 별도 프로젝트가 진행되었다. 마지막으로 MRI(Matz’s Ruby Interpreter) 기반 gem으로 변경하면서 큰 틀이 마무리 되었고 상세 기능들이 덧붙고 있다. 오픈소스 설정 파일이나 Xcode 관련 파일 처리 클래스 등 여러 작은 툴도 별도로 저장, 관리하고 있다. 마침내 2011년 11월에는 프로젝트 그룹을 만들어서 현재 약 126명의 멤버가 활동 중이며 관련 프로젝트도 18개인 꽤 큰 프로젝트가 되었다.

구성

CocoaPods에서 설치하는 ruby-gem은 크게 CocoaPods, Spec, Core, Xcodeproj, CLAide, CocoaPods-Downloader로 이루어져 있다. 추가로 프로젝트 사이트를 제공하는 cocoapods.org와 문서 제공을 하는 cocoadocs.org가 있다.

      • CocoaPods: 명령창 기능과 인스톨러 기능 통합
      • CocoaPods-Core: CocoaPods 모델링에 맞춘 작업을 제공
      • Xcodeproj: Ruby 코드에서 Xcode 프로젝트 파일 생성 및 수정. Xcode 스타일 라이브러리, 워크스페이스, 설정 파일을 지원한다.
      • CLAide: command line parser, 명령어 분석 실행 툴
      • CocoaPods-Downloader: 여러 가지 소스 타입을 다운로드하는 모듈. 4가지 소스(HTTP/SVN/Git/Mercurial)를 지원한다.
      • Podfile: 프로덕트 프로젝트가 가지고 있는 의존성을 명시하고 추가 작업을 기술할 수 있는 설정 파일. Ruby를 이용한 DSL(Domain-specific Language)로 이루어져 있어서 프로그래밍이 가능하다.
      • Spec: 각 오픈소스가 어떤 의존성과 설정이 필요한지를 기술한 명세서. 키-값(key-value) 방식의 맵(map)으로 이루어져 있기 때문에 프로그래밍은 불가능하며, CocoaPods에서 제공하는 설정만을 쓸 수 있다. 다만 후킹 메서드가 두 개 있어서 약간의 유연성을 부여하고 있다.
      • Acknowledgements: 사용하는 오픈소스의 라이선스를 추출해서 프로덕트에 손쉽게 연결시킬 수 있도록 정리한 파일

오픈소스 설치 정보 Spec의 취합

CocoaPods는 오픈소스 연동과 관련된 정보인 Spec 확보에 외부 도움을 많이 받고 있다. 특히 오픈소스 개발자나 해당 오픈소스 사용자가 직접 그 오픈소스를 빌드해서 최적화할 수 있는 옵션을 정리해서 pull request를 보낼 수 있다. 일반적으로 하루나 이틀 정도 지나면 해당 Spec을 반영한다.

CocoaPods 사용 방법

설치

CocoaPods를 설치하는 방법은 다음과 같다.

      1. CocoaPods를 설치하려면 ruby gem을 이용해야 한다.
        다음과 같이 명령하면 설치가 완료된다. 다만 Ruby 2.0 지원이 아직 완벽하지 않기 때문에 RVM(Ruby Version Manager)을 이용해서 조정하는 방법을 사용할 수도 있다. RVM을 쓰는 경우 sudo 명령어를 쓰지 않는 것을 권장한다.
$ [sudo] gem install cocoapods
$ pod setup
      1. Podfile 파일로 프로젝트를 설정한다.
        프로젝트 설정은 이것이 전부이다. 만약 잘 되지 않는다면 설치가 제대로 완료되지 않았거나 CocoaPods 버전이 불안정한 버전일 가능성이 높다.
$ cat >> Podfile
platform :ios
$ pod install
      1. 워크스페이스로 개발을 시작한다.
        의존성 조정으로 Podfile 설정 파일 변경 후 다음을 실행한다.
$ pod update

Podfiles 파일 설정

Podfiles는 CocoaPods의 설정 파일이다. Podfiles의 예와 각 항목에 관한 설명은 다음과 같다.

예제

platform :ios, '6.0' 
inhibit_all_warnings!
xcodeproj 'MyProject'
pod 'ObjectiveSugar', '~> 0.5'
target :test do
pod 'OCMock', '~> 2.0.1'
end
post_install do |installer
installer.project.targets.each do |target|
puts "#target.name"
end
end
      • platform: iOS나 OS X 프로젝트 여부. 지원 OS도 설정할 수 있다.
      • inhibit_all_warinings!: CocoaPods 프로젝트에서 나온 경고 표시 여부.
      • xcodeproj: 프로젝트 이름과 워크스페이스 이름이 다른 경우 설정 옵션. 빌드 설정(Debug나 Release, Alpha)도 가능하다.
      • pod: 사용하는 오픈소스 라이브러리 이름. 특정 버전, 브랜치, 경로, 저장소, 특정 커밋, 특정spec URL 등으로 원하는 시점의 소스 코드를 정의할 수 있다.
      • target: 프로젝트의 특정 타겟에서만 반영되는 추가 설정. 주로 테스트 타겟의 경우에 테스트용 라이브러리 설정에 사용된다.
      • post_install: 커스텀 설정. 다만 CocoaPods의 상태와 사용 가능한 메서드 추적에 어려움이 있기 때문에 최소화해서 사용해야 한다.

Spec 설정

필요한 오픈소스 라이브러리가 CocoaPods에 등록되어 있지 않다면 다음과 같이 직접 만들어서 공유할 수 있다.

      1. .podspec 파일을 생성한다. .podspec 파일을 직접 공유하거나 CocoaPods 중앙 저장소(https://github.com/CocoaPods/Specs)에 추가한다.
$ pod spec create [ NAME | https://github.com/PROJECT ] NAME.podspec 
      1. .podspec 파일을 검증한다. 명령어를 통해서 어떤 요소가 불충분한지 확인할 수 있다.
$ pod spec lint NAME.podspec 

예제 코드

Pod::Spec.new do |s|
s.name = 'Reachability'
s.version = '3.1.0'
s.license = :type => 'BSD'
s.homepage = 'https://github.com/tonymillion/Reachability'
s.authors = 'Tony Million' => 'tonymillion@gmail.com'
s.summary = 'ARC and GCD Compatible Reachability Class for iOS and OS X. Drop in replacement for Apple Reachability.'
s.source = :git => 'https://github.com/tonymillion/Reachability.git', :tag => 'v3.1.0'
s.source_files = 'Reachability.{h,m}'
s.framework = 'SystemConfiguration'
s.requires_arc = true
end
      • version: CocoaPods에서는 Semantic Versioning(http://semver.org)를 따르고 있다.
      • license: 오픈소스 라이선스를 기술하는 영역으로, 기본적인 BSD, MIT 같은 라이선스를 지정해서 설정할 수도 있고 LICENSE.* 파일이나 LICENCE.* 파일을 루트 디렉터리에 넣어 명시적으로 지정할 수도 있다. 라이선스를 지정하면 앱에서 손쉽게 오픈소스 사용을 고지할 수 있도록 Acknowledgements라는 파일을 만들어 지속적으로 업데이트한다.
      • source: 저장소 위치와 버전을 지정할 수 있다. git, hg, svn, http를 지원한다.
      • source_files: 저장소에서 파일을 받아왔다면 이 중 소스 코드는 무엇인지 지정하게 된다. 정규식으로 설정할 수 있다.
      • framework: 링크 시 필요한 프레임워크를 설정한다. 설정된 프레임워크는 CocoaPods 프로젝트에만 존재하며, 프로덕트 프로젝트에는 Pods.xcconfig의 OTHER_LDFLAGS 값에 framework 설정으로 전달된다.
      • resources: 라이브러리에서 제공하는 이미지나 DB 설정 파일과 같은 리소스를 정규식으로 지정해 두면 Copy Pods Resources 빌드 단계에 따라 타겟 빌드 시 포함된다(Facebook-iOS-SDK 참고).
      • requires_arc: 빌드 설정이지만 자주 사용되고 기본값이 변경 중이기 때문에 명확하게 설정하는 것을 추천한다.

.podspec 파일은 오픈소스만 사용할 수 있는 것은 아니다. 기본적으로 여러 소스를 선언적으로 관리하는 것이 목표이기 때문에 오픈소스가 아닌 소스(closed source)에서도 사용할 수 있다. 번들이나 프레임워크, 라이브러리 공유에도 .podspec 파일을 만들어서 프로젝트 저장소에 넣어 놓는 것만으로도 충분히 사용할 수 있다. 다만 소스 다운로드 방식 중 HTTP를 이용하지만 별도 로그인을 사용하는 경우에는 아직까지는 인증을 통과하지 못해서 사용하지 못하는 상황이다.

새로운 라이브러리 등록하기

      1. CocoaPods의 specs 프로젝트를 GitHub에서 포크(fork)해서 저장소로 추가한다.
$ pod repo add REPO 
      1. 쓰기(write) 권한이 있는 저장소에 .podspec 파일을 추가한다.
$ pod push REPO NAME.podspec 
      1. GitHub에서 .podspec 파일을 추가한 커밋을 CocoaPods의 specs 프로젝트에 pull request한다.
      2. 커밋이 반영되면 pod setup으로 받아서 추가 여부를 확인할 수 있다.

마치며

iOS 프로젝트를 위한 의존성 관리 도구를 찾아서 적용하고 오픈소스를 손쉽게 찾아서 적용해 보는 일련의 과정을 통해서 플랫폼 환경에 따라 오픈소스 활성화가 다를 수 있다는 생각이 들었다. 더 나아가 오픈소스를 시작하고 이를 CocoaPods에 등록해 사람들의 호응을 받을 수 있었다. CocoaPods를 이용해 오픈소스를 이용해 보고, 수정할 수 있는 코드와 그렇지 않는 코드들을 잘 분리해 프로젝트를 정리해 보는 것을 추천한다.

7d3aad68ee1585ed170221b3ed0faee3.jpg
Camp Mobile 밴드개발캠프 허혁
한땀 한땀 수공예 코딩이 전문인 한 때 자바에 푹 빠져 있던 프로그래머 입니다. Objective-C와 Core Foundation 코드들을 들여다 보여 컴파일하는 동적 타입언어의 마력에 푹 빠져있습니다. 소스를 돌보고 코드를 키워나가는 재미로 살고 있습니다.