简体   繁体   English

ObjC 应用程序构建在 ObjC 静态库中调用 swift 函数时出现构建错误

[英]ObjC app build gets build error for call to swift function within ObjC static library

Trying to get ObjC app project to call Swift function within ObjC static lib..........试图让 ObjC 应用程序项目在 ObjC 静态库中调用 Swift 函数.......

My ObjC app project build gets build error for reference to a Swift function that is within an ObjC static lib (.a) that is imported into the app project.我的 ObjC 应用项目构建获取构建错误以引用导入应用项目的 ObjC 静态库 (.a) 中的 Swift 函数。

The file Hub_lib-Bridging-Header.h has no code.文件 Hub_lib-Bridging-Header.h 没有代码。

OBJ-C APP PROJECT.............................................. OBJ-C APP 项目............................................................ .

ViewController.mm within the ObjC app project... ObjC 应用程序项目中的 ViewController.mm...

#import "ViewController.h"
#import "Hub_lib.h"
#import "Hub_lib-Swift.h"

#import "hublib.hpp"

@interface ViewController ()
@end

@implementation ViewController
. . .
- (IBAction)run_simple_central:(id)sender {
    [self   BLE.start_central];
}

BLE.h within ObjC app project........... ObjC 应用程序项目中的 BLE.h ......

#import <CoreBluetooth/CoreBluetooth.h>
#import "Hub_lib-Swift.h"


@interface BLE: NSObject
//< CBPeripheralManagerDelegate >
    @property(strong, nonatomic) CBPeripheralManager* peripheralManager;
    @property(strong, nonatomic) CBMutableCharacteristic* transferCharacteristic;
    @property(strong, nonatomic) NSData* dataToSend;
    @property(nonatomic, readwrite) NSInteger sendDataIndex;
    -(void)start_central;

@end /* BLE_h */

BLE.m within app;应用内的BLE.m; a wrapper for call to swift..........................用于调用 swift 的包装器 .....................

#import "BLE.h"
#import "Hub_lib-Swift.h"

@interface BLE ()
@end

@implementation BLE

-(void)start_central
{
        Hub_lib* BLE_central = [Hub_lib new];

    [BLE_central    centralManager.run_central];
}

made a test project to be able to replicate your errors.制作了一个测试项目,以便能够复制您的错误。 You are close but you need to take care of how your static lib has its internal methods and classes exposed in its header so you can use them elsewhere.您很接近,但您需要注意静态库如何在其标头中公开其内部方法和类,以便您可以在其他地方使用它们。

let's begin with the Objective-C Static Library project.让我们从 Objective-C 静态库项目开始。 Hub_lib.h

#import <Foundation/Foundation.h>

// we want the lib header as clean as possible
// it will then be imported in your project with '#import <Hub_lib/Hub_lib.h>'

// auto-generated swift -> objc bridging header imported here will
// mess it up when imported somewhere else.
// so when using swift->objc bridging place next line in .m file instead
//#import "Hub_lib-Swift.h"

//@class BLE_Central; // pre-declaration of a later fully declared Class.
// as we moved the property into .m file we dont need it here.

@interface Hub_lib : NSObject

// to make this work you would need pre-declaration of BLE_Central, see above interface
//@property BLE_Central *ble; // placed in .m interface extension instead.

-(void)run_central;

@end

static lib counterpart / implementation Hub_lib.m静态库对应物/实现Hub_lib.m

#import "Hub_lib.h"
#import "Hub_lib-Swift.h"

@interface Hub_lib ()
@property BLE_Central *ble_central;
@end;

@implementation Hub_lib

-(instancetype)init {
    if (!(self=[super init])) return nil;
    _ble_central = [[BLE_Central alloc] init];
    return self;
}
-(void)run_central {
    [_ble_central run_central];
}

@end

notice BLE_Central property is placed in the class interface extension and when you want to use swift module stuff that exposes back to objc you need to declare the auto-generated bridge somewhere (best done in .m file #import "Hub_lib-Swift.h" )注意BLE_Central属性放置在类接口扩展中,当您想使用暴露回 objc 的 swift 模块内容时,您需要在某处声明自动生成的桥(最好在.m文件中完成#import "Hub_lib-Swift.h" )

your BLE_central.swift with its protocol method implementation您的BLE_central.swift及其协议方法实现

import Foundation

import UIKit
import CoreBluetooth
import os

var centralManager: CBCentralManager = CBCentralManager()

class BLE_Central: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
    
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        os_log("centralManagerDidUpdateState")
    }
    
    var discoveredPeripheral: CBPeripheral?
    var transferCharacteristic: CBCharacteristic?
    var writeIterationsComplete = 0
    var connectionIterationsComplete = 0

    let defaultIterations = 5  // change this value based on test usecase

    var data = Data()

    // as you figured out before we need to expose to @objc
    @objc public func run_central()
    {
        os_log("run_central")
        // out-commented for testing purposes
        //mobile_sys_hub_lib.centralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionShowPowerAlertKey: true])
        os_log("Scanning started")
    }   
}

As long no extra code from objc is used in swift inside the static lib, the Hub_lib-Bridging-Header.h is empty只要在静态库中的 swift 中没有使用来自 objc 的额外代码, Hub_lib-Bridging-Header.h就是空的

//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//

Next let's see how to import your static lib in your "sim backend UI" Objective-C Project app.接下来让我们看看如何在“sim 后端 UI”Objective-C 项目应用程序中导入静态库。
Goto "sim backend UI" App Target settings > General > Framework, Libraries, and.. > hit the + button and search for your compiled libHub_lib.a file.转到“sim backend UI” App Target settings > General > Framework, Libraries, and.. > 点击+按钮并搜索你编译的libHub_lib.a文件。 > select it > hit ok. > 选择它 > 点击确定。 Should be very much in your framework list by now.现在应该在你的框架列表中。

Yes, thats not enough!是的,这还不够! You have to declare its header in your app project somewhere.您必须在您的应用程序项目中的某处声明其标头。 Where exactly is up to you.具体在哪里取决于您。 Instead of implementing it multiple times we do the following in BLE.h我们在BLE.h执行以下操作,而不是多次实现它

#import <Foundation/Foundation.h>

#import <CoreBluetooth/CoreBluetooth.h>
//#import "Hub_lib-Swift.h" // no no no no
#import <Hub_lib/Hub_lib.h> // much better. test-compile once if it is crying

NS_ASSUME_NONNULL_BEGIN

@interface BLE: NSObject
// <CBPeripheralManagerDelegate> // "//<Protocol>" will confuse doxygen

@property (strong, nonatomic) CBPeripheralManager* peripheralManager;
@property (strong, nonatomic) CBMutableCharacteristic* transferCharacteristic;
@property (strong, nonatomic) NSData* dataToSend;
@property (nonatomic, readwrite) NSInteger sendDataIndex;

-(void)start_central;

@end /* BLE_h */

NS_ASSUME_NONNULL_END

counterpart BLE.m对应的BLE.m

#import "BLE.h"

@implementation BLE

-(void)start_central
{    
    NSLog(@"invoked BLE start_central");
    Hub_lib *Hub_central = [Hub_lib new];
    [Hub_central run_central];
}

@end

Your ViewController.m or .mm makes use of BLE.h with its already imported static lib header您的ViewController.m.mm使用BLE.h及其已导入的静态库标头

#import "ViewController.h"

#import "BLE.h"

@interface ViewController ()
@property (nonatomic) BLE *ble;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
    btn.frame = CGRectMake(100, 100, 200, 50);
    [btn setTitle:@"run simple central" forState:(UIControlStateNormal)];
    [btn setTitleColor:UIColor.greenColor forState:(UIControlStateNormal)];
    btn.layer.backgroundColor = UIColor.orangeColor.CGColor;
    btn.layer.cornerRadius = 5.0f;
    [self.view addSubview:btn];
    
    [btn addTarget:self action:@selector(run_simple_central:) forControlEvents:(UIControlEventTouchUpInside)];
}

- (IBAction)run_simple_central:(id)sender {

    // BLE needs to be allocated to take effect.
    if (!_ble) _ble = [[BLE alloc] init];
    
    // testing.. should log "run_central" + "Scanning started"
    [self.ble start_central];
}

@end

Made a testButton so you can see if the implementation invokes what you expect.制作了一个 testButton 以便您可以查看实现是否调用了您所期望的。 Compile!编译!

And?和? Aoutsch!哎呀! Does not work.不起作用。 What happened?发生了什么?

If your App is a plain Objective-C project it doesn't know about swift yet and will complain in a weirdo way, possibly via something like如果你的应用程序是一个普通的 Objective-C 项目,它还不知道 swift 并且会以一种奇怪的方式抱怨,可能是通过类似的方式

Link: Could not find or use auto-linked library 'swiftCoreImage'链接:无法找到或使用自动链接库“swiftCoreImage”
Undefined symbol: value witness table for Builtin.UnknownObject*未定义符号:Builtin.UnknownObject* 的值见证表

Solution: Most easy way is to create a swift file in your app project and allow Xcode to make a bridging header for you.解决方案:最简单的方法是在您的应用程序项目中创建一个 swift 文件,并允许 Xcode 为您制作桥接头。 (Alternatively change project settings, search for "bridge" and go step by step thru the properties) The swift file does not have to have special content. (或者更改项目设置,搜索“bridge”并逐步浏览属性) swift 文件不必具有特殊内容。

//
//  MakeProjectSwiftCompatible.swift
//
import Foundation

Compile again.再次编译。 now it should work because the partly implemented swift module from within your static lib can properly work when your Objc-App-Project is able to work with swift stuff.现在它应该可以工作了,因为当您的 Objc-App-Project 能够处理 swift 的东西时,静态库中部分实现的 swift 模块可以正常工作。


Edit as you asked to work with Objective-C++/C++ in your static library things change a little bit.. so here some additional example code to proof it.当你要求在你的静态库中使用 Objective-C++/C++ 时进行编辑,事情发生了一些变化......所以这里有一些额外的示例代码来证明它。 In Hub_lib project (targeting your "framework") add some files which will keep some random testing c++ code在 Hub_lib 项目(针对您的“框架”)中添加一些文件,这些文件将保留一些随机测试 C++ 代码

//HubCPP.h
#import <Foundation/Foundation.h>

#include <vector>

NS_ASSUME_NONNULL_BEGIN
class SomeCPPClass
{
public:
    SomeCPPClass(id<NSObject> obj, size_t size);
    ~SomeCPPClass();
    id<NSObject>                    getBuffer() { return buffers[bufferIdx]; }
    unsigned int                    getCurrentIdx();
private:
    std::vector <id <NSObject>>     buffers;
    unsigned int                    bufferIdx;
    bool                            isReady;
};
NS_ASSUME_NONNULL_END

To make Xcode know that you work with C++ you need to have implementation file ending with .mm and also need to change HubCpp.h " Identity and Type (right Xcode panel while file selected)" to C++ Header要让 Xcode 知道您使用 C++,您需要有以.mm结尾的实现文件,并且还需要将HubCpp.hIdentity and Type (选择文件时右侧 Xcode 面板)”更改为C++ Header

// HubCpp.mm
#import "HubCPP.h"

SomeCPPClass::SomeCPPClass (id<NSObject> obj, size_t size) :
bufferIdx (0),
isReady(false)
{
    uint8_t ringSize = 255;
    assert (ringSize > 0);
    for (uint8_t i = 0; i < ringSize; i++)
    {
        //buffers.push_back ();
        bufferIdx = (unsigned int)size;
    }
}
SomeCPPClass::~SomeCPPClass() {
    // cleanup allocated stuff here.
}

unsigned int SomeCPPClass::getCurrentIdx() {
    return bufferIdx;
}

Rename Hub_lib.m to .mm and change its import rules accordingly to the following ..Hub_lib.m重命名为.mm并将其导入规则相应地更改为以下 ..

#import "Hub_lib.h"
#import <CoreBluetooth/CoreBluetooth.h> //needed because the -Swift.h bridge will cry in the next line
#import "Hub_lib-Swift.h"
#import "HubCPP.h"

lets change the proofing method in Hub_lib.mm so it really uses C++让我们更改Hub_lib.mm的校对方法,使其真正使用 C++

-(void)run_central {
    [_ble_central run_central];
    SomeCPPClass *cpp = new SomeCPPClass(@"justSomeNSStringObject",2);
    unsigned int idx = cpp->getCurrentIdx();
    NSLog(@"objectiveCplusplus testIdx = %u", idx);
}

Compile Hub_lib (scheme).编译 Hub_lib(方案)。 It should work by now and also accept the use of #import <vector> .它现在应该可以工作并且还接受使用#import <vector>

If this works go on and change your Objc-Project-App.如果这有效,请更改您的 Objc-Project-App。
Switch your compile Scheme to target your Objc-App.切换您的编译方案以定位您的 Objc-App。
Change file name BLE.m to BLE.mm (makes it a Objective C++ Source)将文件名BLE.m更改为BLE.mm (使其成为目标 C++ 源)
Change file name BLE.h to BLE.hh (makes it a C++ header)将文件名BLE.h更改为BLE.hh (使其成为 C++ 头文件)
Change in BLE.mm #import "BLE.h to #import "BLE.hh将 BLE.mm #import "BLE.h更改为#import "BLE.hh
in ViewController.m kick out the line #import "BLE.h" and replace it into ViewController.h instead as #import "BLE.hh"ViewController.m踢出#import "BLE.h"并将其替换为ViewController.h而不是#import "BLE.hh"
(In general its much easier to keep your compiler informed what language to expect in implementation when you place import headers in header files.) (通常,当您将导入头文件放在头文件中时,让您的编译器知道在实现中期望使用哪种语言要容易得多。)

Compile.编译。 Thats it!就是这样! Your Objective-C++ static lib should properly work at this point.此时您的 Objective-C++ 静态库应该可以正常工作。

Edit You can find a ready made workspace for Xcode here...编辑您可以在此处找到 Xcode 的现成工作区...
github: combine cpp swift and objective-c in static lib github:在静态库中结合 cpp swift 和 Objective-c

Solution from StackO user: Asperi来自 StackO 用户的解决方案:Asperi

  1. Emptied top folder清空顶层文件夹

  2. Created workspace at top folder 1.1 Copied clean app and lib to top folder在顶部文件夹 1.1 创建工作区将干净的应用程序和库复制到顶部文件夹

  3. Add ObjC lib .xcodeproj to workspace using Xcode > File > Add files ...使用 Xcode > File > Add files ... 将 ObjC lib .xcodeproj 添加到工作区

  4. Add ObjC app .xcodeproj to workspace将 ObjC 应用程序 .xcodeproj 添加到工作区

  5. Added dependency of sim_backend_UI to lib via workspace a.通过工作区 a 添加了 sim_backend_UI 对 lib 的依赖。 app proj > General tab > Frameworks, Libs.. > + b. app proj > 常规选项卡 > 框架、库.. > + b. Select lib .a c.选择 lib .a c。 Add.添加。

  6. Add Some.swift (any swift file you want) to sim_backend_UI, just for the purpose Xcode add required system swift dynamic libraries (which will be needed for swift part in static library as well)... and confirm creating bridge in appeared dialog a.将 Some.swift(您想要的任何 swift 文件)添加到 sim_backend_UI,只是为了 Xcode 添加所需的系统 swift 动态库(静态库中的 swift 部分也需要这些库)...并确认在出现的对话框中创建桥. new file > Swift > Some.swift b.新文件 > Swift > Some.swift b. Create Bridging Header c.创建桥接头 c. Added to Some.swift ...添加到 Some.swift ...

import Foundation进口基金会

struct Some{}结构一些{}

  1. Set Xcode active scheme to app and target device将 Xcode 活动方案设置为应用程序和目标设备
  2. Build "succeeded"构建“成功”

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM