需求中遇到一个问题, 现在有一个用Swift语言编写的framework, 有一个需求需要获取设备的IP地址,这个功能函数是在ifaddrs.h头文件下的,Swift库中并没有提供相应的功能函数,所以需要调用Objective-C或C的代码,我的第一反应是创建一个桥接的头文件来引用这个头文件
// #include <ifaddrs.h> //
然后在Objective-C Bridging Header中输入这个对应的桥接头文件的路径名称,然而结果是悲剧的,Xcode直接来了这个错误
// : error: using bridging headers with framework targets is unsupported //
瞬间头大,难道还要我再把这么简单的功能的模块封装成一个framework供此模块调用。搜索一番找到了解决方案, 下面来通过编写一个示例工程说明解决过程
创建一个New Project, 选择iOS Single View Application,swift语言,命名为CallFramework,在此工程的基础上创建一个Target,选择 Cocoa Touch Framework,命名为FrameworkCallC
在FrameworkCallC中创建一个简单的类Hello, 选择Objective-C语言,编写一个简单的方法hello
- (void)hello { NSLog(@"Objective-C file hello method"); }
我们在创建这个FrameworkCallC的时候会生成一个同名的头文件FrameworkCallC.h也叫做 umbrella header ,在这个头文件中引用刚才创建的头文件Hello
#import <UIKit/UIKit.h> #import "Hello.h" //! Project version number for FrameworkCallC. FOUNDATION_EXPORT double FrameworkCallCVersionNumber; //! Project version string for FrameworkCallC. FOUNDATION_EXPORT const unsigned char FrameworkCallCVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import <FrameworkCallC/PublicHeader.h>
这个时候编译会报以下错误
include of non-modular header inside framework module 'FrameworkCallC' #import "Hello.h" ^ <unknown>:0: error: could not build Objective-C module 'FrameworkCallC'
此时到Build Phases->Headers,将Hello.h拖到Public,再编译通过,
尝试创建一个Swift的Wrapper类用来调用刚才的Hello方法
class Wrapper { init() { let h = Hello() h.hello(); } }
发现编译也是正常的,我们在CallFramework中的ViewController.swift调用这个framework测试下。
let wrapper = Wrapper() wrapper.callHello();
会发现终端输出了Objective-C file hello method 证明是调用成功的
同时因为Hello是public的,我也可以直接
let hello = Hello() hello.hello()
也是成功的,那我这个Hello如果只是想供FrameworkCallC这个内部调用,不想让外部看到怎么办,这个会在第二种方式中说明
回到我最初的目的,我想include < ifaddrs.h>发现使用同样的方法是行不通的,因为我们不能像Hello那样将这个头文件置为public的
下面就要用到另外一种方法 modulemap
在FrameworkCallC创建Library文件夹,在此文件夹下创建world.h world.c文件,同时编写方法
void world() { printf("I am in a c file's world method "); }
创建一个module.map文件内容为
module LibWorld [system] { header "Library/world.h" export * }
同时在Build Setting->Swift Compiler – General下的Import Paths下输入$(SRCROOT)/FrameworkCallC ,确保为non-recursive,也就是module.map的目录,这个时候编译一下,发现并没有报错,在Wrapper中调用这个方法,也是可以正常编译的
import UIKit import LibWorld open class Wrapper { public init() { } open func callHello() { let h = Hello() h.hello(); } open func callCWorld() { LibWorld.world() } }
这个时候在想是不是同样的道理Objective-C代码也可以这样来处理,新建一个类ObjCWorld, 将ObjcWorld.h加入到module.map中,最终的module.map内容如下
module LibWorld [system] { header "Library/world.h" header "Library/ObjcWorld.h" export * }
在wrapper中调用此方法
open func callObjCWorld() { let objcWorld = LibWorld.ObjCWorld() objcWorld.world() }
编译正常,最终我们尝试在CallFramework的ViewController类中调用我们Wrapper中的方法
override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. let wrapper = Wrapper() wrapper.callHello(); wrapper.callCWorld() wrapper.callObjCWorld() let hello = Hello() hello.hello() }
编译正常, 终端日志输出了期望的内容,去生成的framework中看有哪些内容, 如下图:
发现有刚才放入到public中的Hello.h头文件,并没有后加入的world.h和ObjCWorld.h这个方法只在FrameworkCallC中可见,再来看module.modulemap中的内容
framework module FrameworkCallC {
umbrella header “FrameworkCallC.h”
export *
module * { export * }
}
module FrameworkCallC.Swift {
header “FrameworkCallC-Swift.h”
}
/cpp]
看到这里,相信各位也已经明白文中开头提到的需求如何解决了, 本文完. 本文最终的示例工程github地址
参考资料: WRAPPING A C LIBRARY IN A SWIFT FRAMEWORK