转载

swift framework工程下调用Objective-C或C语言代码

问题:

需求中遇到一个问题, 现在有一个用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供此模块调用。搜索一番找到了解决方案,  下面来通过编写一个示例工程说明解决过程

1: 创建示例工程

创建一个New Project, 选择iOS Single View Application,swift语言,命名为CallFramework,在此工程的基础上创建一个Target,选择 Cocoa Touch Framework,命名为FrameworkCallC

2: 直接调用Objc:

在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 framework工程下调用Objective-C或C语言代码

尝试创建一个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

3: 调用C代码

在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中看有哪些内容, 如下图:

swift framework工程下调用Objective-C或C语言代码

发现有刚才放入到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

原文  http://www.ethanwhy.com/2016/12/17/swift-framework-call-objective-c-c-language/
正文到此结束
Loading...