背景:
Unity 2019.3+ 引入了全新的 UnityFramework 架构:
旧架构 (Unity 2019.2 及更早):
└── Unity-iPhone.app
└── Unity-iPhone (包含所有代码的可执行文件)
新架构 (Unity 2019.3+):
└── Unity-iPhone.app
├── Unity-iPhone (轻量级启动器)
└── Frameworks/
└── UnityFramework.framework (核心 Unity 代码存放处)
主要改进点:
- Unity C# 代码 → 通过 IL2CPP 编译 → 最终生成 UnityFramework.framework
- Unity 原生插件 → 统一集成到 UnityFramework target
- Unity-iPhone 应用 → 仅作为轻量级启动外壳
iOS应用的限制
- 应用沙盒限制:每个 iOS 应用都在自己的沙盒中运行,只能访问自己沙盒内的文件
- 系统路径限制:不能像传统桌面系统那样将动态库安装在系统路径(如 /usr/lib)
- 代码签名要求:所有可执行代码必须经过苹果的代码签名验证
OS应用沙盒机制要求,所有第三方动态库必须被嵌入到应用包内。如果只链接而未嵌入,应用在设备上启动时,系统在约定的位置找不到这个动态库,就会抛出@rpath相关的错误,也就是链接只在编译时有用,能够减少编译时间
为什么一定要添加到Unity-iPhone中
- 应用包构建:unity-iPhone 是最终生成 .app 包的主 target
- 资源包含:只有添加到 unity-iPhone 的资源才会被打包到最终应用中
- 代码签名:所有嵌入的框架都需要与主应用一起签名
加载方式
unity-iPhone (主应用)
├── 应用启动代码
├── Info.plist
├── 资源文件
└── Frameworks/
├── UnityFramework.framework (动态库)
└── YourCustomFramework.framework (你的动态库) ← 必须在这里
链接三方库和嵌入三方库的不同
- 链接:在Build Phases的 Link Binary With Libraries阶段。这步是告诉编译器:"我这个程序需要这些库"。
- 嵌入:在Build Phases的 Embed Frameworks阶段。这步是告诉打包系统:"请把这些动态库复制到最终的应用包(.app)里"。
Cocos 与 Unity的 target的不同
Cocos的单一Target结构:Cocos Creator打包后通常生成一个主Target(例如mygame-mobile)。当你使用use_frameworks!让CocoaPods以动态框架形式集成库时,这些框架会被自动添加到这个主Target中。CocoaPods的脚本通常会自动配置好编译时的链接和运行时的嵌入,所以即使有动态库,一般也能正常运行,不容易出现@rpath错误
动态库与静态库的区别
🔄 动态库与静态库的核心区别
代码加载机制差异
| 特性 | 静态库 | 动态库 |
|---|---|---|
| 内存布局 | 代码嵌入主可执行文件 | 独立内存段,支持共享 |
| 加载时机 | 应用启动时完整加载 | 支持按需延迟加载 |
| 代码共享 | 每个进程独享副本 | 多进程共享同一物理内存 |
| 热更新 | 不支持 | 理论可行(iOS受限) |
| 耦合度 | 紧密耦合 | 松散耦合 |
内存占用对比
静态库多进程之间引用才会复制多份,单进程多target并不会多份
c
// 静态库场景:3进程共用静态库
进程A: [主程序 + 静态库副本A]
进程B: [主程序 + 静态库副本B]
进程C: [主程序 + 静态库副本C]
总内存 = 3 × (主程序 + 静态库)
// 动态库场景:3进程共用动态库
进程A: [主程序] → 共享动态库
进程B: [主程序] → 共享动态库
进程C: [主程序] → 共享动态库
总内存 = 3 × 主程序 + 1 × 动态库
📱 iOS采用动态库的三大优势
系统框架高效共享,核心系统框架均为动态库:
UIKit.framework
Foundation.framework
CoreGraphics.framework
静态库方案会导致:
- 重复占用磁盘空间
- 内存使用效率低下
- 系统更新复杂化
- 扩展架构支持
- 主应用与扩展共享库资源
- 避免多份静态库副本
- 显著优化存储和内存
模块化开发支持
swift
// 独立模块开发架构
AppModuleA.framework // Team A
AppModuleB.framework // Team B
AppModuleC.framework // Team C
// 优势:
// - 团队独立开发测试
// - 二进制级别集成
// - 版本灵活管理
如何选择
-
AppDelegate 相关功能 → 归入 Unity-iPhone 目标
-
URL 与推送通知处理 → 归入 Unity-iPhone 目标
-
纯 UI 界面(非游戏相关) → 归入 Unity-iPhone 目标
-
Unity C# 调用的 DllImport 方法 → 添加至所有目标(addToAllTargets)
-
UnitySendMessage 相关调用 → 添加至所有目标(addToAllTargets)
-
不确定归属的功能 → 建议添加至所有目标(最稳妥方案)
podfile 中的链接标签
传统静态库(无特殊配置)
- 打包方式:所有代码 → 生成 .a 文件 → 直接链接到主应用
- 优点:⚡ 性能最优
- 缺点:❌ 完全不支持 Swift(致命缺陷),
- 稍微增加包体大小,不是缺陷
动态框架(use_frameworks!)
- 打包方式: 生成 .framework 动态库
- 优点:✅ 完整支持 Swift
- 缺点:🐌 启动时间增加 35-60%(尤其当加载 30+ 个框架时)
静态框架(use_frameworks! :linkage => :static)
- 打包方式:.framework,强制使用静态库,内签到App内
- 优点:✅ 支持 Swift ⚡ 接近静态库的性能
动态框架(use_frameworks! :linkage => :dynamic)
use_frameworks! 的生效范围
- 三方库只能是源码的形式,有cocoapod进行编译
- 对于podfile中包含了static_framework = true/false的三方库,不生效
- 对于预编译好的静态库、或者动态库,不生效
- 优先以三方库的设置为准
@rpath/报错
App加载Unity-iPhone时,在Framework中没找到对应的库。在
如果使用的是use_frameworks! :linkage => :static,说明该库尝试静态链接失败了,需要手动添加到Unity-iPhone这个target即可。
总结
-
使用use_frameworks! :linkage => :static标签,获取更快地启动时间、支持swift
-
如果使用EDM4U插件,dependency 里面使用addToAllTarget=true,防止动态库未静态链接成功,报@rpath错误