안녕하세요.
오늘은 SPM으로 배포한 라이브러리 탑재 테스트 중에 발생한 에러 해결 방법에 대해 작성해보겠습니다.
제가 SPM으로 배포한 라이브러리는 내부적으로 Kingfisher를 사용하고 있습니다.
따라서 Package.swift의 dependencies에 아래와 같이 Kingfisher를 추가해주었습니다.
dependencies: [
.product(name: "Kingfisher", package: "Kingfisher")
],
그러나 이렇게 배포된 라이브러리를 App에 탑재하여 테스트 하니
dyld[77467]: Library not loaded: @rpath/Kingfisher.framework/Kingfisher
Referenced from: <18D202AE-1902-34DE-B48E-F43223D899E6>
dyld: Library not loaded 에러가 나며 앱에 실패했습니다.
이 에러는 dyld(Dynamic Link Editor)가 @rpath/Kingfisher.framework/Kingfisher 를 로드하지 못했을 때 발생하는 에러입니다.
Reason: tried: '~Library/Developer/Xcode/DerivedData/KingfisherTest-evnpdmnjmvrnelbsjxzpmdcolgsk/Build/Products/Debug-iphonesimulator/Kingfisher.framework/Kingfisher' (no such file)
dyld는 이유도 알려주고 있습니다.
해당 경로에서 Kingfisher.framework/Kingfisher 를 찾지 못했다고합니다.
위 경로에서 확인해보니 Kingfisher는 Kingfisher.framework가 아니라 Kingfisher.swiftmodule 형식으로 추가되어있는 것을 확인할 수 있었습니다.

여태까지 다른 라이브러리들은 A.framework 형식으로 추가되어서 dyld가 찾을 수 있었는데, Kingfisher는 Kingfisher.o, Kingfisher.swiftmodule 등으로 추가되었기 때문에 dyld 가 찾을 수 없었고 에러가 난 것입니다.
그렇다면 어떤 라이브러리는 .o, .swiftmodule 로 추가되고 어떤 라이브러리는 .framework 로 추가될까요?
이는 라이브러리의 패키지 구성에 따라 결정됩니다.
Kingfisher를 SPM으로 받아보면 아래와 같이 Swift 소스로 구성되어있는 것을 확인할 수 있습니다.

Kingfisher의 Package.swift도 확인해보면

소스코드 자체를 받아서 빌드하는 구조임을 알 수 있습니다.
따라서 빌드 결과물이 .framework가 아니라 모듈 단위(swiftmodule)로 앱이 직접 링크 됩니다.
반대 예시로써, AdFitSDK를 확인해보면 아래와 같이 Swift 소스가 아니라 AdFitSDK.framework 로만 구성되어 있습니다.

AdFitSDK의 Package.swift도 확인해보면

소스가 아니라 framework를 빌드하는 구조임을 알 수 있습니다.
SPM은 이 framework를 그대로 Xcode 빌드 폴더에 복사해줍니다.
즉, 해당 라이브러리가 Swift 소스로 구성되어있으면 SPM이 .swiftmodule, .o 형식으로 추가하고
framework 형식으로 구성되어 있으면 SPM이 그대로 복사하여 .framework 형식으로 추가됩니다.
이 이슈를 해결하기 위해서는 Kingfisher도 framework 형식으로 제공해야합니다.
Kinfisher는 소스를 모두 제공해주고 있으니 xcframework로 패키징하고 이를 binaryTarget으로 추가해주면 됩니다

고맙게도 Kingfisher는 github release에서 xcframework를 제공하고 있어 여기서 다운받아 사용했습니다.
그 다음부터는 SPM 배포 에서와 동일하게 xcframework.zip 을 공개 저장소에 push하고 , checksum 확인하여

위와 같이 binaryTarget으로 kingfisher를 작성해주면 됩니다!
테스트 해보면 정상 동작함을 확인하실 수 있을거에요 🎉
문제는 해결되었지만
dyld는 왜 .framework 형식의 라이브러리만 link 하려고 할까요?
dyld는 Mach-O 포맷을 가진 모든 동적 라이브러리(dylib)를 로드할 수 있습니다.
MyLib.framework/MyLib
dyld의 역할은 앱 실행 시점에 필요한 바이너리를 메모리에 로드하고 연결하는 것입니다.
즉 어떤 바이너리 코드(기계어)를 어디에 올려 실행할지를 관리합니다.
그러나, .swiftmodule은 컴파일 시점용 파일입니다.
기계어 코드가 아니라 컴파일러가 다른 모듈을 import할 때 사용하는 파일로
타입, 함수, 프로토콜 등을 컴파일러에게 알려주는 역할을 합니다.
정리하자면,
dyld는 런타임에 바이너리 코드(기계어) 가 포함된 Mach-O만 로드할 수 있고,
.swiftmodule은 컴파일타임에만 쓰이는 인터페이스 정보이기 때문에 dyld가 로드할 수 없습니다.