【问题标题】:Accessing property of forwardly declared enum from swift从 swift 访问前向声明的枚举的属性
【发布时间】:2019-05-08 00:56:30
【问题描述】:

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

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

还有一个带有 MessageType 类型属性的 ObjC 类,必须提前声明:

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

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

为了在 Swift 代码库的其余部分中使用 Message,将 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 的可能性,但这不是这里的选项,我正在寻找一种可以与当前设置。

【问题讨论】:

    标签: ios objective-c swift objc-bridging-header


    【解决方案1】:

    我猜想在 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)!
        }
        
    }
    

    【讨论】:

    • 感谢这个解决方法,我唯一担心的是它会改变 API,因为每个枚举都必须有额外的 .swift.objc,也许我应该在问题。
    【解决方案2】:

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

    • 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
          }
      }
      

    【讨论】:

    • 这不会让 messageType 在 ObjC 中不可枚举吗?
    • @MiroslavKovac 你是对的。目前不支持循环引用。
    猜你喜欢
    • 1970-01-01
    • 2011-11-20
    • 1970-01-01
    • 1970-01-01
    • 2023-03-27
    • 2013-02-14
    • 1970-01-01
    • 2010-09-09
    相关资源
    最近更新 更多