Доступ к свойству предварительно объявленного перечисления из Swift

Учитывая, что перечисление, совместимое с ObjC, написано на Swift:

// 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 мог получить доступ к свойству?

Примечание. Мне известно о возможности переписать сообщение в Swift или импортировать App-Bridging-Header.h в Message.h, но здесь это не вариант, я ищу решение, которое будет работать с текущей настройкой.


person Miroslav Kovac    schedule 06.12.2018    source источник


Ответы (2)


Я предполагаю, что одна из причин использования NS_ENUM на стороне Objective-C заключается в том, чтобы время компиляции проверяло, является ли использование оператора 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 не поддерживает изначально объявленные объединения, объединение C, импортированное как структура Swift, по-прежнему ведет себя как объединение 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), мы можем безопасно использовать принудительную развертку.

class MessageTypeReader {

    static func readMessageType(of message: Message) -> MessageType {
        return MessageType(rawValue: message.messageType.swift)!
    }
    
}
person Stephan Schlecht    schedule 09.12.2018
comment
Спасибо за этот обходной путь, меня беспокоит только то, что он изменяет API, поскольку для каждого перечисления должны быть дополнительные .swift и .objc, возможно, мне следовало быть еще более конкретным в вопросе. - person Miroslav Kovac; 13.12.2018

Вот обходной путь, предложенный Кристик (все заслуги принадлежат им):

  • В Message.h объявите messageType как NSInteger :

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

    Apple рекомендует использовать NS_REFINED_FOR_SWIFT , но здесь это не обязательно.

  • В Swift добавьте следующее расширение Message:

    extension Message {
        var messageType: MessageType {
            guard let type = MessageType(rawValue: self.__messageType) else {
                fatalError("Wrong type")
            }
            return type
        }
    }
    
person ielyamani    schedule 09.12.2018
comment
Разве это не сделает messageType неперечислимым в ObjC? - person Miroslav Kovac; 13.12.2018
comment
@MiroslavKovac Ты прав. В настоящее время циклические ссылки не поддерживаются. - person ielyamani; 13.12.2018