How to make Objective-c class using std:vector made available to Swift classes
Swift only supports bridging to Objective-C. You need to move any CPP code / declarations to the .mm file, such as:
Foo.h
#import <Foundation/Foundation.h>
@interface Foo : NSObject
- (void)bar;
@end
Foo.mm
#import "Foo.h"
#import <vector>
@interface Foo() {
std::vector<int> _array;
}
@end
@implementation Foo
- (void)bar {
NSLog(@"in bar");
}
@end
One solution, if you have to consume the C++ classes in other C++ / Objective-C++ code, to create a separate header file for Swift's bridging header, and expose what you need:
Foo.h
#import <Foundation/Foundation.h>
#import <vector>
@interface Foo : NSObject {
std::vector<int>* _bar;
}
@property (atomic, readonly) std::vector<int>* bar;
@property (readonly) size_t size;
- (void)pushInt:(int)val;
- (int)popInt;
@end
Foo+Swift.h
Include this in your bridging header
#import <Foundation/Foundation.h>
#import <stdint.h>
@interface Foo : NSObject
@property (readonly) size_t size;
- (void)pushInt:(int)val;
- (int)popInt;
@end
Foo.mm
#import "Foo.h"
@implementation Foo
@synthesize bar;
- (instancetype)init {
if (self = [super init]) {
_bar = new std::vector<int>();
}
return self;
}
- (void)dealloc {
delete _bar;
}
- (void)pushInt:(int)val {
_bar->push_back(val);
}
- (int)popInt {
if (_bar->size() == 0) {
return -1;
}
auto front = _bar->back();
_bar->pop_back();
return front;
}
- (size_t)size {
return _bar->size();
}
@end
main.swift
#import Foundation
let f = Foo()
f.pushInt(5);
f.pushInt(10);
print("size = \(f.size)")
print("\(f.popInt())")
print("\(f.popInt())")
print("size = \(f.size)")
Most efficient way of getting large std::vector into Swift?
Some parts are not clear enough but I will try to show you some example.
First of all, you need to prepare a class which can access your contourVector
. (I cannot see if it is an instance field or a global variable, if it is an instance field, you may use the existing class.)
Create a header for the prepared class, again you may utilize the existing header, but this header needs to be compiled both in C-context and in C++ context. So, if your existing header contains some declaration which cannot be compiled in C-context, you may need separated two headers or some #if
s.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface YourClass : NSObject
- (NSInteger)contoursSize;
- (NSInteger)contourSizeAtIndex:(NSInteger)index;
- (CGPoint *)contourAtIndex:(NSInteger)index;
//...
@end
NS_ASSUME_NONNULL_END
Then add 3 methods to the class specified in the header:
#import "YourClass.h"
#import <vector>
typedef std::vector<CGPoint> CGContour;
typedef std::vector<CGContour> CGContours;
static CGContours contourVector;
@implementation YourClass
- (NSInteger)contoursSize {
return contourVector.size();
}
- (NSInteger)contourSizeAtIndex:(NSInteger)index {
return contourVector[index].size();
}
- (CGPoint *)contourAtIndex:(NSInteger)index {
return contourVector[index].data();
}
@end
Please do not forget to include the header inside your Project-Bridging-Header.h
:
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "YourClass.h"
You need to create a Swift side wrapper class, as you cannot create UnsafeBufferPointer
in Objective-C.
class YourClassWrapper {
let yourInstance = YourClass()
var count: Int {
return yourInstance.contoursSize()
}
subscript(index: Int) -> UnsafeBufferPointer<CGPoint> {
guard 0..<count ~= index else {fatalError("Index \(index) out of bounds \(0..<count)")}
let start = yourInstance.contour(at: index)
let count = yourInstance.contourSize(at: index)
return UnsafeBufferPointer(start: start, count: count)
}
}
With these preparations above, you can access each CGPoint
as:
let wrapper = YourClassWrapper()
let point = wrapper[0][1]
Or you can get the pointer to the first element in CGContour
as:
let ptr = wrapper[0].baseAddress!
You may need to modify some parts to fit this into your actual code. Hope you can make it.
Convert a vectordlib::point to an NSArray so it can be passed to a Swift class
NSArray
s can only contain Objective-C objects, no C++ objects. So you have to convert your points to NSValue
s first. Something like this should work:
NSMutableArray* facialLandmarksArray = [NSMutableArray arrayWithCapacity: facialLandmarks.size()];
for (auto const& pt: facialLandmarks) {
[facialLandmarksArray addObject: [NSValue valueWithCGPoint:CGPointMake(pt.x, pt.y)]];
}
And make sure this is compiled as Objective-C++ (file ending *.mm).
However, note that this does not scale well for large vectors. Swift and C++ are both able to make sure that the values of such arrays/vectors are stored in consecutive memory, bridging through Objective-C destroys this locality.
Can I mix Swift with C++? Like the Objective-C .mm files
No. When you switch from .m to .mm you are actually switching from Objective-C to a different language (which has many subtle differences) called Objective-C++. So you're not really using C++; you're using Objective-C++ which accepts most C++ as input (in the same way that C++ accepts most but not all C as input). When I say it's not quite C++, consider a C++ file that includes a variable named nil
(which is legal C++) and then try to compile that as Objective-C++.
Swift doesn't have the same relationship. It is not a superset of C or C++, and you can't directly use either in a .swift
file.
"Using Swift with Cocoa and Objective-C" also tells us:
You cannot import C++ code directly into Swift. Instead, create an Objective-C or C wrapper for C++ code.
Objective-Zip method not available to Swift class
Eventually figured it out on my own. The problem was that the method returns an object from another file that I hadn't imported (FileInZipInfo.h
), and for some reason the compiler decided that the best way to deal with that was to pretend that the method didn't exist. Once I imported the other file, everything worked fine.
Can I have Swift, Objective-C, C and C++ files in the same Xcode project?
YES.
You can mix Swift
, C
, C++
, Objective-C
& Objective-C++
files in the same Xcode project.
C
// Declaration: C.h
#ifndef C_h
#define C_h
#ifdef __cplusplus
extern "C" {
#endif
void hello_c(const char * name);
#ifdef __cplusplus
}
#endif
#endif /* C_h */
// Definition: C.c
#include "C.h"
#include <stdio.h>
void hello_c(const char * name) {
printf("Hello %s in C\n", name);
}
C++
// Declaration: CPP.hpp
#pragma once
#include <string>
class CPP {
public:
void hello_cpp(const std::string& name);
};
// Definition: CPP.cpp
#include "CPP.hpp"
#include <iostream>
using namespace std;
void CPP::hello_cpp(const std::string& name) {
cout << "Hello " << name << " in C++" << endl;
}
Objective-C wrapper for C++
// Declaration: CPP-Wrapper.h
#import <Foundation/Foundation.h>
@interface CPP_Wrapper : NSObject
- (void)hello_cpp_wrapped:(NSString *)name;
@end
// Definition: CPP-Wrapper.mm
#import "CPP-Wrapper.h"
#include "CPP.hpp"
@implementation CPP_Wrapper
- (void)hello_cpp_wrapped:(NSString *)name {
CPP cpp;
cpp.hello_cpp([name cStringUsingEncoding:NSUTF8StringEncoding]);
}
@end
Objective-C
// Declaration: Objective-C.h
#import <Foundation/Foundation.h>
@interface Objective_C : NSObject
- (void)hello_objectiveC:(NSString *)name;
@end
// Definition: Objective-C.m
#import "Objective-C.h"
@implementation Objective_C
- (void)hello_objectiveC:(NSString*)name {
printf("Hello %s in Objective-C\n", [name cStringUsingEncoding:NSUTF8StringEncoding]);
}
@end
Objective-C++
// Declaration: Objective-CPP.h
#import <Foundation/Foundation.h>
@interface Objective_CPP : NSObject
- (void)hello_objectiveCpp:(NSString *)name;
@end
// Definition: Objective-CPP.mm
#include <iostream>
#import "Objective-CPP.h"
using namespace std;
@implementation Objective_CPP
- (void)hello_objectiveCpp:(NSString *)name {
cout << "Hello " << [name cStringUsingEncoding:NSUTF8StringEncoding] << " in Objective-C++\n";
}
@end
Swift
// Declaration & definition: Swift.swift
func hello_swift(_ name: String) {
print("Hello \(name) in Swift")
}
Bridging-Header.h
Cannot import CPP.hpp
header file, not because of it's naming convention, but because it contains the class
keyword.
#import "C.h"
#import "CPP-Wrapper.h"
#import "Objective-C.h"
#import "Objective-CPP.h"
Invocation from Swift
// Invoke C
hello_c("World".cStringUsingEncoding(NSUTF8StringEncoding))
// Can't Invoke C++ without a wrapper
// CPP().hello_cpp("World".cStringUsingEncoding(NSUTF8StringEncoding))
// Invoke C++ through Objective-C
CPP_Wrapper().hello_cpp_wrapped("World")
// Invoke Objective-C
Objective_C().hello_objectiveC("World")
// Invoke Objective-C++
Objective_CPP().hello_objectiveCpp("World")
// Invoke Swift
Swift().hello_swift("World")
.h (Headers)
(See item 3 in this Stack Overflow answer)
.h: this is the tricky part, since they are ambiguously used for all flavors of C, ++ or not, Objective or not. When a .h does not contain a single C++ keyword, like class, it can be added to the ...Bridging-Header.h, and will expose whatever function the corresponding .c or .cpp functionalities it declares. Otherwise, that header must be wrapped in either a pure C or Objective-C API.
Output
Hello World in C
Hello World in C++
Hello World in Objective-C
Hello World in Objective-C++
Hello World in Swift
Comments
Cy-4AH:
Yes. You only need wrap C++
into C
or Objective-C
to use in Swift
.
Tommy
Indeed, I have a project that does exactly that. C++
for the thrust of the abstract cross-platform model stuff with some C
parts underneath; Objective-C
to wrap the C++
classes for Swift
purposes, Swift
to bind all that to a subclass of NSDocument
, with some custom views that interrogate the C
stuff.
MaddTheSane
Added the extern "C"
wrapper as per your excellent suggestion. To invoke the C method void hello_c(const char * name)
from C++ method hello_cpp(const std::string& name)
, add #include "C.h"
and call hello_c(name.c_str());
.
Keith Adler
The new SO-32541268: Now with parameters!
► Find this solution on GitHub and additional details on Swift Recipes.
Import headers from c++ library in swift
You don't import anything in your Swift code when Objective-C headers are imported in the bridging header.
All public interfaces available from the imported files get available in the entire Swift module by default after that.
Sample listing
TDWObject.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface TDWObject : NSObject
- (void)someCPPCode;
@end
NS_ASSUME_NONNULL_END
TDWObject.mm
#include <iostream>
#import "TDWObject.h"
@implementation TDWObject
- (void)someCPPCode {
std::cout << "Hello from CPP cout" << std::endl;
}
@end
Some-Bridging-Header.h
#import "TDWObject.h"
main.swift
TDWObject().someCPPCode()
Provided the main.swift file is the entry point of the program, it will print Hello from CPP cout
.
Related Topics
Class Type Non-Type Template Parameter Initialization Does Not Compile
Is There Still a Use for Inline
Operator Overloading Outside Class
Can't Overload Operator<< as Member Function
How to Generate Thread-Safe Uniform Random Numbers
Should a Move Constructor Take a Const or Non-Const Rvalue Reference
Understanding Double Dispatch C++
Complete C++ I18N Gettext() "Hello World" Example
Exception Safety and Make_Unique
How Does Switch Compile in Visual C++ and How Optimized and Fast Is It
Understanding the Example on Lvalue-To-Rvalue Conversion
Building Qt5 with Visual Studio 2012/Visual Studio 2013, and Integrating with the Ide
What Is a Good Oo C++ Wrapper for SQLite
C++ Vector, What Happens Whenever It Expands/Reallocate on Stack
Performance Cost of Passing by Value VS. by Reference or by Pointer
Operator= and Functions That Are Not Inherited in C++