티스토리 뷰

서비스 운영할 때는 대부분 develope, production (or Staging) 서버를 함께 운영한다.

dev 에서 테스트하고 추가한 기능들을 릴리즈에 맞춰서 prod로 옮기는 방식일텐데,

서버의 URL이 달라지므로 클라이언트에서는 이 부분에 대한 처리가 필요하다.

 

방법은 여러가지가 있겠지만 이번에 사용한 방법은 Target을 분리하고,

Schem에 따라 처리가 되도록 구현했다.

 

진행한 프로젝트는 Tuist를 사용했기 때문에 Tuist Manifest에서 설정을 시작한다.

 

1. Tuist Manifest - Build Setting

public enum AppEnviroment: String {
    case dev
    case prod
}

public func setEnviroment(to env: AppEnviroment) -> SettingsDictionary {
    return .init(dictionaryLiteral: ("AppEnviroment", .string(env.rawValue)))
}

먼저 AppEnviroment Enum을 만들어준 뒤에 다음과 같이 Setting 을 추가해주는 함수를 만들었다.

 

let prodTarget = Target(
    name: "weave-ios-prod",
    platform: .iOS,
    product: .app,
    bundleId: "-",
    deploymentTarget: .iOS(targetVersion: "17.0",
                           devices: .iphone,
                           supportsMacDesignedForIOS: false),
    infoPlist: .file(path: "Support/weave-ios-Info.plist"),
    sources: ["Sources/**"],
    resources: ["Resources/**"],
    entitlements: .file(path: .relativeToCurrentFile("weave-ios.entitlements")),
    dependencies: [
        .project(target: "Services",
                 path: .relativeToRoot("Projects/Core")),
        .project(target: "DesignSystem",
                 path: .relativeToRoot("Projects/DesignSystem")),
        .package(product: "ComposableArchitecture", type: .macro),
        .package(product: "KakaoSDKCommon", type: .macro),
        .package(product: "KakaoSDKAuth", type: .macro),
        .package(product: "KakaoSDKUser", type: .macro),
        .package(product: "KakaoSDKShare", type: .macro),
        .package(product: "KakaoSDKTemplate", type: .macro),
    ],
    settings: .settings(
        base: setEnviroment(to: .prod)
    )
)

let devTarget = Target(
    name: "weave-ios-dev",
    platform: .iOS,
    product: .app,
    bundleId: "-",
    deploymentTarget: .iOS(targetVersion: "17.0",
                           devices: .iphone,
                           supportsMacDesignedForIOS: false),
    infoPlist: .file(path: "Support/weave-ios-Info.plist"),
    sources: ["Sources/**"],
    resources: ["Resources/**"],
    entitlements: .file(path: .relativeToCurrentFile("weave-ios.entitlements")),
    dependencies: [
        .project(target: "Services",
                 path: .relativeToRoot("Projects/Core")),
        .project(target: "DesignSystem",
                 path: .relativeToRoot("Projects/DesignSystem")),
        .package(product: "ComposableArchitecture", type: .macro),
        .package(product: "KakaoSDKCommon", type: .macro),
        .package(product: "KakaoSDKAuth", type: .macro),
        .package(product: "KakaoSDKUser", type: .macro),
        .package(product: "KakaoSDKShare", type: .macro),
        .package(product: "KakaoSDKTemplate", type: .macro),
    ],
    settings: .settings(
        base: setEnviroment(to: .dev)
    )
)

기존에 있었던 하나의 App Target 을 두개의 타겟으로 분리해주었고,

project setting 에 아까 만들었던 setEnviroment 를 넣어준다.

프로젝트에 따라 빌드세팅이 복잡할수도, xcconfig를 사용할 수도 있지만 
build setting에 값이 들어가도록 하는 것은 동일하다.

 

let project = Project(
    name: "Weave-ios",
    organizationName: nil,
    options: .options(),
    packages: [
        .remote(
            url: "https://github.com/pointfreeco/swift-composable-architecture.git",
            requirement: .exact("1.7.2")),
        .remote(url: "https://github.com/kakao/kakao-ios-sdk", requirement: .branch("master"))
    ],
    settings: nil,
    targets: [prodTarget, devTarget],
    schemes: [],
    fileHeaderTemplate: nil,
    additionalFiles: [],
    resourceSynthesizers: []
)

이렇게 타겟만 분리해줘도 Scheme이 나눠지게 되는데, 

Scheme의 세팅은 기본 세팅으로 설정된다. 추가적인 설정이 필요하거나 사용중이라면 Scheme 설정의 수정이 필요하다.

 

프로젝트를 확인해 보면 target과 Schem이 나눠진 것을 확인할 수 있다.

 

 

그리고, 각 타겟의 빌드 세팅에 가보면 아까 입력한 값이 User-Defined 로 잘 들어가는 것을 볼 수있다.

 

이제 이 값을 가지고 prod와 dev를 분기 할 수 있다.

 

 

2. info.plist

위에서 입력한 buildSetting 값을 런타임에서 읽기 위해서 info.plist 에서 설정이 필요하다.

key를 입력한 뒤에 value는 앞서 입력한 BuildSetting을 가르키도록 한다.

<key>App Enviroment</key>
	<string>${AppEnviroment}</string>

 

 

3. 코드

그럼 이제 준비는 완료가 되었고, 코드에서 info.plist의 값을 읽어야 한다.

진행한 프로젝트에서는 다른 팀원이 네트워크 모듈에 ServerType 분기를 미리 만들어주셨으므로, 
App Enviroment 를 가져와서 설정해주는 작업만 진행했다.

enum ServerType: String {
    case dev // db 개발, api 개발
    case prod // db 상용, api 상용
    
    var baseURL: String {
        switch self {
        case .dev:
            return SecretKey.developURL
        case .prod:
            return SecretKey.releaseURL
        }
    }
}

public class APIProvider {
    static private(set) var serverType: ServerType = {
        if let appEnviroment = Bundle.main.infoDictionary?["App Enviroment"] as? String,
           let serverType = ServerType(rawValue: appEnviroment) {
            return serverType
        }
        assert(false, "App Enviroment가 설정되지 않았습니다")
        return .prod
    }()
    
    ...
}

 

구현한 코드는 다음과 같다. 

Provider 내부의 변수로 serverType 을 가지고 있도록 하고,

네트워크 요청시 baseURL 은 serverType이 가지고 있는 baseURL로 설정되도록 했고, 정상적으로 작동 된다.

 

4. 마무리

본 기능에서 내가 의도한건 코드를 변경하지 않고 prod와 dev를 전환하는 것 이였다.

아무래도 코드에서 변경하면 사람의 실수가 있을 수 있을 수 있다고 생각했기 때문이다.

 

이렇게 구현 하면 매번 prod와 dev를 바꿔줄 때 코드에서 변경해줄 필요 없이 scheme 을 변경함으로 간편하게 대응이 가능하다.

또한 배포를 위해 빌드할 때 prod scheme 이 빌드되도록 신경만 써주면 코드를 통한 변경보다 안전하다고 생각한다.

(fastlane 등에서 특정 scheme이 빌드되도록 사용하면 더 안전할 것 같다.)

 

휴먼 에러를 방지하기 위한 더 좋은 방법은 BundleId 를 분리하는 방법도 있을 것 같다.

BundleId 까지 분리한다면 아예 별도의 앱이 되기 때문에 실수로 Dev를 빌드하는 경우에도 대응이 가능할 것이다.

 

다만, 앱 내의 설정을 prod, dev 에 동일하게 적용해야 하므로 관리 포인트가 늘어나고, provisioning 또한 별도 관리가 필요할 것이다.

 

개발환경 분리는 이외에도 여러가지 방법이 있으니,

프로젝트의 상황에 맞는 방법을 사용하는 것이 좋을 것 같다.

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함