Сборка приложения ObjC получает ошибку сборки для вызова функции swift в статической библиотеке ObjC

#ios #objective-c #swift #xcode #import

#iOS #objective-c #swift #xcode #импорт

Вопрос:

Пытаюсь заставить проект приложения ObjC вызывать функцию Swift в статической библиотеке ObjC……….

Моя сборка проекта приложения ObjC получает ошибку сборки для ссылки на функцию Swift, которая находится в статической библиотеке ObjC (.a), которая импортируется в проект приложения.

Файл Hub_lib-Bridging-Header.у h нет кода.

ПРОЕКТ ПРИЛОЖЕНИЯ OBJ-C……………………………………….

ViewController.mm в рамках проекта приложения ObjC…

 #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 в проекте приложения ObjC………..

 #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 внутри приложения; оболочка для вызова 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];
}
 

Комментарии:

1. чем больше вы кодируете в objc, тем больше вам понравится, когда вы будете следовать синтаксису camelCase и именовать классы с завершающей прописной буквой и значениями / свойствами со строчной буквой. кстати: вы не можете импортировать -Swift.h в свое приложение objc, потому что это относится к внутренней части статической библиотеки. смотрите мой ответ, особенно правила #import .

Ответ №1:

создал тестовый проект, чтобы иметь возможность копировать ваши ошибки. Вы близки, но вам нужно позаботиться о том, чтобы в заголовке вашей статической библиотеки были представлены ее внутренние методы и классы, чтобы вы могли использовать их в другом месте.

давайте начнем с проекта статической библиотеки 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
 

аналог статической библиотеки / реализация 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
 

обратите BLE_Central внимание, что свойство помещается в расширение интерфейса класса, и если вы хотите использовать модуль swift, который возвращается обратно в objc, вам нужно где-то объявить автоматически сгенерированный мост (лучше всего сделать в .m файле #import "Hub_lib-Swift.h" )

ваша 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")
    }   
}
 

Пока дополнительный код из objc не используется в swift внутри статической библиотеки,
Hub_lib-Bridging-Header.h она пуста

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

Далее давайте посмотрим, как импортировать вашу статическую библиотеку в ваше приложение проекта Objective-C «sim backend UI».

Перейдите в настройки целевого приложения «sim backend UI»> Общие> Фреймворк, библиотеки и .. > нажмите кнопку и найдите свой скомпилированный libHub_lib.a файл. > выберите его> нажмите ok.К настоящему времени в вашем списке фреймворков должно быть очень много.

Да, этого недостаточно! Вы должны где-то объявить его заголовок в своем проекте приложения. Где именно, зависит от вас. Вместо того, чтобы реализовывать его несколько раз, мы делаем следующее в 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
 

аналог 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
 

Ваш ViewController.m or .mm использует BLE.h с уже импортированным статическим заголовком lib

 #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
 

Создал testButton, чтобы вы могли видеть, вызывает ли реализация то, что вы ожидаете. Скомпилируйте!

И? Ого! Не работает. Что произошло?

Если ваше приложение представляет собой простой проект Objective-C, оно еще не знает о swift и будет жаловаться странным образом, возможно, с помощью чего-то вроде

Ссылка: не удалось найти или использовать автоматически связанную библиотеку ‘swiftCoreImage’
Неопределенный символ: таблица-свидетель значений для встроенной.Неизвестный объект*

Решение: самый простой способ — создать файл swift в вашем проекте приложения и разрешить Xcode создавать для вас связующий заголовок. (В качестве альтернативы измените настройки проекта, найдите «мост» и перейдите шаг за шагом через свойства) Файл swift не должен иметь специального содержимого.

 //
//  MakeProjectSwiftCompatible.swift
//
import Foundation

 

Скомпилируйте снова. теперь это должно сработать, потому что частично реализованный модуль swift из вашей статической библиотеки может работать должным образом, когда ваш Objc-App-Project способен работать со swift.


Редактировать поскольку вы просили работать с Objective-C / C в вашей статической библиотеке, все немного меняется .. так что вот дополнительный пример кода для доказательства этого. В проекте 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

 // 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;
}
 

Переименуйте 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"
 

давайте изменим метод проверки 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);
}
 

Скомпилируйте Hub_lib (схема). К настоящему времени он должен работать, а также принимать использование #import <vector> .

Если это работает, продолжайте и измените свой Objc-Project-App.
Переключите схему компиляции на таргетинг на ваше Objc-приложение.
Измените имя файла BLE.m на BLE.mm (делает его источником Objective C )
Измените имя файла BLE.h на BLE.hh (делает его заголовком C )
Изменение в BLE.mm #import "BLE.h чтобы #import "BLE.hh
в ViewController.m удалите строку #import "BLE.h" и замените ее на ViewController.h вместо этого как #import "BLE.hh"
(В общем, гораздо проще информировать ваш компилятор о том, какой язык следует ожидать в реализации, когда вы помещаете заголовки импорта в заголовочные файлы.)

Скомпилируйте. Вот и все! Ваша статическая библиотека Objective-C должна правильно работать на этом этапе.

Редактировать Вы можете найти готовое рабочее пространство для Xcode здесь…
github: объединить cpp swift и objective-c в статической библиотеке

Комментарии:

1. Ol Sen: статическая библиотека ObjC на самом деле Hub-lib.mm и не .m, потому что он импортирует библиотеку C .

2. Ol Sen: строка BLE_Central.swift от Hub_lib «class BLE_Central …» выдает следующую ошибку: «Тип ‘BLE_Central’ не соответствует протоколу ‘CBCentralManagerDelegate’. Я разыщу это место.

3. Приветствую, старый сенатор. Если у вас это получилось, не могли бы вы, пожалуйста, отправить почтовый индекс?

4. Вау, объединение C и Swift в одной статической библиотеке слишком сложно. Вместо этого я создам две статические библиотеки: одну для переноса C и одну для переноса Swift BLE

5. Хм. @property BLE_Central в файле .m выдает ошибку сборки «Неизвестное имя типа ‘BLE_Central'».

Ответ №2:

Решение от пользователя StackO: Asperi

  1. Пустая верхняя папка
  2. Создано рабочее пространство в верхней папке 1.1 Скопировано чистое приложение и библиотека в верхнюю папку
  3. Добавьте ObjC lib .xcodeproj в рабочую область с помощью Xcode> File> Add files …
  4. Добавьте ObjC app .xcodeproj в workspace
  5. Добавлена зависимость sim_backend_UI от библиотеки lib через рабочую область a. app proj> вкладка Общие> Фреймворки, библиотеки .. > b. Выберите библиотеку .a. Добавить.
  6. Добавьте немного.swift (любой файл swift, который вы хотите) в sim_backend_UI, просто для того, чтобы Xcode добавил необходимые системные динамические библиотеки swift (которые также понадобятся для swift-части в статической библиотеке)… и подтвердите создание моста в появившемся диалоговом окне a. новый файл> Swift> Some.swift b. Создайте заголовок моста c. Добавлен в Some.swift …

импортировать основу

struct Some{}

  1. Установите активную схему Xcode для приложения и целевого устройства
  2. Сборка «выполнена успешно»