Given that there is an ObjC compatible enum written in Swift:
// from MessageType.swift
@objc enum MessageType: Int {
case one
case two
}
and an ObjC class with a property of type MessageType
which has to be forwardly declared:
// from Message.h
typedef NS_ENUM(NSInteger, MessageType);
@interface Message: NSObject
@property (nonatomic, readonly) MessageType messageType;
@end
In order to use the Message
in the rest of the Swift codebase, the Message.h
was added into the bridging header:
// from App-Bridging-Header.h
#import "Message.h"
Now, imagine there is a Swift class that tries to read the messageType
property:
// from MessageTypeReader.swift
class MessageTypeReader {
static func readMessageType(of message: Message) -> MessageType {
return message.messageType
}
}
The compilation would fail with the following error:
Value of type 'Message' has no member 'messageType'
My question would be: Is there a way to forwardly declare a Swift enum in order for the MessageTypeReader
to be able to access the property?
Note: I am aware of the possibility of rewriting the Message into Swift or importing App-Bridging-Header.h into Message.h, but that is not an option here, I am looking for a solution that would work with the current setup.
I guess one reason to use NS_ENUM on Objective-C side is to have compile time checks whether the switch statement usages are exhaustive.
If that's the case one could utilize C unions.
Objective-C Header
typedef NS_ENUM(NSInteger, MessageType);
union MessageTypeU {
MessageType objc;
NSInteger swift;
};
@interface Message : NSObject
@property (nonatomic, readonly) union MessageTypeU messageType;
@end
So the basic idea is:
Swift imports C unions as Swift structures. Although Swift doesn’t support natively declared unions, a C union imported as a Swift structure still behaves like a C union.
...
Because unions in C use the same base memory address for all of their fields, all of the computed properties in a union imported by Swift use the same underlying memory. As a result, changing the value of a property on an instance of the imported structure changes the value of all other properties defined by that structure.
see here: https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/using_imported_c_structs_and_unions_in_swift
Objective-C Implementation Example
@interface Message ()
@property (nonatomic, readwrite) union MessageTypeU messageType;
@end
@implementation Message
- (instancetype)init
{
self = [super init];
if (self) {
_messageType.objc = MessageTypeTwo;
[self testExhaustiveCompilerCheck];
}
return self;
}
- (void)testExhaustiveCompilerCheck {
switch(self.messageType.objc) {
case MessageTypeOne:
NSLog(@"messageType.objc: one");
break;
case MessageTypeTwo:
NSLog(@"messageType.objc: two");
break;
}
}
@end
Usage on Swift Side
Since the messageType.swift property comes originally from the Swift side (see definition of MessageType) we can safely use force-unwrap.
class MessageTypeReader {
static func readMessageType(of message: Message) -> MessageType {
return MessageType(rawValue: message.messageType.swift)!
}
}
Here is a workaround suggested by Cristik (all credit goes to them):
In Message.h
, declare messageType
as NSInteger
:
@interface Message : NSObject
@property (nonatomic, readonly) NSInteger messageType;
@end
Using NS_REFINED_FOR_SWIFT
is recommended by Apple, but not necessary here.
In Swift, add the following Message
extension :
extension Message {
var messageType: MessageType {
guard let type = MessageType(rawValue: self.__messageType) else {
fatalError("Wrong type")
}
return type
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With