在模块化,组件化的热潮下,不管是真的因为业务复杂或者是对于未来业务的提前规划还是只是被这股热潮裹挟着,我们手上不少项目都已经完成了组件化或者正在实现。当历史模块需要修改时,直接使用OC进行是一个很正确的选择,但是某些时候,引入 Swift 也是其中一个选择。
最近也往一个在工作空间直接管理的一个OC模块中引入了Swift,也遇到了一些问题与此记录,以备日后查阅。
1、 项目中混编 OC 和 Swift
Swift 通过 ProjectName-Bridging-Header.h
引入需要的 OC 头文件调用 OC, OC 则通过 引入 ProjectName-swift.h
调用 Swift。
- Xcode 会提示创建桥接文件,点击创建就行,此时会自动生成
ProjectName-Bridging-Header.h
和ProjectName-swift.h
。 - 如果选错了则可以手动创建一个HeaderFile 文件,命名:
ProjectName-Bridging-Header.h
ProjectName 为项目名称,然后在 Build Settings 中,找到 Swift Compiler - General 中的 Objective-C Bridging Header, 输入HeaderFile.h 的路径就行,同样ProjectName-swift.h
会自动生成,如图可见系统通过$(SWIFT_MODULE_NAME)-Swift.h
自动进行命名。
Build Settings - Swift Compiler - General - Objective-C Bridging Header
2、OC framework 与 Swift 混编
主要参考 Swift framework 与 OC 混编 及其Demo GitHub - smallyou/swift-oc-interpolation
在任意位置新建一个 modulemap 文件夹,并在其中新增一个 module.modulemap 文件
注意:
- 该文件的名称一定是 module.modulemap,否则 XCode 无法 import 进来;
- 该文件的位置与 OC 源代码/头文件的位置不做要求,仅仅需要注意的是 modulemap 文件内部引用头文件一定是 OC 头文件的相对位置。
modulemap
文件内容如下, 具体语法格式见:Modules — Clang 18.0.0git documentation (llvm.org)
module ModuleName [extern_c] {
// 相对路径
umbrella header "../ModuleName_Private.h"
export *
module * { export * }
}
ModuleName_Private.h
是一个 OC 的头文件,里面 import 需要开放给 Swift 使用的 OC 头文件。
最开始参考参考demo想多引入其他OC文件时发现报错:
Umbrella for module '********' already covers this directory
Umbrella for module '*****' already covers this directory
然后就需要配置多个 modulemap 来处理,那无疑是不太现实的处理方式,尝试之后采用头文件方式,同时此处 ModuleName
就是这个模块导出的名字,不要自定义命名,否则该 framework 编译正常,后面导出的时候会有问题。
然后在OC 的 framework 的 Build Settings 中,将 Packaging 的 Defines Module 为 Yes:
Build Settings 中,Packaging - Defines Module
接下来,在 OC 的 framework target 中设置 Build Settings, Swift Compiler 的 Import Paths 属性,将其设置为 module.modulemap 的文件路径:
Build Settings, Swift Compiler - Import Paths
接下里在工程任意位置新建一个与 target 同名的 modulemap 文件。
注意:
- 新建的 modulemap 文件存放的路径任意
- modulemap 文件的名称一定要与 target 的产物名称一致
modulemap
文件内容如下, 具体语法格式见:Modules — Clang 18.0.0git documentation (llvm.org)
// ModuleName.modulemap
framework module ModuleName {
// 此处引用你的 umbrella 文件,无需配置路径,直接名称即可。
// 需要注意的是,ModuleName.h 需要在 Build Phases 的 Header 中设置为 Public
umbrella header "../ModuleName.h"
export *
module * { export * }
}
上文提到 module.modulemap 中提到 module 的名字就设置为 ModuleName, 如果不这样设置,此处 umbrella header 会提示 ModuleName 这个module 找不到 Umbrella header '../ModuleName.h' not found
最后 在 Build Settings 的 Packaging 中设置 Module Map File 为当前 modulemap 的路径。
Build Settings - Packaging - Module Map File
此时,万事大吉。workSpaces 项目可以正常运行了。
3、 OC framework 与 Swift 混编后需要导入 Swift 三方库
原项目使用 CocoaPods 管理,配置的 #use_frameworks!, 并且因为某些原因,该项目不能直接改成 use_frameworks! 了事。
如果可以改,直接改了就不会有其他问题!
此时,在 ModelTarget 导入 Swift 库
pod 'SnapKit', '~> 5.6.0'
然后
使用如下脚本配置该库需要编译成动态库:
dynamic_frameworks = ['SnapKit'] # <- swift libraries names
# Make all the other frameworks into dynamic frameworks by overriding the dynamic_framework? function to return true
pre_install do |installer|
installer.pod_targets.each do |pod|
if dynamic_frameworks.include?(pod.name)
puts "Overriding the dynamic_framework? method for #{pod.name}"
def pod.dynamic_framework?;
true
end
def pod.build_type;
Pod::BuildType.dynamic_framework
end
end
end
end
pod install 之后正常编译。
随后开始运行项目报错,程序卡死在启动页: ` dyld[44691]: Library not loaded: @rpath/SnapKit.framework/SnapKit
Referenced from: <4B87D787-447E-33D1-B3A1-C8A1A70BBF95> /private/var/containers/Bundle/Application/7BBAB1C0-064B-460F-BC39-01061BBA2F30/ProjectName.app/ProjectName
Reason: tried: '/usr/lib/swift/SnapKit.framework/SnapKit' (no such file, not in dyld cache), '/private/preboot/Cryptexes/OS/usr/lib/swift/SnapKit.framework/SnapKit' (no such file), '/usr/lib/swift/SnapKit.framework/SnapKit' (no such file, not in dyld cache), '/private/preboot/Cryptexes/OS/usr/lib/swift/SnapKit.framework/SnapKit' (no such file), '/private/var/containers/Bundle/Application/7BBAB1C0-064B-460F-BC39-01061BBA2F30/ProjectName.app/Frameworks/SnapKit.framework/SnapKit' (no such file), '/private/var/containers/Bundle/Application/7BBAB1C0-064B-460F-BC39-01061BBA2F30/ProjectName.app/Frameworks/SnapKit.framework/SnapKit' (no such file), '/private/var/containers/Bundle/Application/7BBAB1C0-064B-460F-BC39-01061BBA2F30/ProjectName.app/Frameworks/SnapKit.framework/SnapKit' (no such file), '/usr/lib/swift/SnapKit.framework/SnapKit' (no such file, not in dyld cache), '/private/preboot/Cryptexes/OS/usr/lib/swift/SnapKit.framework/SnapKit' (no such file), '/usr/lib/swift/SnapKit.framework/SnapKit' (no such file, not in dyld cache), '/private/preboot/Cryptexes/OS/usr/lib/swift/SnapKit.framework/SnapKit' (no such file), '/private/var/containers/Bundle/Application/7BBAB1C0-064B-460F-BC39-01061BBA2F30/ProjectName.app/Frameworks/SnapKit.framework/SnapKit' (no such file), '/private/var/containers/Bundle/Application/7BBAB1C0-064B-460F-BC39-01061BBA2F30/ProjectName.app/Frameworks/SnapKit.framework/SnapKit' (no such file), '/private/var/containers/Bundle/Application/7BBAB1C0-064B-460F-BC39-01061BBA2F30/ProjectName.app/Frameworks/SnapKit.framework/SnapKit' (no such file), '/System/Library/Frameworks/SnapKit.framework/SnapKit' (no such file, not in dyld cache) ` 随后通过 PodFile 的主工程中也引入该库解决,可以自定义 Swift 库引入模块,然后同时在主工程和模块内引入:
代码语言:javascript复制def dependencies_swift_third_party
pod 'SnapKit', '~> 5.6.0'
end
target 'Project' do
platform :ios, '12.0'
...
dependencies_swift_third_party
end
target 'Module' do
platform :ios, '12.0'
...
dependencies_swift_third_party
end
Done!