iOS의 프로젝트가 커지다 보면 코드를 다수의 라이브러리들로 나누어야 한다. 불행이도 iOS에서는 동적 라이브러리가 지원되지 않기 때문에 iOS에서 라이브러리를 만든다면 정적인 라이브러리를 말한다. (비공식적인 방법도 있다고는 하지만 테스트는 해보지 않았다.)
Fat 타입
라이브러리를 만들때 Fat 타입으로 만들면 사용하지 쉽다. Fat 타입은 여러가지 CPU 아키텍쳐의 라이브러리가 한 파일에 포함되어 있는 파일이다.
$> file libcrypto.a libcrypto.a: Mach-O universal binary with 3 architectures libcrypto.a (for architecture i386): current ar archive random library libcrypto.a (for architecture armv7): current ar archive random library libcrypto.a (for architecture cputype (12) cpusubtype (11)): current ar archive random library
위에서 처럼 file 명령을 사용하면 원하는 라이브러리가 Fat 타입인지 확인할 수 있다. 위 예제를 보면libcrypto.a 파일에 i386, armv7, armv7s ( cputype (12) cpusubtype(11) 이라고 되어 있다. ) 가 지원됨을 할 수 있다. i386은 시뮬레이터에서 사용할 파일이고 armv7, armv7s는 실제 기기에 올라가는 앱을 만들때 사용된다.
Fat 타입 라이브러리 만들기
Fat 타입 라이브러리를 만들기 위해서는 i386, armv7, armv7s로 빌드된 라이브러리가 있어야 한다. 각각의 플랫폼 별로 라이브러리가 만들어져 있다면 다음 명령으로 하나의 Fat 파일을 만들 수 있다.
$> lipo -ouput libtest.a -create libtest_i386.a libtest_armv7.a libtest_armv7s.a
lipo 명령을 이용하게 되는 lipo파일로 여러타입으로 빌드된 라이브러리를 하나로 만들 수 있다. lipo 명령은 비단 라이브러리에만 적용되는 것 아니다. mac의 실행파일로 Fat 파일로 되어 있다. Mac에 인텔 CPU는 사용하기 전에는 Power PC를 사용했는데 앱을 개발하는 입장에서 각 CPU별로 따로 빌드해서 배포하는 것을 귀찮은 일이다. 이럴때 lipo를 이용해서 여러 타입의 바이너리를 하나로 합칠 수 있다.
쉬운 방법
Fat 타입 라이브러리를 만드는 과정은 lipo 명령을 이용해서 수동으로 만들 수도 있지만
Xcode만을 주로 사용한다면 Xcode에 쉘스트립트를 추가해서 Xcode 빌드시마다 Fat 라이브러리를 만들 수 있다.
그림 1. 스크립트 추가
그림 1과 같이 Static Library 프로젝트의 Build Phases에서 Add Run Script를 통해서 빌드 과정에서 필요한 스트립트를 추가할 수 있도록 한다.
다음 스크립트를 Run Script에 추가한다. ( 출처 : http://stackoverflow.com/questions/3520977)
# # c.f. StackOverflow question/answer here: http://stackoverflow.com/questions/3520977/build-fat-static-library-device-simulator-using-xcode-and-sdk-4 # # Version 2.5 # # Latest Change: # - The "copy headers" section now respects the build setting for the location of the public headers # # Purpose: # Create a static library for iPhone from within XCode # Because Apple staff DELIBERATELY broke Xcode to make this impossible from the GUI (Xcode 3.2.3 specifically states this in the Release notes!) # ...no, I don't understand why they did this! # # Author: Adam Martin - http://twitter.com/redglassesapps # Based on: original script from Eonil (main changes: Eonil's script WILL NOT WORK in Xcode GUI - it WILL CRASH YOUR COMPUTER) # set -e set -o pipefail #################[ Tests: helps workaround any future bugs in Xcode ]######## # DEBUG_THIS_SCRIPT="false" if [ $DEBUG_THIS_SCRIPT = "true" ] then echo "########### TESTS #############" echo "Use the following variables when debugging this script; note that they may change on recursions" echo "BUILD_DIR = $BUILD_DIR" echo "BUILD_ROOT = $BUILD_ROOT" echo "CONFIGURATION_BUILD_DIR = $CONFIGURATION_BUILD_DIR" echo "BUILT_PRODUCTS_DIR = $BUILT_PRODUCTS_DIR" echo "CONFIGURATION_TEMP_DIR = $CONFIGURATION_TEMP_DIR" echo "TARGET_BUILD_DIR = $TARGET_BUILD_DIR" fi #####################[ part 1 ]################## # First, work out the BASESDK version number (NB: Apple ought to report this, but they hide it) # (incidental: searching for substrings in sh is a nightmare! Sob) SDK_VERSION=$(echo ${SDK_NAME} | grep -o '.\{3\}$') # Next, work out if we're in SIM or DEVICE if [ ${PLATFORM_NAME} = "iphonesimulator" ] then OTHER_SDK_TO_BUILD=iphoneos${SDK_VERSION} else OTHER_SDK_TO_BUILD=iphonesimulator${SDK_VERSION} fi echo "XCode has selected SDK: ${PLATFORM_NAME} with version: ${SDK_VERSION} (although back-targetting: ${IPHONEOS_DEPLOYMENT_TARGET})" echo "...therefore, OTHER_SDK_TO_BUILD = ${OTHER_SDK_TO_BUILD}" # #####################[ end of part 1 ]################## #####################[ part 2 ]################## # # IF this is the original invocation, invoke WHATEVER other builds are required # # Xcode is already building ONE target... # # ...but this is a LIBRARY, so Apple is wrong to set it to build just one. # ...we need to build ALL targets # ...we MUST NOT re-build the target that is ALREADY being built: Xcode WILL CRASH YOUR COMPUTER if you try this (infinite recursion!) # # # So: build ONLY the missing platforms/configurations. if [ "true" == ${ALREADYINVOKED:-false} ] then echo "RECURSION: I am NOT the root invocation, so I'm NOT going to recurse" else # CRITICAL: # Prevent infinite recursion (Xcode sucks) export ALREADYINVOKED="true" echo "RECURSION: I am the root ... recursing all missing build targets NOW..." echo "RECURSION: ...about to invoke: xcodebuild -configuration \"${CONFIGURATION}\" -project \"${PROJECT_NAME}.xcodeproj\" -target \"${TARGET_NAME}\" -sdk \"${OTHER_SDK_TO_BUILD}\" ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO" BUILD_DIR=\"${BUILD_DIR}\" BUILD_ROOT=\"${BUILD_ROOT}\" SYMROOT=\"${SYMROOT}\" xcodebuild -configuration "${CONFIGURATION}" -project "${PROJECT_NAME}.xcodeproj" -target "${TARGET_NAME}" -sdk "${OTHER_SDK_TO_BUILD}" ${ACTION} RUN_CLANG_STATIC_ANALYZER=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" SYMROOT="${SYMROOT}" ACTION="build" #Merge all platform binaries as a fat binary for each configurations. # Calculate where the (multiple) built files are coming from: CURRENTCONFIG_DEVICE_DIR=${SYMROOT}/${CONFIGURATION}-iphoneos CURRENTCONFIG_SIMULATOR_DIR=${SYMROOT}/${CONFIGURATION}-iphonesimulator echo "Taking device build from: ${CURRENTCONFIG_DEVICE_DIR}" echo "Taking simulator build from: ${CURRENTCONFIG_SIMULATOR_DIR}" CREATING_UNIVERSAL_DIR=${SYMROOT}/${CONFIGURATION}-universal echo "...I will output a universal build to: ${CREATING_UNIVERSAL_DIR}" # ... remove the products of previous runs of this script # NB: this directory is ONLY created by this script - it should be safe to delete! rm -rf "${CREATING_UNIVERSAL_DIR}" mkdir "${CREATING_UNIVERSAL_DIR}" # echo "lipo: for current configuration (${CONFIGURATION}) creating output file: ${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}" lipo -create -output "${CREATING_UNIVERSAL_DIR}/${EXECUTABLE_NAME}" "${CURRENTCONFIG_DEVICE_DIR}/${EXECUTABLE_NAME}" "${CURRENTCONFIG_SIMULATOR_DIR}/${EXECUTABLE_NAME}" ######### # # Added: StackOverflow suggestion to also copy "include" files # (untested, but should work OK) # echo "Fetching headers from ${PUBLIC_HEADERS_FOLDER_PATH}" if [ -d "${CURRENTCONFIG_DEVICE_DIR}${PUBLIC_HEADERS_FOLDER_PATH}" ] then mkdir -p "${CREATING_UNIVERSAL_DIR}${PUBLIC_HEADERS_FOLDER_PATH}" # * needs to be outside the double quotes? cp -r "${CURRENTCONFIG_DEVICE_DIR}${PUBLIC_HEADERS_FOLDER_PATH}"* "${CREATING_UNIVERSAL_DIR}${PUBLIC_HEADERS_FOLDER_PATH}" fi fi
설정이 끝났다면 terminal에서 다음 명령을 실행해서 빌드 해보자. ( xcodebuild 명령어 사용 )
$> xcodebuild test.xcodeproj build
이렇게 빌드를 하면 다음 경로에 Fat 라이브러리가 보인다.
build/Release-universal/
이제 이 라이브러리를 사용하면 시뮬레이터와 디바이스용으로 빌드할 때 같은 파일을 사용할 수 있다.
마무리
iOS 앱을 직접 개발을 한다면 static library를 작성할 일을 많지 않을 것이다. 대부분 소스 자체를 공유해서 쓰는 편이 문제가 더 적기 때문이다. 하지만 조금 큰 프로젝트라면 static library를 만들어야 할 때가 있다. 이럴때 사용하면 유용할 팁이다.
참고
[출처] http://10apps.tistory.com/123