从swift访问预先声明的枚举的属性

时间:2018-12-06 10:39:47

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

鉴于有一个用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,但这不是一种选择,我正在寻找一种适用于当前设置。

2 个答案:

答案 0 :(得分:12)

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

如果是这种情况,则可以使用C并集。

Objective-C标头

$fltr  = '(&(objectClass=user)(objectCategory=person))'
$props = 'SamAccountName', 'LastLogon'

Get-ADDomainController -Filter * |
    Select-Object -Expand HostName |
    Where-Object { Test-Connection -Computer $_ -Count 2 -Quiet } |
    ForEach-Object { Get-ADObject -LDAPFilter $fltr -Properties $props -Server $_ } |
    Select-Object Name, SamAccountName,
        @{n='LastLogon';e={[DateTime]::FromFileTime($_.LastLogon)}} |
    Group-Object SamAccountName |
    ForEach-Object { $_.Group | Sort-Object LastLogon | Select-Object -Last 1 } |
    Export-Csv $exportFilePath -NoType

所以基本思想是:

  

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实现示例

typedef NS_ENUM(NSInteger, MessageType);

union MessageTypeU {
    MessageType objc;
    NSInteger swift;
};


@interface Message : NSObject

@property (nonatomic, readonly) union MessageTypeU messageType;

@end

在Swift Side上的使用

由于messageType.swift属性最初来自Swift端(请参阅MessageType的定义),因此我们可以安全地使用强制展开。

@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

答案 1 :(得分:6)

这是C++ standard library的一种解决方法suggested(所有功劳归功于他们):

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

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

    使用Apple的NS_REFINED_FOR_SWIFT Cristik,但此处不必要。

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

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