14年6月3日苹果发布Swift以来,这门语言以让人惊讶的速度在成长,越来越多的开发者关注学习,很多App和开源库也在从Objective-C迁移到Swift上。
Swift语法确实更新进、更漂亮,而在实际开发过程中,由于Objective-C更贴近底层,可以使用如OC Runtime这样的黑魔法,很多开源库也是依赖其实现。因此OC和Swift混编应该一个长期的趋势,之前只是依赖于Xcode自动引入bridge header等类似的机制,没有仔细去理解,借着新做项目用到evernote oc库的机会好好的总结一下。苹果提供的Swift与Objective-C混编方案都是基于Xcode和LLVM编译,采用Mix and Match机制。
从开发者实现角度根据不同的混编场景可以分为如下几种情况:
普通代码混编:项目内普通代码文件混编(.swift内使用OC的.h和.m文件或者反过来,包括.a形式项目的开发),采用的bridge方案;
开发Framework混编:如果你的项目是输出一个Framework,混编方式稍有不同,姑且成为umbrella方案;
引用外部Framework和宿主App混编:如果你的项目引用一个外部提供的Framework(无论这个Framework是单一语言开发还是本身就是混编的),混编方案也有不同。> 详细的原理参见上文提到的官方文档,本文主要关注三种方式的实现以及可能遇到的问题。
普通代码文件混编方案:
Swift引用OC实现通过桥接头文件,OC引用Swift实现直接importProductModuleName-Swift.h
这个文件即可。
OC引用Swift实现
ProductModuleName
在Build Settings
里面配置:
默认用ProductName,可以支持自定义。(注明:Framework项目不支持自定义)
Swift引用OC实现
Swift引用OC实现稍微麻烦一点,需要自己生成一个bridge header文件,和创建普通.h方式相同File > New > File > (iOS, watchOS, tvOS, or OS X) > Source > Header File
,名字随意,然后配置到Build Settings - Swift Compiler - Code Generation
下的Objective-C Bridging Header
选项。
注意路径从项目根目录开始计算,可以使用..
来指定与根目录平级目录。bridge header内import所有想要在swift中使用的OC类,就会作为一个module在swift中使用。例如:
#import "XYZCustomCell.h"
#import "XYZCustomView.h"
#import "XYZCustomViewController.h"
Swift中用如下代码访问:
let myOtherCell = XYZCustomCell()
myOtherCell.subtitle = "Another custom cell"
FYI. 语言类型为Swift的项目引入OC文件时Xcode会给个创建bridge header的提示,自己会配置了之后用处不大:
Framework项目中使用代码混编方案:
Umbrella Header的相关知识苹果没有给出很明确的说明,只有以前介绍Umbrella Framework的时候介绍过,找了很久发现iOS - Umbrella Header在framework中的应用这篇文章介绍的很好,详细的内容可以进入了解。
Swift引用OC实现
现在我们只需要了解Framework里面Swift引用OC逻辑需要一个与ProductName同名的.h文件作为Umbrella Header,如果不存在则创建一个。不需要在Build Settings
配置因为这文件是map modules的时候自动指定的,如果基于某种原因(比如这个同名文件已经被用来写其他逻辑)一定要自定义的话可以参考上面文章里介绍的方法。第二步到Build Settings - Packaging
中将Defines Module
选项设为YES
。然后将Swift中需要引用的OC逻辑引用进来,访问方式同普通代码混编。
#import "XYZCustomCell.h"
#import "XYZCustomView.h"
#import "XYZCustomViewController.h"
OC引用Swift
实现OC引用Swift同样需要将Defines Module
选项设为YES
,其余和普通代码混编相比只是改了个引用文件的方式:#import
。
引用外部Framework时混编
方案:
重要前提
这里有一个重要的前提是这个外部Framework在编译时必须开启了Defines Module
,如果没有开启并且没有Framework源码的情况下还是绕路吧。
external framework混编
在这种情况下当前App使用外部Framework是不关心其内部到底是Swift实现、OC实现还是本身就是混编实现的。只需要Swift使用Framework逻辑时添加import FrameworkName
,OC使用时在任意.m
文件中添加@import FrameworkName;
语法即可。
混编后哪些逻辑可以被另一种语言引用到?
Swift中可以被OC引用的逻辑:
用
public
关键字;有bridging header的target中用
internal
关键字修饰;用
private
修饰的关键字通常是访问不到的,除了@IBAction, @IBOutlet, 和 @objc标记;
OC中由于开发习惯的原因基本上头文件中的属性、方法都可以被swift访问到。
Evenote-Mac Framework混编时遇到的问题
-
Evenote-Mac
这个奇葩的Framework名字在生成umbrella header的时候报错:
warning: EvernoteSDK-Mac is not a valid PRODUCT_NAME for use with framework targets enabling DEFINES_MODULE (name is not a valid C99 extended identifier)
warning: no umbrella header found for target 'EvernoteSDK-Mac', module map will not be generated
因为名字中有-
字符,所以只能替换或者去掉;
- 改名时建议直接改target的名字,只改module的名字就会报错:
Warning: PRODUCT_MODULE_NAME may not be overridden for framework target 'EvernoteSDKMac'