[Swift] Tuist에서 SPM으로 추가한 Objective-C로 만든 라이브러리 에러 해결하는 법

이 글은 tuist 3.7.0 버전 기준으로 작성되었습니다. (제목 참 길다)

Tuist에서 일반적으로 외부 라이브러리를 추가하는 방법은 아래와 같다.

  1. Tuist의 Swift Package Manager
  2. Tuist의 Carthage
  3. Xcode에서 사용되는 Swift Package Manager
  4. XCFramework
  5. CocoaPods

평소에 Tuist의 디펜던시 관리를 1번으로 하게 되는데 Objective-C로 만들어진 라이브러리들을 추가할 때 마다 빌드 오류 또는 런타임 오류가 발생한다… (필자는 FLEX 라이브러리와 SDWebImage를 추가했었음)

FLEX의 경우 빌드 시 아래의 에러가 나타난다.

Undefined symbol: std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::~basic_string()
Undefined symbol: ___gxx_personality_v0

SDWebImage의 경우 런타임에서 아래의 에러가 나타난다.

 *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIImageView sd_setImageWithURL:completed:]: unrecognized selector sent to instance 0x138718130'
*** First throw call stack:
(
	0   CoreFoundation                      0x000000018040e7c8 __exceptionPreprocess + 172
	1   libobjc.A.dylib                     0x0000000180051144 objc_exception_throw + 56
	2   CoreFoundation                      0x000000018041d47c +[NSObject(NSObject) instanceMethodSignatureForSelector:] + 0
	3   UIKitCore                           0x00000001118ffd74 -[UIResponder doesNotRecognizeSelector:] + 232
	4   CoreFoundation                      0x00000001804126c8 ___forwarding___ + 1308
	5   CoreFoundation                      0x0000000180414b4c _CF_forwarding_prep_0 + 92

...

몇 번 삽질을 하다보니.. 해결 방법을 몇 개 알게되었는데 기록차 남긴다.

해결 1: Tuist SPM + Header Search Path 직접 추가

https://github.com/tuist/tuist/issues/3762

https://github.com/tuist/tuist/issues/4180

Tuist 이슈에 적혀있는 해결 방법인데 라이브러리에 직접 Header Search Path를 추가하는 방법이다.

Objective-C로 된 라이브러리 마다 이렇게 하기엔 좀 무리가… 🫠

해결 2: Tuist SPM + Dynamic Framework 로 사용

Tuist의 SPM은 기본적으로 라이브러리들의 Mach-O Type을 Static Framework로 설정한다. 에러가 발생하는 라이브러리들을 Dynamic Framework로 설정하면 오류가 발생하지 않는다.

import ProjectDescription

let swiftPackageManager = SwiftPackageManagerDependencies(
    [
        .remote(url: "https://github.com/FLEXTool/FLEX", requirement: .upToNextMajor(from: "5.0.0")),
        .remote(
            url: "https://github.com/SDWebImage/SDWebImage",
            requirement: .upToNextMajor(from: "5.0.0")
        )
    ],
    productTypes: ["FLEX": .framework, "SDWebImage": .framework]
)

let dependencies = Dependencies(carthage: [],
                                swiftPackageManager: swiftPackageManager,
                                platforms: [.iOS])

위와 같이 productTypes를 추가하고 tuist clean > tuist fetch > tuist generate > Xcode Clean Build 후 다시 빌드하면 작동한다.

P.S. 참고로 CocoaLumberjack은 위 방법이 동작하지 않는다. 3번으로 해결하자.

P.S.2 Static framework와 Dynamic framework는 각각의 장단점을 갖고 있으니 상황에 따라 1번이나 3번으로 해결하자

해결 3: Xcode에서 사용하는 SPM을 사용한다.

https://docs.tuist.io/guides/third-party-dependencies/

Tuist에서 Xcode에서 사용하는 SPM를 사용할 수 있다. 기존 TargetDependency.external 로 정의된 것을 TargetDependency.package 로 대신 사용하면 된다.

대신, Tuist의 캐싱 및 패키지 확인과 같은 중요한 기능에 대한 지원을 잃는다고 한다.

본인에게 맞는 해결 방법을 선택하도록 하자… 🤔