从 swift 访问前向声明的枚举的 属性

Accessing property of forwardly declared enum from swift

假设有一个 ObjC 兼容的枚举写在 Swift:

// from MessageType.swift
@objc enum MessageType: Int {
    case one
    case two
}

和一个 ObjC class 具有类型 MessageType 的 属性 必须预先声明:

// from Message.h
typedef NS_ENUM(NSInteger, MessageType);

@interface Message: NSObject
@property (nonatomic, readonly) MessageType messageType;
@end

为了在 Swift 代码库的其余部分使用 Message,将 Message.h 添加到桥接 header:

// from App-Bridging-Header.h
#import "Message.h"

现在,假设有一个 Swift class 试图读取 messageType 属性:

// from MessageTypeReader.swift
class MessageTypeReader {
    static func readMessageType(of message: Message) -> MessageType {
        return message.messageType
    }
}

编译将失败并出现以下错误:

Value of type 'Message' has no member 'messageType'

我的问题是:有没有办法预先声明一个 Swift 枚举,以便 MessageTypeReader 能够访问 属性?

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.

这是一个解决方法 by Cristik(所有功劳都归功于他们):

  • Message.h 中,将 messageType 声明为 NSInteger :

    @interface Message : NSObject
    @property (nonatomic, readonly) NSInteger messageType;
    @end
    

    Apple 使用 NS_REFINED_FOR_SWIFT is recommended,但此处不需要。

  • 在 Swift 中,添加以下 Message 扩展名:

    extension Message {
        var messageType: MessageType {
            guard let type = MessageType(rawValue: self.__messageType) else {
                fatalError("Wrong type")
            }
            return type
        }
    }
    

我想在 Objective-C 端使用 NS_ENUM 的一个原因是让编译时检查 switch 语句的用法是否详尽无遗。

如果是这种情况,可以利用 C 联合。

Objective-C Header

typedef NS_ENUM(NSInteger, MessageType);

union MessageTypeU {
    MessageType objc;
    NSInteger swift;
};


@interface Message : NSObject

@property (nonatomic, readonly) union MessageTypeU messageType;

@end

所以基本思路是:

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.

看这里:https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/using_imported_c_structs_and_unions_in_swift

Objective-C 实现示例

@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

Swift 侧的用法

由于 messageType.swift 属性 最初来自 Swift 方(请参阅 MessageType 的定义)我们可以安全地使用 force-unwrap.

class MessageTypeReader {

    static func readMessageType(of message: Message) -> MessageType {
        return MessageType(rawValue: message.messageType.swift)!
    }
    
}