Search

Configuration으로 dev, prod 서버 분리해보기

 들어가며

현재 진행하는 프로젝트에서는 Dev, Prod 서버를 사용합니다.
서버가 나눠져 있기 때문에 개발 시에는 Dev 서버와 Dev 서버용 GoogleService-Info.plist를 사용해야 합니다. 배포 시에는 Prod 서버와 Prod 서버용 GoogleService-Info.plist를 사용해야 합니다.
따라서, Dev와 Prod 서버를 매번 손수 바꿔주고 GoogleService-Info도 Dev, Prod 별로 다른 파일을 가지기 때문에 손수 바꿔주어야 합니다. 굉장히 귀찮습니다.. ^^;
따라서, 제가 Build Scheme만 바꾸면 원하는 Configuration의 서버와 GoogleService-Info 파일을 가질 수 있도록 만들어주려고 합니다.

 Configuration 생성

저희는 Dev와 Prod 서버를 가지기 때문에 이 두 가지 상태를 구분할 수 있게 두 가지 Configuration를 만들어주어야 합니다.
Navigator 부분에서 Project 이름을 클릭하면 하단의 화면이 중간에 보일 겁니다. 그 중에서 PROJECT를 누르고 Info 버튼을 누르면 Configurations 영역을 가진 부분이 나타납니다.
*이미 설정이 완료된 화면을 캡처해서 Based on Configuration File이 설정되어 있습니다.
Configurations 영역에 Name를 설정해주세요. 저는 Dev, Prod로 이름을 설정해주었습니다.
Based on Configuration File 부분에는 각 Configuration에 지정할 xcconfig 파일을 적용해주어야 합니다. 현재 저희는 xcconfig 파일이 없습니다. 따라서, Dev, Prod xcconfig 파일을 만들어 줄게요.
xcconfig 파일을 만들기 전에 xcconfig 파일을 저장할 Configuration 폴더를 만들었습니다.
해당 폴더에서 New File를 누른 후에 Configuration Settings File를 눌러주세요.
저는 각각의 파일명을 Dev, Prod로 설정했습니다.
그리고 아까 Configuration 영역으로 돌아가서 Based on Configuration File에 xcconfig 파일을 적용해주었습니다.

 Build Scheme 설정

Configuration를 생성하고 설정해주었으니 이번엔 Build Scheme를 Configuration 별로 분리해줍니다.
Build Scheme이 뭔가요?
우리가 시뮬레이터 설정을 위해서 누르는 상단 바 영역 옆에 존재하는 저 빨간 네모가 Build Scheme를 변경하는 부분입니다. 해당 부분을 누르면 빌드를 돌릴 Scheme를 생성하고 변경할 수 있습니다.
아직 설정이 안되어 있는 Scheme은 <프로젝트명>으로 되어 있고, Edit Scheme를 누르면 Dev Configuration으로 설정되어 있을 겁니다. 해당 Scheme 이름을 <프로젝트명>-Dev로 저는 바꿔주었습니다.
Manage Schemes에서 해당 Scheme를 누르고 엔터를 누르면 이름을 바꿀 수 있습니다.
Dev Configuration를 위한 Build Scheme은 있으니, Prod Configuration를 위한 Build Scheme를 만들어 봅시다. Manage Schemes에서 하단 + 버튼을 누르면 새로운 Scheme를 추가할 수 있습니다.
<프로젝트명>-Prod로 Scheme 이름을 설정했습니다. 그리고 해당 Scheme를 더블 클릭해서 Configuration를 수정해줍시다. Prod Scheme에서 빌드하면 우리는 Prod Configuration에서 빌드되도록 할 겁니다.
해당 Build Configuration를 원하는 Configuration으로 변경해주시면 됩니다.
그리고 Scheme에 따라 Configuration이 제대로 설정 되는지 확인하기 위해서 출력문을 작성해봤습니다.
하지만, else 부분이 출력되는 매직..
알고보니 제가 Custom Flags를 설정해주지 않았기 때문이었습니다.
TARGETS에서 Build Settings를 누르고 custom flags를 누르면 Swift Complier - Custom Flags라는 영역이 보입니다.
Other Swift Flags에 Configuration 별로 Flags를 설정할 수 있는데 저는 DEV, PROD로 사용할거라서 -DDEV, -DPROD로 작성해주었습니다.
Custom Flag를 설정해주고 Build Scheme를 바꿔가며 print문을 출력해보면 잘 나오는걸 확인할 수 있습니다.

 Configuration 코드 작성

Configuration에 맞춰서 두 가지 부분이 달라져야 합니다. ⓵BaseURL과 ⓶GoogleService-Info.plist 파일입니다. ⓵번부터 설정해줍시다.
먼저, Dev.xcconfigProd.xcconfig 파일 내부에 ROOT_URL를 다르게 설정하는 코드를 작성해줄게요.
// Server URL ROOT_URL = https:/$()/<설정할서버url를작성하세요>
Swift
복사
그리고, Info.plist에 ROOT_URL이라는 Key를 만들어서 Value를 $(ROOT_URL)로 설정해주세요. Source Code로 했을 경우에는 하단 코드를 붙여 넣어주시면 됩니다. Configuration에 있는 ROOT_URL를 Info.plist가 가져와서 ROOT_URL로 설정해줄겁니다.
<key>ROOT_URL</key> <string>$(ROOT_URL)</string>
Swift
복사
그러면 Info.plist에서 해당 ROOT_URL이라는 Key를 가진 Value를 가지고 와서 해당 Value를 코드 상에서 사용할 수 있도록 설정할 필요가 있겠네요.
Environment라는 구조체 타입을 하나 만들고, 내부에 Plist에 접근할 수 있는 코드를 작성하겠습니다. infoDictionary로 Info.plist에서 원하는 Key값에 해당하는 Value를 가지고 올 수 있을겁니다.
struct Environment { // MARK: - Plist private static let infoDictionary: [String: Any] = { guard let dict = Bundle.main.infoDictionary else { assertionFailure("info.plist hasn't key value") return [:] } return dict }() }
Swift
복사
Key 값으로 Value를 가지고 와봅시다. Keys라는 enum 타입을 만들고 Key에 해당하는 String를 저장해둡니다. 그리고 rootURL이라는 프로퍼티를 만들어서 infoDictionary에서 Keys.rootURL에 해당하는 String 타입의 값을 가지고 와서 설정해줍시다.
struct Environment { // MARK: - Keys private enum Keys { static let rootURL = "ROOT_URL" } // MARK: - Values static let rootURL: String = { guard let rootURL = Environment.infoDictionary[Keys.rootURL] as? String else { assertionFailure("rootURL isn't vaild") return "" } return rootURL }() }
Swift
복사
그리고, 해당 rootURL를 네트워크 코드에서 baseURL로 설정해주었습니다.
enum APIEnvironment { static var baseUrl: String { return Environment.rootURL } }
Swift
복사
이제 Build Scheme 별로 빌드를 돌려보면 다른 baseURL이 적용이 되는걸 확인할 수 있습니다. (∗❛⌄❛∗)
Dev Configuration
Prod Configuration

 GoogleService-Info 설정

⓶GoogleService-Info.plist 파일을 설정해줍시다. GoogleService-Info 파일이 Configuration 별로 다릅니다. Configuration 별로 다른 두 개의 파일을 넣어줍시다. 그리고 파일이 구분되도록 이름을 다르게 설정해주겠습니다.
파일 추가 시에 Target Membership은 해제해주세요. Run Script를 통해서 GoogleService-Info 파일을 만들어 줄거라서 타겟을 해제해주어야 합니다.
TARGETS에서 프로젝트 이름을 클릭하고 Build Phases에서 + 버튼을 눌러서 New Run Script Phase를 눌러줍니다.
해당 Script 이름은 원하는대로 설정해주시면 됩니다. 그리고 내부에 하단 코드를 넣어주시면 됩니다. “” 안에 설정되는 Path가 폴더 구조에 따라서 다를 수 있으니 현재 프로젝트에 맞춰서 Path를 수정해주시면 됩니다.
case "${CONFIGURATION}" in "Dev" ) cp -r "$SRCROOT/Configuration/Development/GoogleService-Info-Dev.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist" ;; "Prod" ) cp -r "$SRCROOT/Configuration/Production/GoogleService-Info-Prod.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist" ;; *) ;; esac
Swift
복사
만약, Configuration 폴더를 Project 내부 폴더로 만들어주셨다면 $SRCROOT 밑에 ${PROJECT_NAME}를 Path로 넣어주시면 됩니다. 프로젝트를 빌드하게 되면 해당 plist 파일을 GoogleService-Info.plist로 복사해서 실행하게 됩니다.
시뮬레이터를 실행하면 GoogleService-Info.plist가 생깁니다. plist 파일 내부를 보면 Build Scheme별로 다르게 설정되어 있는걸 확인할 수 있어요.

 +⍺ ) Output Files

저도 몰랐는데, 빌드 시에 Warning이 계속 발생했었나봅니다. 그걸 찾아서 알려주신 noah님…
하단 Warning은 GoogleService-Info.plist의 출력 파일을 지정하지 않아서 발생하는 경고였습니다. 따라서, Custom Output Files를 따로 지정해주었습니다.
run script build phase 'googleservice-info.plist' will be run during every build because it does not specify any outputs. to address this warning, either add output dependencies to the script phase, or configure it to run in every build by unchecking "based on dependency analysis" in the script phase.
Plain Text
복사
해당 Build Script를 통해서 생성되는 Output Files이 GoogleService-Info.plist 파일이기 때문에 Output File로 해당 파일의 경로를 추가해주었습니다.
$(DERIVED_FILE_DIR)/${PRODUCT_NAME}.app/GoogleService-Info.plist
Plain Text
복사
더이상 경고문이 뜨지 않는걸 확인할 수 있습니다.
Build Configuration 관련 Apple Document에서 Input, Output 관련 Note가 있었는데, 이 부분을 제대로 안보고 넘겼는데, 이런 내용이었습니다.
You don’t have to specify input and output files, but it’s highly recommended that you do. Xcode uses the set of input and output files to optimize build times, by running your script only when necessary. If you don’t specify input or output files, Xcode runs your script every time you build the target. For more information, see Improving the speed of incremental builds.
특정 문장을 보면,
Xcode uses the set of input and output files to optimize build times, by running your script only when necessary. Xcode는 입력 및 출력 파일 집합을 사용하여 필요한 경우에만 스크립트를 실행하여 빌드 시간을 최적화합니다.
Output File를 추가하게 되면 빌드 시간이 최적화된다는걸 알 수 있습니다.
Note 부분에서 언급한 링크를 통해서 들어가면 동일한 부분을 강조하는걸 볼 수 있습니다.
You must still specify an input and output file to prevent Xcode from running the script every time, even if your script doesn’t actually require those files. For a script that requires no input, provide a file that never changes as the input file. For a script with no outputs, create a static output file from your script so Xcode has something to check.
Xcode는 Input File이 변경될 때만 Script를 실행하여 최적화하기 위해서 Output File를 사용하게 됩니다.

 참고자료