15

鉴于有一个用 Swift 编写的 ObjC 兼容枚举:

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

和一个 ObjC 类,其类型的属性MessageType必须提前声明:

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

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

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

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

现在,假设有一个 Swift 类试图读取该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能够访问该属性?

注意:我知道将 Message 重写为 Swift 或将 App-Bridging-Header.h 导入 Message.h 的可能性,但这不是一个选项,我正在寻找一种适用于当前设置的解决方案。

4

2 回答 2

12

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

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

Objective-C 头文件

typedef NS_ENUM(NSInteger, MessageType);

union MessageTypeU {
    MessageType objc;
    NSInteger swift;
};


@interface Message : NSObject

@property (nonatomic, readonly) union MessageTypeU messageType;

@end

所以基本思路是:

Swift 将 C 联合作为 Swift 结构导入。尽管 Swift 不支持本地声明的联合,但作为 Swift 结构导入的 C 联合仍然表现得像 C 联合。

...

因为 C 中的联合对其所有字段使用相同的基内存地址,所以由 Swift 导入的联合中的所有计算属性都使用相同的底层内存。因此,更改导入结构实例的属性值会更改该结构定义的所有其他属性的值。

见这里: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)!
    }
    
}
于 2018-12-09T12:49:44.193 回答
6

这是Cristik建议的解决方法(所有功劳归于他们):

  • Message.h中,声明messageTypeNSInteger

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

    AppleNS_REFINED_FOR_SWIFT 推荐使用,但这里没有必要。

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

    extension Message {
        var messageType: MessageType {
            guard let type = MessageType(rawValue: self.__messageType) else {
                fatalError("Wrong type")
            }
            return type
        }
    }
    
于 2018-12-09T10:31:32.180 回答