返回

iOS JitsiMeet集成:修复GiphyUISDK `Library not loaded`依赖

IOS

解决 iOS SDK 集成 JitsiMeet 导致的 Pods 依赖 GiphyUISDK 找不到问题

问题来了:SDK 集成 JitsiMeet 后,DemoApp 跑不起来

搞 SDK 开发的时候,一开始图方便,把 SDK 项目和 DemoApp 放同一个 Workspace 里。这样改 SDK 代码,不用每次都编译 XCFramework 就能在 DemoApp 里看到效果,效率确实高。

最近 SDK 需要加个外部框架 JitsiMeet。这家伙得用 Pod 来集成。于是,我在 SDK 项目目录下执行 pod init,然后在生成的 Podfile 里加了这几行:

# platform :ios, '9.0' # 先注释掉,后面根据需要调整

target 'MySDK' do
  use_frameworks! # 使用动态框架

  # MySDK 需要的 Pods
  pod 'JitsiMeetSDK', '10.1.2'
end

跑完 pod install 后,SDK 项目就变成了一个 .xcworkspace。因为不知道怎么在一个 .xcworkspace 里再创建一个 .xcworkspace,我就索性把原来的 DemoApp 项目也拖进了这个由 Pod 生成的新 SDK workspace 里。

然后麻烦就来了。JitsiMeet 自己会带一些依赖,像 Podspec 里写的:

  s.dependency 'Giphy', '2.2.4'
  s.dependency 'JitsiWebRTC', '~> 124.0'

这些依赖在 Xcode 的 Pods 项目里能看到。但是,当我尝试运行 (Cmd + R) DemoApp 的时候,直接闪退,报了这个错:

dyld[34627]: Library not loaded: @rpath/GiphyUISDK.framework/GiphyUISDK
  Referenced from: <EF76E202-DE0D-3F40-ABB3-29F7780A9C63>
....
'/Library/Developer/CoreSimulator/Volumes/iOS_21E213/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 17.4.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/GiphyUISDK.framework/GiphyUISDK' (no such file)

奇怪的是,单独编译 SDK (Cmd + B) 是没问题的。运行时就挂。我猜可能是两个原因:

  1. JitsiMeet 或者它的依赖 (比如 Giphy) 没有被正确安装或者链接。
  2. 我那个 DemoApp 在新的 Workspace 里,没能正确找到或者加载 SDK 通过 Pod 引入的那些依赖。

具体是哪个原因,或者怎么修呢?下面咱们就来分析分析,找找解决办法。

刨根问底:为什么会找不到 GiphyUISDK?

这个 dyld: Library not loaded 错误,是典型的运行时动态链接库找不到的问题。意思是,你的 DemoApp 在启动时,系统(dyld,动态链接器)想去加载 GiphyUISDK.framework,但是按照指定的路径 (@rpath/GiphyUISDK.framework) 没找着。

为什么会找不到呢?主要原因在于 依赖管理和框架嵌入 (Embedding) 的方式。

  1. 依赖关系链条

    • 你的 DemoApp 依赖你的 MySDK
    • 你的 MySDK (通过 Podfile) 依赖 JitsiMeetSDK
    • JitsiMeetSDK (通过它的 Podspec) 依赖 Giphy (提供了 GiphyUISDK.framework) 和 JitsiWebRTC
    • 所以,DemoApp 运行时,间接需要 GiphyUISDK.framework
  2. Cocoapods 的工作方式

    • 当你给 MySDK target 执行 pod install 时,Cocoapods 会下载 JitsiMeetSDK 和它的所有依赖(Giphy 等),并将它们编译成 .framework 文件。
    • 它还会修改 MySDK 项目的 Build Settings,确保 MySDK 在编译时能找到 JitsiMeetSDK 和 GiphyUISDK 的头文件和库,也就是链接 (Linking) 过程。
    • 对于 Application Target(比如 DemoApp),pod install 通常还会自动添加一个 Build Phase(构建阶段),叫做 [CP] Embed Pods Frameworks。这个阶段的作用是,在 App 打包时,把 App 直接依赖的 Pods 编译出来的 .framework 文件拷贝并签名 (Embed & Sign) 到最终的 App Bundle (.app 文件) 的 Frameworks 目录下。这样,App 运行时,dyld 才能在 @rpath (通常指向 App Bundle 里的 Frameworks 目录) 下找到这些框架。
  3. 问题症结

    • 你是在 MySDKPodfile 里声明了依赖。Cocoapods 配置的是让 MySDK 能找到并链接 Giphy。
    • 但是,你的 DemoApp 并不直接依赖 Giphy。它只直接依赖 MySDK
    • 当你把 DemoApp 项目拖进 MySDK.xcworkspace 时,DemoApp 的 Target Build Settings 和 Build Phases 并没有自动被配置去了解或者嵌入 MySDK 的那些间接依赖 (比如 GiphyUISDK)。
    • Cocoapods 主要处理的是 Podfile 中 直接声明 的 target 的依赖。虽然 Giphy 是 MySDK 的依赖,但对 DemoApp 来说,它是一个“依赖的依赖”。除非你在 DemoApp 的 target 里也明确依赖它,或者通过某种方式告诉 DemoApp 需要嵌入这个框架,否则 DemoApp 在打包时就不会包含 GiphyUISDK.framework
    • 结果就是:MySDK 编译没问题 (因为它知道去哪链接 Giphy),但 DemoApp 运行时 dyld 就懵了,在 App Bundle 里找不到所需的 GiphyUISDK.framework

简单说,就是 DemoApp 没有被配置去嵌入 GiphyUISDK 这个间接依赖

见招拆招:修复依赖问题的几种方案

明白了原因,解决起来就有方向了。目标是让 DemoApp 在运行时能找到 GiphyUISDK.framework。下面提供几种方案,推荐程度和适用场景各不相同。

方案一:统一管理,共享 Podfile (最佳实践)

这是最符合 Cocoapods 设计思路,也是最推荐的方案,尤其是在开发阶段。思路是让 SDK 和 DemoApp 共享同一个顶层 Workspace,并且使用一个统一的 Podfile 来管理所有依赖。

原理:
通过一个根 Podfile 定义工作空间内所有需要使用 Pods 的 target(包括 SDK 和 DemoApp)。当你对这个根 Podfile 执行 pod install 时,Cocoapods 会创建一个包含所有项目(SDK、DemoApp、Pods 项目)的 Workspace。最关键的是,它会正确配置 DemoApp Target 的 Build Phase,让它嵌入所有需要的框架,无论是直接依赖还是间接依赖。

操作步骤:

  1. 准备 Workspace:

    • 回到你最初的开发模式:创建一个空的 Workspace (File -> New -> Workspace)。
    • 将你的 MySDK 项目(.xcodeproj 文件,不是 之前 pod install 生成的 .xcworkspace)拖拽到这个新的 Workspace 里。
    • 将你的 DemoApp 项目(.xcodeproj 文件)也拖拽到这个 Workspace 里。
    • 现在的结构应该是:
      MyWorkspace.xcworkspace
      ├── MySDK.xcodeproj
      └── DemoApp.xcodeproj
      
  2. 创建根 Podfile:

    • MyWorkspace.xcworkspace 文件所在的目录下(也就是包含 MySDK.xcodeprojDemoApp.xcodeproj 的那个目录)创建一个名为 Podfile 的文件。
    • 编辑 Podfile,内容类似这样:
    # 定义工作空间使用的项目文件路径 (可选,如果你的项目不在同一级目录下需要指定)
    # workspace 'MyWorkspace.xcworkspace' 
    # project 'MySDK/MySDK.xcodeproj'
    # project 'DemoApp/DemoApp.xcodeproj'
    
    platform :ios, '13.0' # 确保平台版本满足 JitsiMeetSDK 的要求
    
    # 定义一个抽象 target,让 SDK 和 DemoApp 共享 Pods
    # 避免同一个 Pod 被下载和编译多次
    abstract_target 'SharedPods' do
      use_frameworks! # 如果 SDK 或 App 需要 Swift Pods 或你选择使用动态框架
    
      # 在这里放 SDK 和 DemoApp 都需要的 Pods(如果有的话)
    
      # MySDK 的 Target
      target 'MySDK' do
        # 这里只放 MySDK 特有的 Pods,如果 JitsiMeet 只有 SDK 需要,就放这里
        pod 'JitsiMeetSDK', '10.1.2' 
        # 如果 MySDK 不直接引用 Giphy,不用在这里写 Giphy,JitsiMeetSDK 会自动依赖
      end
    
      # DemoApp 的 Target
      target 'DemoApp' do
        # DemoApp 直接需要的 Pods 放这里
        # 比如一些测试、UI 库等
    
        # 关键:如果 DemoApp 需要直接调用 MySDK,它需要能访问到 MySDK
        # 但 Cocoapods 处理的是 Pod 依赖,而不是项目间依赖
        # 项目间依赖在 Xcode Workspace 中设置即可 (后面会提)
    
        # 不需要在这里重复写 JitsiMeetSDK 或 Giphy
        # 因为它们作为 MySDK 的依赖,会被包含在 Pods 项目中
        # Cocoapods 会通过 [CP] Embed Pods Frameworks 确保 DemoApp 能嵌入运行时需要的框架
      end
    end
    
    • 注意: abstract_target 不是必须的,但如果你有多个 target 共享很多 Pods 时,可以避免重复下载和编译。如果 MySDKDemoApp 依赖差别很大,也可以分开写 target,不用 abstract_target。核心是 确保 DemoApp target 在 Podfile 中被定义了
  3. 执行 Pod Install:

    • 在包含 Podfile 的目录下,运行 pod install
    • 完成后,关闭所有旧的 Xcode 项目和 Workspace ,打开新生成的 MyWorkspace.xcworkspace
  4. 配置项目间依赖:

    • MyWorkspace.xcworkspace 中,选中 DemoApp Target。
    • 进入 Build Phases -> Dependencies
    • 点击 +,添加 MySDK Target (从 Workspace 中选择)。
    • 进入 Build Phases -> Link Binary With Libraries
    • 点击 +,添加 MySDK.framework (它应该会出现在 Workspace 分类下)。
    • 进入 General -> Frameworks, Libraries, and Embedded Content
    • 确认 MySDK.framework 在列表中,并且其 Embed 属性通常设置为 Do Not Embed (因为 SDK 代码会编译进 App 主二进制,或者如果是动态库,后续会处理)。对于你的 MySDK,如果是动态框架 XCFramework,最终需要嵌入;但在这个开发阶段,链接通常就够了。而 JitsiMeetSDK 和 GiphyUISDK 等 Pods 框架,Cocoapods 的 Embed Pods Frameworks 脚本会处理它们的嵌入。
  5. 运行 DemoApp:

    • 现在尝试运行 (Cmd + R) DemoApp。Cocoapods 应该已经正确设置了 Build Phase,会自动将 GiphyUISDK.framework 等必要的依赖嵌入到 DemoApp 的 Bundle 中。dyld 错误应该就消失了。

进阶使用与注意事项:

  • 平台版本: platform :ios, '...' 的版本号需要满足你所有 Pods 的最低要求。JitsiMeet 可能需要较高的 iOS 版本。
  • use_frameworks! vs. Static Libraries: use_frameworks! 会让 Cocoapods 将所有 Pods 打包成动态框架 (.framework)。如果不使用它(或者使用 use_frameworks! :linkage => :static),Pods 会被编译成静态库 (.a)。JitsiMeetSDK 本身可能推荐或要求使用动态框架。使用静态库可以减少 App 启动时间,但可能会遇到其他问题(比如资源文件处理、Swift 静态库限制等)。对于 JitsiMeet 这种复杂的 Pod,通常遵循它的集成建议(很可能是 use_frameworks!)。
  • Clean Build Folder: 如果遇到奇怪的编译或链接问题,尝试清理构建文件夹 (Product -> Clean Build Folder 或 Shift + Cmd + K) 后再试。

方案二:手动链接与嵌入 Framework (特定场景)

如果坚持要把 DemoApp 和 SDK 分开管理(比如 DemoApp 有自己的 Podfile,或者根本不用 Pods),或者方案一因为某些特殊原因行不通,可以尝试手动让 DemoApp 知道并嵌入 Giphy 等框架。但不推荐,维护成本高。

原理:
在 DemoApp Target 的 Build Settings 和 Build Phases 里,手动添加对 GiphyUISDK.framework(以及 JitsiMeetSDK 可能依赖的其他需要嵌入的框架)的引用,并确保在构建 App 时将其拷贝到 App Bundle 中。

操作步骤:

  1. 定位 Framework 文件:

    • 首先,你需要找到 MySDK 项目通过 pod install 生成的 Pods 产物。通常在 MySDK 项目目录下的 Pods/ 目录中,或者在 DerivedData 目录下对应的构建产物里找到编译好的 GiphyUISDK.framework。最稳妥的是在你之前能成功编译 MySDK (Cmd+B) 后的 Build 产物目录里找,例如 DerivedData/YourProject-xxxxx/Build/Products/Debug-iphonesimulator/GiphyUISDK/GiphyUISDK.framework
  2. 在 DemoApp 中添加 Framework:

    • 打开包含 MySDK.xcworkspace(或者你把 DemoApp 拖进去的那个 Workspace)。
    • 选中 DemoApp Target。
    • 进入 General -> Frameworks, Libraries, and Embedded Content
    • 点击 + 按钮。
    • 点击 Add Other... -> Add Files...
    • 导航到你找到的 GiphyUISDK.framework 文件,选中它,点击 Add
    • 关键: 在列表中找到刚刚添加的 GiphyUISDK.framework,确保它的 Embed 选项是 Embed & Sign。对于动态框架,运行时必须嵌入到 App Bundle 中。
    • 重复此步骤 :检查 JitsiMeetSDK 的 Podspec,看它是否还有其他运行时必须的动态框架依赖,同样添加并设置为 Embed & SignJitsiWebRTC.framework 也很可能需要嵌入。
  3. 设置 Framework 搜索路径 (可能需要):

    • 如果 Xcode 找不到框架头文件,可能需要配置 Framework Search Paths
    • 选中 DemoApp Target -> Build Settings
    • 搜索 Framework Search Paths
    • 添加包含你手动添加的 .framework 文件所在的目录 的路径。例如,如果你把它们都拷贝到了项目里的某个 VendorFrameworks 目录下,就添加 $(PROJECT_DIR)/VendorFrameworks。如果直接引用 Build 产物路径(不推荐,不稳定),路径会比较复杂。

安全与维护建议:

  • 路径问题: 直接引用 Build 产物路径非常脆弱,一旦清理 DerivedData 或切换配置(Debug/Release)就可能失效。更稳妥的做法是将需要的 .framework 文件从 Pods 构建产物中拷贝 到 DemoApp 项目结构下的某个稳定位置(比如创建一个 VendorFrameworks 目录),然后再从 Xcode 添加引用。
  • 版本更新: 每次 MySDK 更新了 JitsiMeetSDK 或其依赖的版本后,你需要手动 重复上述过程:找到新版本的 .framework 文件,替换旧的,并检查设置。非常繁琐且容易出错。
  • 架构: 确保你添加的 .framework 文件包含了所有需要的架构(比如模拟器 x86_64, arm64 和真机 arm64)。从 Pods 构建产物里拿到的通常是当前构建目标对应的架构。制作 XCFramework 时这个问题更突出。

方案三:检查并调整 Build Phases (深入理解)

这个方案更像是对方案一的补充检查,或者在特定情况下微调 Cocoapods 的行为。

原理:
Cocoapods 通过在宿主 App Target(在这里是 DemoApp)中注入特定的 Build Phase 脚本(如 [CP] Embed Pods Frameworks[CP] Copy Pods Resources)来完成框架嵌入和资源拷贝。如果这些脚本没有正确执行或配置有误,也会导致运行时找不到框架。

操作步骤:

  1. 检查 Build Phases:

    • 确保你在使用了方案一(共享 Podfile)之后进行此检查。
    • 选中 DemoApp Target -> Build Phases
    • 确认存在名为 [CP] Embed Pods Frameworks 的 Run Script Phase。
    • 确认它的位置在 Compile Sources 之后,通常是比较靠后的阶段。
    • 查看脚本内容(展开脚本区域)。它应该会调用一个 Pods/Target Support Files/Pods-DemoApp/Pods-DemoApp-frameworks.sh 类似的脚本。
    • 确认 Input Files / Output Files(如果 Xcode 版本有这些字段)或脚本逻辑看起来是正确的,指向了正确的 Pods 目录和目标 App Bundle。
  2. 检查 Copy Pods Resources Phase:

    • 同样检查是否存在 [CP] Copy Pods Resources Phase,并确保它也在合适的位置执行。JitsiMeet 可能有需要拷贝的资源文件(.bundle 等)。
  3. 确保脚本执行:

    • 确认这些 [CP] 开头的脚本前面的复选框是勾选状态,表示它们会被执行。
    • 在某些罕见情况下,比如项目迁移或手动修改 .xcodeproj 文件后,这些脚本可能丢失或损坏。如果怀疑有问题,可以尝试删除 Pods/ 目录, Podfile.lock 文件和 .xcworkspace 文件,然后重新执行 pod install 来让 Cocoapods 重新生成所有配置。

进阶技巧与风险:

  • 手动修改脚本: 不建议直接修改 Cocoapods 生成的脚本内容,因为下次 pod install 时这些修改会被覆盖。
  • Podfile Hooks: 如果需要自定义 Cocoapods 的行为(比如修改 Build Settings 或添加脚本),最好通过 Podfile 中的 post_install 钩子来实现。这更稳定。
    post_install do |installer|
      installer.pods_project.targets.each do |target|
        # 对每个 Pod Target 进行操作
      end
      installer.aggregate_targets.each do |target|
        # 对每个 App/Extension Target (aggregate target) 进行操作
        if target.name == 'DemoApp'
          # 比如,可以在这里添加自定义的 Build Phase
        end
      end
    end
    
  • 脚本失败: 如果 Build 日志显示 [CP] Embed Pods Frameworks 脚本执行失败,仔细阅读错误信息,可能是路径问题、权限问题或脚本本身有 bug(可能性小)。

关于 XCFramework 打包的提醒

解决了开发阶段的运行问题后,别忘了最终目标是发布 MySDKXCFramework。这里需要注意:

  • 动态框架依赖: 像 JitsiMeetSDK 和 GiphyUISDK 这样的动态框架,通常不应该 被静态链接或直接捆绑进你的 MySDK.xcframework 内部。如果这样做,使用你的 SDK 的 App 可能会遇到重复类定义(如果 App 自己也用了 Jitsi 或 Giphy)或签名问题。

  • 推荐做法: 你的 MySDK.xcframework 应该只包含你自己的代码。然后在文档或 MySDK.podspec (如果你用 Cocoapods 发布 SDK) 中明确声明它依赖于特定版本的 JitsiMeetSDK。最终使用你的 SDK 的 App 需要自己负责在其 Podfile(或其他依赖管理方式)中引入 JitsiMeetSDK(以及其传递依赖)。

  • 传递依赖: 如果你通过 Cocoapods 发布你的 MySDK,可以在 MySDK.podspec 文件中使用 s.dependency 来声明对 JitsiMeetSDK 的依赖。这样,当别人 pod 'MySDK' 时,Cocoapods 会自动帮他们把 JitsiMeetSDK 也下载下来。

    # MySDK.podspec
    Pod::Spec.new do |s|
      # ... 其他配置 ...
      s.dependency 'JitsiMeetSDK', '10.1.2' 
      # 用户 pod 'MySDK' 时,会自动包含 JitsiMeetSDK
    end
    

总之,对于开发阶段,优先使用方案一:统一管理,共享 Podfile ,让 Cocoapods 在 Workspace 级别正确处理所有依赖和嵌入。这能最直接地解决 dyld 找不到 GiphyUISDK 的问题,也最符合常见的 iOS 开发工作流。