Swift: Сделайте так, чтобы два типа с одинаковой «формой» соответствовали общему протоколу

#swift #swift-protocols

#быстрый #swift-протоколы

Вопрос:

У меня есть два разных типа, которые представляют одни и те же данные и имеют точно такую же «форму». Два разных типа являются кодовыми, и я вынужден иметь с ними дело. Но я хочу, чтобы они соответствовали общему протоколу, чтобы я мог одинаково относиться к обоим типам. Вот пример:

Допустим, это мои два типа, созданные с помощью кода, с которыми я застрял:

 struct User1 {
    var email: String
    var name: Name
    
    struct Name {
        var givenName: String
        var familyName: String
    }
}

struct User2 {
    var email: String
    var name: Name
    
    struct Name {
        var givenName: String
        var familyName: String
    }
}
 

Я хочу иметь возможность использовать эти типы взаимозаменяемо, поэтому я создаю пару протоколов, которым они могут соответствовать:

 protocol NameRepresenting {
    var givenName: String { get }
    var familyName: String { get }
}

protocol UserRepresenting {
    var email: String { get }
    var name: NameRepresenting { get }
}
 

А затем я пытаюсь заставить их соответствовать:

 extension User1.Name: NameRepresenting {}
// Error: Type 'User1' does not conform to protocol 'UserRepresenting'
extension User1: UserRepresenting {}

extension User2.Name: NameRepresenting {}
// Error: Type 'User2' does not conform to protocol 'UserRepresenting'
extension User2: UserRepresenting {}
 

Я ожидаю, что вышеизложенное сработает, но компиляция завершается с ошибкой, описанной выше. Есть ли какой-нибудь элегантный способ привести эти два типа в соответствие с общим протоколом, чтобы я мог использовать их взаимозаменяемо?

Ответ №1:

name Свойства сгенерированных структур имеют тип Name , а не NameRepresenting как того требует протокол. Ковариантные возвраты пока не поддерживаются в Swift: (

Что вы можете сделать, так это добавить соответствующее требование к типу:

 protocol UserRepresenting {
    associatedtype Name : NameRepresenting
    var email: String { get }
    var name: Name { get }
}
 

Для этого требуется, чтобы конформеры имели тип, который соответствует NameRepresenting и является типом name свойства.

Однако теперь, когда у него есть соответствующее требование к типу, вы не можете использовать UserRepresenting его в качестве типа параметра переменной / функции. Вы можете использовать его только в общих ограничениях. Итак, если у вас есть функция, которая принимает a UserRepresenting , вам нужно написать ее следующим образом:

 func someFunction<UserType: UserRepresenting>(user: UserType) {

}
 

и если одному из ваших классов / структур необходимо сохранить свойство типа UserRepresenting , вам также необходимо сделать свой класс / структуру универсальным:

 class Foo<UserType: UserRepresenting> {
    var someUser: UserType?
}
 

Это может работать или не работать в вашей ситуации. Если это не так, вы можете написать средство удаления типов:

 struct AnyUserRepresenting : UserRepresenting {
    var email: String
    var name: Name
    struct Name : NameRepresenting {
        var givenName: String
        var familyName: String
    }

    init<UserType: UserRepresenting>(_ userRepresenting: UserType) {
        self.name = Name(
            givenName: userRepresenting.name.givenName,
            familyName: userRepresenting.name.familyName)
        self.email = userRepresenting.email
    }
}
 

Теперь вы можете преобразовать любой UserRepresenting в этот AnyUserRepresenting и работать с AnyUserRepresenting ним вместо этого.