I have a function written in Swift that I want to call from Rust. I've tried exposing it through Objective-C, however I continue to get errors from ld saying it can't find _foo. The Rust project is linked to the Swift project by compiling the Rust project as a staticlib.
foo.h
#pragma once
#include <stdint.h>
uint8_t foo_bridge(uint8_t);
foo.m
#import <Foundation/Foundation.h>
#import <Foo-Swift.h>
uint8_t foo_bridge(uint8_t byte) {
return foo(byte);
}
bar.swift
public func foo(byte: UInt8) -> UInt8 {
return byte * 2
}
src/lib.rs
extern "C" {
pub fn foo_bridge(byte: u8) -> u8;
}
Bar-Bridging-Header.h
#import "foo.h"
Cargo.toml
[package]
name = "foo"
version = "0.1.0"
[lib]
name = "foo"
crate-type = ["staticlib"]
The issue here is attempting to call a bare Swift function from Objective-C, which isn't supported.
If you check the Foo-Swift.h header you'll find that the foo() function isn't present, which indicates that the symbol for it, _foo, won't be available at runtime either.
The solution is to put the foo() function inside something that can be called from Objective-C, like a class deriving from NSObject:
class Foo: NSObject {
class func foo(byte: UInt8) -> UInt8 {
return byte * 2
}
}
Once you do that, you'll find the Foo-Swift.h header now has an interface for it:
#interface Foo : NSObject
+ (uint8_t)fooWithByte:(uint8_t)byte;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
#end
Since we know it's available from Objective-C now, we can call it like this:
uint8_t foo_bridge(uint8_t byte) {
return [Foo fooWithByte:byte];
}
Related
I have the following Objective-C class which uses an Objective-C enum:
MyClass.h:
typedef NS_ENUM(NSInteger, MyEnum) {
MyEnumCase1
};
#interface MyClass : NSObject
-(void)method:(MyEnum)param;
#end
MyClass.m:
#implementation MyClass
-(void)method:(MyEnum)param {}
#end
I can subclass MyClass in Swift and override the method like this:
SubClass.swift
class SubClass: MyClass {
override func method(_ param: MyEnum) {}
}
But if I define the enum in Swift instead of in Objective-C, overriding the method fails:
MyClass.h:
// Forward declare the enum in Objective-C
typedef NS_ENUM(NSInteger, MyEnum);
#interface MyClass : NSObject
-(void)method:(MyEnum)param;
#end
SubClass.swift:
#objc enum MyEnum: NSInteger {
case case1
}
class SubClass: MyClass {
override func method(_ param: MyEnum) {} // Error
}
In this case, overriding the method fails with the error
Method does not override any method from its superclass
The enum itself works in Swift, I can add the following method to SubClass, and it compiles:
func useEnum() {
let x = MyEnum.case1
}
Why does the overriding fail?
When I opened the Generated Interface of MyClass.h, Xcode showed something like this:
import Foundation
open class MyClass : NSObject {
}
Nothing more but comments.
Seems Swift cannot import incomplete enums, so methods which use such types are not imported neither.
So, your #objc enum MyEnum just declares a new enum type, and override func method(_ param: MyEnum) is an attempt to override a method which does not exist in its superclass, from the Swift side.
The enum itself works in Swift
Of course. The enum works even if you removed the line of typedef (with all lines using it) from MyClass.h .
The enum works even if you specify some different type than NSInteger:
#objc enum MyEnum: UInt8 {
case case1
}
Seems you cannot write an actual definition of an enum in Swift, which is declared as an incomplete enum in Objective-C.
I have an enum defined in Objective C and used as a datatype in a Swift object.
Now I'm using that swift object in Objective C and I'd like to access the property.
However the property isn't added to the "xxxx-Swift.h" file in the class. I know it's possible to use Swift enums in Obj C with #objc prepending it but I don't have much control over this enum since it's auto generated from existing Objective C code.
I'm not sure exactly what your use case is, but here is a quick example of how an enum defined in Objective-C can be used in a Swift class, which can in turn be used in Objective-C.
Here is the Objective-C header (oclib.h):
#ifndef oclib_h
#define oclib_h
#import <Foundation/Foundation.h>
typedef enum {A, B, C} oc_enum_t;
void useSwiftClassInC();
#endif /* oclib_h */
Here is the corresponding Objective-C .m file (oclib.m):
#import "oclib.h"
#import "swift_c_1-Swift.h" // Need this to have access to Swift types; the swift_c_1 part will be your product module name.
void useSwiftClassInC() {
UseEnum *ue = [[UseEnum alloc] initWithE:B i:444];
printf("In Objective-C useSwiftClassInC(): m_Enum = %d, m_Int = %d\n", ue.m_Enum, ue.m_Int);
ue.m_Enum = A;
ue.m_Int = 10;
[UseEnum printUseEnum: ue];
}
And here is the Swift file:
// Swift class that uses an enum from Objective-C
class UseEnum : NSObject // NSObject inheritance is important!
{
var m_Enum : oc_enum_t
var m_Int : Int32
init(e : oc_enum_t, i : Int32)
{
m_Enum = e
m_Int = i
}
static func printUseEnum( x : UseEnum )
{
print("In Swift UseEnum::printUserEnum: m_Enum = \(x.m_Enum), m_Int = \(x.m_Int)")
}
}
// This calls an Objective-C function that does the following:
// - creates a UseEnum object
// - prints it out from Objective-C
// - modifies it
// - calls printUseEnum(), implemented in Swift, to print the object
useSwiftClassInC()
The bridging header just has
#import "oclib.h"
You have probably seen this documentation already, but if not, here it is: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html
Please provide more details about your specific situation if this doesn't answer your question. Here is the output I get from the example:
In Objective-C useSwiftClassInC(): m_Enum = 1, m_Int = 444
In Swift UseEnum::printUserEnum: m_Enum = oc_enum_t(rawValue: 0), m_Int = 10
An interesting situation arises if a data type defined in Swift is used in the signature of an Objective-C function that needs to be called from Swift. Xcode won't let us import the *-Swift.h into an Objective-C header or into a bridging header. A solution is to use #class forward declaration of the Swift type. Thus, if in the above example we needed an Objective-C function that takes UseEnum as a parameter, we could have something like this in oclib.h:
...
#class UseEnum; // Forward declaration must come before UseEnum is used!
...
void useSwiftClassInC_1(UseEnum * useEnum);
...
I am developing a command line app which uses obj-c and c files together. In my obj-c file (say x.m), I use a struct which uses an interface and the interface uses a struct. This is easily handled in C++ with forward declarations but I need obj-c in my app.
I was wondering if someone can please shed some light on what I doing wrong.
Appreciate any help and thanks in advance.
typedef struct mystruct_s
{
...
....
} mystruct;
struct abc ;
#interface abcDelegate:NSObject {
#public
struct abc *abc;
}
#end
struct abc
{
mystruct b
abcDelegate *abcdelegate;
};
I get the following error error:
expected specifier-qualifier-list before ‘mystruct’
You've forgot ;
should be
struct abc
{
mystruct b;
abcDelegate *abcdelegate;
};
If using .m file you must use c-style structs. such as
typedef struct mystruct_
{
...
} mystruct;
or
struct abc
{
struct mystruct b;
abcDelegate *abcdelegate;
};
If you want structs just like in c++ change your file extension to .mm to support c++
Almost there. Change the code to look like this:
struct abc
{
mystruct b;
abcDelegate *abcdelegate;
};
#interface abcDelegate:NSObject {
#public
struct abc *abc;
}
#end
I've read many things about enum types in objective-c, and I see there is many ways to define them. But I don't see the right way (if there is one) to define an enum that can be called with CARS.ROLLSROYCE and that cannot be used only with ROLLSROYCE in the code.
So I can define ROLLSROYCE in the CARS enum and also in the BEAUTIFULCARS enum.
Do you know the way to define such an enum ?
You are trying to implement namespaces for your Enums in Objective-C. What you're asking for is a lot of elbow grease in Objective-C. You are probably best-off using C++ for this, since it is easy and afaik fully supported in any iOS or Cocoa application. You'll have to rename the files that #import your C++ code to .mm files instead of .m files, and the C++ compiler can be trickier than the Objective-C one. Going this route you'll create a header file like Enums.h.
// Enums.h
namespace CARS
{
enum CARS
{
ROLLSROYCE
};
}
namespace BEAUTIFULCARS
{
enum BEAUTIFULCARS
{
ROLLSROYCE = 45
};
}
And in your .mm sourcefile
#import "Enums.h"
-(void)printEnumvals
{
NSLog(#"CARS %d BEAUTIFULCARS %d",
CARS::ROLLSROYCE,
BEAUTIFULCARS::ROLLSROYCE);
}
If you want to avoid using C++ for this solution, there's a lot more elbow grease, bookkeeping, and opportunity for error. You'll need a header and a source file for this.
// CARS.h
#interface BEAUTIFULCARS : NSObject
{
enum
{
BEAUTIFULCARS_ROLLSROYCE = 45
} BEAUTIFULCARS;
}
#end
#interface CARS : NSObject
{
enum
{
CARS_ROLLSROYCE
} CARS;
}
#end
// CARS.m
#implementation BEAUTIFULCARS
+(NSInteger)ROLLSROYCE{ return BEAUTIFULCARS_ROLLSROYCE; }
#end
#implementation CARS
+(NSInteger)ROLLSROYCE{ return CARS_ROLLSROYCE; }
#end
Your .m source is almost the same:
#import "CARS.h"
-(void)printEnumvals
{
NSLog(#"CARS %d BEAUTIFULCARS %d",
CARS.ROLLSROYCE,
BEAUTIFULCARS.ROLLSROYCE);
}
Objective-C does not manage scope in the same way that most other OO languages do. Interfaces define the properties and messages that an object that interface supports, but don't support protection levels like public or private. When you define an enum in an #interface, that enum ends up in global scope.
For my case, I didn't want to use C++ namespaces or write redundant Objective-C classes for such a simple case, so I fallen back to the C.
// Enum.h
typedef struct
{
const int ROLLSROYCE;
} _CARS;
typedef struct
{
const int ROLLSROYCE;
} _BEAUTIFULCARS;
extern const _CARS CARS;
extern const _BEAUTIFULCARS BEAUTIFULCARS;
And then in Enum.m, define values
// Enum.m
#import "Enum.h"
const _CARS CARS = {0};// 0 is to be assigned to ROLLSROYCE field in struct
const _BEAUTIFULCARS BEAUTIFULCARS = {1}; // same but with 1
And finally, in your "main" code
#import "Enum.h"
// Some method
{
NSLog(#"I can refer to CARS.ROLLSROYCE = %d and BEAUTIFULCARS.ROLLSROYCE = %d", CARS.ROLLSROYCE, BEAUTIFULCARS.ROLLSROYCE);
}
Which will produce this output:
I can refer to CARS.ROLLSROYCE = 0 and BEAUTIFULCARS.ROLLSROYCE = 1
How do you initialise a constant in a header file?
For example:
#interface MyClass : NSObject {
const int foo;
}
#implementation MyClass
-(id)init:{?????;}
For "public" constants, you declare it as extern in your header file (.h) and initialize it in your implementation file (.m).
// File.h
extern int const foo;
then
// File.m
int const foo = 42;
Consider using enum if it's not just one, but multiple constants belonging together
Objective C classes do not support constants as members. You can't create a constant the way you want.
The closest way to declare a constant associated with a class is to define a class method that returns it. You can also use extern to access constants directly. Both are demonstrated below:
// header
extern const int MY_CONSTANT;
#interface Foo
{
}
+(int) fooConstant;
#end
// implementation
const int MY_CONSTANT = 23;
static const int FOO_CONST = 34;
#implementation Foo
+(int) fooConstant
{
return FOO_CONST; // You could also return 34 directly with no static constant
}
#end
An advantage of the class method version is that it can be extended to provide constant objects quite easily. You can use extern objects, nut you have to initialise them in an initialize method (unless they are strings). So you will often see the following pattern:
// header
#interface Foo
{
}
+(Foo*) fooConstant;
#end
// implementation
#implementation Foo
+(Foo*) fooConstant
{
static Foo* theConstant = nil;
if (theConstant == nil)
{
theConstant = [[Foo alloc] initWithStuff];
}
return theConstant;
}
#end
A simple way for value type constants like integers is to use the enum hack as hinted by unbeli.
// File.h
enum {
SKFoo = 1,
SKBar = 42,
};
One advantage to this over using extern is that it's all resolved at compile time so there no memory is needed to hold the variables.
Another method is to use static const which is what was to replace the enum hack in C/C++.
// File.h
static const int SKFoo = 1;
static const int SKBar = 42;
A quick scan through Apple's headers shows that the enum hack method appears to be the preferred way of doing this in Objective-C and I actually find it cleaner and use it myself.
Also, if you are creating groups of options you should consider using NS_ENUM to create a typesafe constants.
// File.h
typedef NS_ENUM(NSInteger, SKContants) {
SKFoo = 1,
SKBar = 42,
};
More info on NS_ENUM and it's cousin NS_OPTIONS is available at NSHipster.