Swift与Objective-C混编
前言
本文是笔者在解决混编时的一些记录,有些东西可能已经发生了变化。而且由于只是随手记录,写的比较乱,各位看官见谅~~~
笔者负责的业务是以
pod模块
的形式存在于工程中的,所以以下调研的方案只针对于pod
中的混编场景,在MM主工程混编几乎是无缝,没什么可说的。。。推荐大家浏览下
CocoaPods(podfile & podspec)
的API
,没几个,花费不了几分钟,但是却能帮助大家少踩很多的坑,一本万利~
前期方案
暂时采用折中方案,把Swift
独立成一个pod
,然后业务pod
再引用Swift pod
。目的是减少依赖,避免引用不规范的repo
。
如果打算在同一
pod
中混编,只要把你依赖的库都支持module
即可,而且需要修改一下你们引用外部repo
头文件的形式,比如#import "SDWebImage.h"
改为#import <SDWebImage/SDWebImage.h>
在MM主工程中创建个新的
Swift
文件(空文件即可),让Xcode
自动生成一个bridge-header
,目的是营造一个Swift
环境(之前一直想不修改主工程而只在pod中营造,但是很遗憾,最后以失败告终);由于混编
pod
中依赖的repo
需要支持module
,但是MM中的pod
水平参差不齐,大部分都没有支持module
,这就限制我们在业务pod
中混编时编译失败。而让pod
一下子都支持module
是一个不太现实的要求,所以我们暂时采用了一种折中的方案;把
Swift
单独放一个pod
中去,让Swift
尽量少的依赖其他repo
,然后业务pod
再依赖Swift repo
来调用Swift
代码;
踩坑记录
pod
中不支持bridging-header
,所以混编pod
中要想引用OC
类,pod
需要支持module
混编的Swift
库需要打成framework
形式才可以编译成功,比如RxCocoa
、PromiseKit
- 限于苹果本身机制和现有二进制方案实现问题,不支持
:modular_headers => true
,所以使用:modular_headers => true
时临时需要添加参数:use_source_code => true
,切换为代码编译; 报错如下图Swift
与OC
混编的pod
所依赖的库需要改为动态库,比如ZDFlexLayout
内部为Swift与OC混编的,依赖了Yoga
,需要把Yoga
编为动态库。
实际操作
跨模块引用时需要把要暴露给外部的类或者函数的访问权限设置为
public
,并标记为@objc
,同时需要继承自NSObject
类pod
中引用其他pod
都是通过@import
语法Swift
依赖的repo
需要module
化,有3种方式:
i: 在
podfile
中让所有的repo
开启modular
:use_modular_headers!
ii: 只给某几个repo开启modular,举个例子:
pod 'SDWebImage', :modular_headers => true
iii: 让repo自己开启module支持,需要在podspec中修改下设置:
spec.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
, 这个设置不管你开不开启modular
开关,都会自动创建module
如果
podspec
中不设置DEFINES_MODULE=true
,默认是不会生成module
的,哪怕你在podspec
中设置了module_map
也不行,除非你在podfile
中手动开启modular_hear
你自己的modulemap
才会生效如果你手动创建了
modulemap
就不要设置DEFINES_MODULE=true
了,因为笔者发现开启DEFINES_MODULE
后它还会自己再生成一份xxx-umbraller
文件。推荐让
CocoaPods
帮我们创建modulemap
,如非你特别懂modulemap
,不建议自己手动创建。只需要把
Swift
用到的OC
类放到umbrella
中(后面说控制方法)
同一混编pod内OC调用Swift
在头文件中引入 #import <module-name>-Swift.h"
,然后就可以调用Swift
类了
同一混编pod内Swift调用OC
同一pod
中,把oc
类引用放入umbrella
中(默认就有了),然后需要这个文件能被找到。
一种方式是修改此文件的
membership
为public
,目的是为了把它移到public header
中去(静态库形式pod
中的文件默认都是project
的,动态库形式的pod
才会区分public
、private
、project
)修改起来成本比较高,不推荐
第二种方式是把这个文件的路径包含搜索路径中,可以通过设置
podspec
中的spec.header_dir
参数header_dir
可以是任意名字, 笔者一般会设置为./
,即当前文件夹这个选项也不是万能的,你会发现就算设置了这个选项,也会出现报错的问题。建议业务方的pod如无必要,把类都放到
private_header_files
中,减少umbrella
中的头文件数量。如下设置
静态库中的
import
需要是全路径的,而动态库中的搜索路径会被flatten
,所以动态库不会出现此问题
不过这里有点需要注意的是,设置 header_dir
后需要同时设置 module_name
,否则 modulename
默认会取 header_dir
的值。。。
其实官方文档上都有提到,惭愧
解惑
为什么能够混编?
能够互相调用的类都需要继承
NSObject
Swift
中的类和Objective-C
中的类底层元数据(class metadata
)是共用的
|
|
1. XCode9 & Cocopoads 1.5 之后,不是已经支持把Swift编译为静态库了吗,为什么会报错呢?
第三方库对于把混编pod编译为静态库支持的不好,这不是苹果的锅,而是三方库未进行及时的适配,像Kingfisher
、RxCocoa
都有问题
这两个库笔者已经提了
pr
来解决这个问题,现已合入主分支,从RxCocoa 6.1.0
、Kingfisher 6.1.0
开始都已支持编译为静态库;
2. 为什么改为动态库就可以正常编译通过了?
静态库需要使用绝对路径引用,而动态库强制把头文件平铺了,所以动态库能引到,静态库引不到
可以自己验证一下,改成 #import "<module-name>/xxxx.h"
之后你再编译一下
3. 为什么设置 header_dir 编译就不报错了?
默认情况下使用的是普通的header
,设置header_dir
之后,pod
会以header_dir
为名称创建一个文件夹,然后把所有public
出来的头文件引用放里面,umbrella
引用头文件的时候其实指向的都是这里;
见源码
4. pod
中没有bridging-header
为什么Swift
还能引用Objective-C
类?
pod
中umbrella
文件其实就相当于是主工程中的bridging-header
。
5. 为什么Xcode
生成的hmap
对我们的项目并没起到什么作用?
上面已经提到静态库的形式下我们的类文件的membership
是project
,hmap
生成的是#import "xx.h"
形式的引用路径的cache
,而我们通常引用库文件的方式为#import <A/B.h>
,这就导致我们的引用根本就没办法命中hmap
中的映射缓存(pcm
),所以最终还是会走search_path
的查找逻辑。而且由于Xcode
中的USE_HEADERMAP
设置默认是开启的,Xcode
在编译期还会自动创建对我们用处不大的hmap
,而这个过程间接拖慢了我们的编译速度。
6. public
、private
、project
区别
Public: The interface is finalized and meant to be used by your product’s clients. A public header is included in the product as readable source code without restriction.
Private: The interface isn’t intended for your clients or it’s in early stages of development. A private header is included in the product, but it’s marked “private”. Thus the symbols are visible to all clients, but clients should understand that they’re not supposed to use them.
Project: The interface is for use only by implementation files in the current project. A project header is not included in the target, except in object code. The symbols are not visible to clients at all, only to you.
Project
和Private
的权限或者说是作用基本一致,都是私有化的一种方式,只是Project
权限的头文件是不会放到编译产物中的,注意说的是头文件,而Private
头文件会放到编译产物中,只是告诉编译器不要暴漏给外界。CocoaPods
是通过Pods->Headers->Public/Private
目录管理头文件的引用,来控制对某一文件的访问权限的。
推荐设置
podspec
中在不指定private_header_files
或project_header_files
的时候source_files
路径下的文件默认全都是public
的,而public
的头文件默认都会放到umbrella
中,这样很容易导致umbrella
中头文件爆炸,尤其是业务pod
(比如我们的直播业务有2300多个头文件),特别影响编译速度。
解决办法:把那些暴露给swift
的头文件放到public_header_files
中,其他的头文件则默认变成project
类型。或者是把全部头文件默认指定为private_header_files
或project_header_files
,然后把需要公开的放到public_header_files
中,尽量减少umbrella
中头文件的数量。
贴一下供参考的Swift podspec
,请按需修改
|
|
CocoaPods 骚操作:
用踩坑中提到的RxCocoa
做例子,为了编译成功,我们需要把它指定为动态库,而其他的保持不变,这种需求我们可以在 pre_install
阶段动态修改编译模式:把dynamic_framework
数组中的repo
编译为framework
,其他未指定的默认还是静态库。
|
|
|
|