skip to Main Content

Trying to get ObjC app project to call Swift function within ObjC static lib……….

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.

The file Hub_lib-Bridging-Header.h has no code.

OBJ-C APP PROJECT……………………………………….

ViewController.mm within the ObjC app project…

#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………..

#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; a wrapper for call to 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];
}

2

Answers


  1. Chosen as BEST ANSWER

    Solution from StackO user: Asperi

    1. Emptied top folder

    2. Created workspace at top folder 1.1 Copied clean app and lib to top folder

    3. Add ObjC lib .xcodeproj to workspace using Xcode > File > Add files ...

    4. Add ObjC app .xcodeproj to workspace

    5. Added dependency of sim_backend_UI to lib via workspace a. app proj > General tab > Frameworks, Libs.. > + b. Select 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. new file > Swift > Some.swift b. Create Bridging Header c. Added to Some.swift ...

    import Foundation

    struct Some{}

    1. Set Xcode active scheme to app and target device
    2. Build "succeeded"

  2. 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. 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

    #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")

    your BLE_central.swift with its protocol method implementation

    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

    //
    //  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.

    Goto "sim backend UI" App Target settings > General > Framework, Libraries, and.. > hit the + button and search for your compiled libHub_lib.a file. > 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

    #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

    #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

    #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. 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

    Link: Could not find or use auto-linked library ‘swiftCoreImage’
    Undefined symbol: value witness table for 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. (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.

    //
    //  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.


    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.
    In Hub_lib project (targeting your "framework") add some files which will keep some random testing c++ code

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

    Rename Hub_lib.m to .mm and change its import rules accordingly to the following ..

    #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++

    -(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). It should work by now and also accept the use of #import <vector>.

    If this works go on and change your Objc-Project-App.
    Switch your compile Scheme to target your Objc-App.
    Change file name BLE.m to BLE.mm (makes it a Objective C++ Source)
    Change file name BLE.h to BLE.hh (makes it a C++ header)
    Change in BLE.mm #import "BLE.h to #import "BLE.hh
    in ViewController.m kick out the line #import "BLE.h" and replace it into ViewController.h instead as #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.

    Edit
    You can find a ready made workspace for Xcode here…
    github: combine cpp swift and objective-c in static lib

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search