저희가 프로젝트를 하다보면 서비스 개선을 위해 통계을 달고, 얻어 정보를 바탕으로 분석을 진행하게 돼요.
이 과정에서 쉽고 편리하면서 간단하게 통계를 달기위해 많이들 파이어베이스를 사용합니다.

파이어베이스가 보면 구글 소유이기에, 똑같은 구글의 소유인 Android는 파이어베이스의 기능을 매우 잘 지원해줍니다.
그렇기에 보통은 Android에서 통계를 달고자하면 파이어베이스를 쓰는데요, 오늘의 문제 배경은 여기서 시작합니다.
제가 CMP로 마이그레이션을 진행하면서 기존에 Android에 적용되어있던 파이어베이스 통계 또한 마이그레이션을 해야했어요.
통계 관련 클래스는 공통 모듈에서 호출해야했기에 마찬가지로 공통 모듈에 존재해야하는데요. 일단, 파이어베이스는 아직 CMP를 지원하지 않아요.
그렇기에 공통 모듈에 expect를 두고 iosMain, androidMain에서 각각 actual을 구현해서 접근할 수 있도록 해야합니다.
이때, android는 이미 kotlin으로 코드가 작성되어있어 사용에 문제가 없지만, iOS는 스위프트 기반인 코드를 바로 코틀린에서 호출 할 수 없어요.
kotlin/native는 Objective-C/ObjC Runtime로 된 인터페이스만 호출 가능
-> 즉 스위프트로 된 코드를 호출하지 못하고, 오브젝트 C로 변환된 인터페이스만 호출 할 수 있다!
이를 해결하기 위해서는 스위프트 코드를 오브젝트C로 노출하는 브릿지 코드를 작성하고, 이를 cinterop를 통해 코틀린에서 호출가능한 인터페이스로 만드는 복잡한 작업이 필요해요.
이때 만약 cocoapot을 사용해 스위프트 라이브러리를 설치하면 자동으로 cinterop 관련 코드를 생성해줘요.
또한 cocoapot은 gradle에서 바로 사용할 수 있기에 여러 이점이 있죠.
하지만, cocoapot을 실행하기 위해서는 루비 언어를 설치해야하고, 무엇보다 iOS 측면에서는 최근에 cocoapot대신 swift package manager(SPM)을 더 권장하고 있기에 SPM을 통해 애널리틱스를 적용하기로 하였어요.
SPM을 적용하는 것은 좋지만 cinterop로 스위프트 코드를 코틀린에서 쓸 수 있도록 한다는 것은 생각보다 생성하고, 다뤄야할 파일이 많아요.
이러한 SPM 사용 권장의 흐름과 복잡성을 많은 사람들이 공유하였는지 자동으로 cinterop를 생성해주는 플러그인이 존재해요.
The plugin uses the embedded Swift Package Manager, so no third-party dependency is needed, and it's less intrusive.
이 플러그인 사용하고 하단에 설명한대로 세팅하면 우리가 브릿지함수만 만들면 자동으로 cinterop를 적용하여 코틀린에서 사용할 수 있는 코드로 만들어줘요.
먼저 플러그인을 사용할 수 있도록 그래들에 의존성을 적용해야해요.
//프로젝트 단 build.gradle plugin { //... id("io.github.frankois944.spmForKmp") version "0.11.3" } //gradle.property //... kotlin.mpp.enableCInteropCommonization=true //앱 단 build.gradle plugin { //... id("io.github.frankois944.spmForKmp") version "0.11.3" }
이렇게하면 기본적인 의존성은 완료됐어요.
다음으로 이제 저희가 스위프트 브릿지를 작성할 패키지 명을 지정해줘야해요.
플러그인은 해당 패키지 안에 있는 스위프트 코드만 cinterop를 적용해줘요.
각 사용하는 모든 iOS 플랫폼(iosX64(), iosArm64(), iosSimulatorArm64() 등) 에 대해 아래 코드를 적용해주시면 됩니다.
for문을 사용하면 편하게 접근할 수 있어요.
listOf( iosX64(), iosArm64(), iosSimulatorArm64() ).forEach { iosTarget -> //... iosTarget.compilations { val main by getting { cinterops.create("nativeIosShared") } } }
저는 nativeIosShared라고 지정했습니다.
마지막으로 스위프트 패키지 컨피그를 지정해주면 됩니다.
여기서는 이름 그대로 저희가 지정한 패키지명대로 스위프트 패키지를 만들고 최소버전, 의존성 등을 지정하는 기능을해요.
그렇기에 위에 cinterop.create에 적은 이름대로 패키지를 만들고 의존성을 생성할 패키지 명, 버전, uri를 지정해줍니다.
swiftPackageConfig { create("nativeIosShared") { minIos = "15.0" dependency { linkerOpts = listOf("-ObjC") remotePackageVersion( url = uri("https://github.com/firebase/firebase-ios-sdk.git"), products = { add("FirebaseAnalytics", exportToKotlin = false) add("FirebaseCore", exportToKotlin = false) add("FirebaseCrashlytics", exportToKotlin = false) }, version = "12.0.0", ) } } }
이렇게 하면 플러그인이 스위프트 패키지를 만들고, 그곳에 SPM을 통해 파이어베이스 의존성을 제공하게 돼요.
즉 저희가 위에서한 3가지 작업은 아래와 같습니다.
- 플러그인 의존성 적용
- cinterop를 생성할 패키지 명 지정
- SPM 의존성을 제공할 패키지 명 지정 및 생성
이 과정을 거쳐 저희는 해당 패키지 안에서 원하는 파이어베이스 코드를 작성할 수 있게되고, 해당 패키지에서는 cinterop가 자동 적용되니 코틀린 코드에서도 사용할 수 있게 된거에요!
이제 한번 그래들 싱크를 돌리면 자동으로 저희가 이름을 지정한 패키지가 하나 생겨요!

이제 이 안에서 코틀린에서 호출할 브릿지 함수를 작성하면 됩니다.
통계 로깅에 필요한 함수인 logEvent를 예로 들면 아래와 같이 작성할 수 있어요.

앞서 이야기했듯 cinterop는 오브젝트C 인터페이스를 만들어 코틀린에서 사용하는 것이기에 반드시 @objc와 public을 클래스와 함수에 붙여 오브젝트 C에서도 인식가능한 타입으로 만들어야해요.
이러면 최종적으로 코틀린을 사용하는 iOSMain에서 브릿지로 생성한 클래스명과 함수 명 그대로 호출할 수 있게 됩니다!

actual로 그대로 호출하고, 오류가 생기지 않는 모습을 볼 수 있습니다.