从 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.
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)!
}
}
假设有一个 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.
这是一个解决方法
在
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.
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)!
}
}