Как создать общие протоколы в Swift?

85

Я хотел бы создать протокол с методом, который принимает общий ввод и возвращает общее значение.

Это то, что я пробовал до сих пор, но он вызывает синтаксическую ошибку.

Использование необъявленного идентификатора T.

Что я делаю неправильно?

protocol ApiMapperProtocol {
    func MapFromSource(T) -> U
}

class UserMapper: NSObject, ApiMapperProtocol {
    func MapFromSource(data: NSDictionary) -> UserModel {
        var user = UserModel() as UserModel
        var accountsData:NSArray = data["Accounts"] as NSArray     
        return user
    } 
}
Фархад-Таран
источник
Пожалуйста, проверьте мой ответ: stackoverflow.com/a/54900296/3564632
denis_lor

Ответы:

143

Для протоколов все немного иначе. Посмотрите «Связанные типы» в документации Apple .

Вот как вы используете это в своем примере

protocol ApiMapperProtocol {
    associatedtype T
    associatedtype U
    func MapFromSource(_:T) -> U
}

class UserMapper: NSObject, ApiMapperProtocol {
    typealias T = NSDictionary
    typealias U = UserModel

    func MapFromSource(_ data:NSDictionary) -> UserModel {
        var user = UserModel()
        var accountsData:NSArray = data["Accounts"] as NSArray
        // For Swift 1.2, you need this line instead
        // var accountsData:NSArray = data["Accounts"] as! NSArray
        return user
    }
}
Лу Франко
источник
5
Обратите внимание, что единственная цель ApiMapperProtocol - использовать для общего ограничения. Не похоже, чтобы вы могли написать let x: ApiMapperProtocol = UserMapper ()
Бен
19
Почему Apple настаивает на том, чтобы все было настолько противоречивым?
deusprogrammer
@Ben, как в этом случае добиться let x: ApiMapperProtocol = UserMapper ()?
denis_lor
@denis_lor, если xлокальный, вам не нужно явно указывать его тип, поэтому let x = UserMapper().
Ben Leggiero
2
@BenLeggiero Я только что узнал, что вы можете делать такие вещи, как let x: ApiMapperProtocol = UserMapper (), если используете a в среднем общем классе: stackoverflow.com/a/54900296/3564632
denis_lor
21

Чтобы немного разъяснить ответ Лу Франко , если вы хотите создать метод, который использует конкретный ApiMapperProtocol, вы делаете это следующим образом:

protocol ApiMapperProtocol {
    associatedtype T
    associatedtype U
    func mapFromSource(T) -> U
}

class UserMapper: NSObject, ApiMapperProtocol {
    // these typealiases aren't required, but I'm including them for clarity
    // Normally, you just allow swift to infer them
    typealias T = NSDictionary 
    typealias U = UserModel

    func mapFromSource(data: NSDictionary) -> UserModel {
        var user = UserModel()
        var accountsData: NSArray = data["Accounts"] as NSArray
        // For Swift 1.2, you need this line instead
        // var accountsData: NSArray = data["Accounts"] as! NSArray
        return user
    }
}

class UsesApiMapperProtocol {
    func usesApiMapperProtocol<
        SourceType,
        MappedType,
        ApiMapperProtocolType: ApiMapperProtocol where
          ApiMapperProtocolType.T == SourceType,
          ApiMapperProtocolType.U == MappedType>(
          apiMapperProtocol: ApiMapperProtocolType, 
          source: SourceType) -> MappedType {
        return apiMapperProtocol.mapFromSource(source)
    }
}

UsesApiMapperProtocolтеперь гарантированно принимает только SourceTypes, совместимые с данным ApiMapperProtocol:

let dictionary: NSDictionary = ...
let uses = UsesApiMapperProtocol()
let userModel: UserModel = uses.usesApiMapperProtocol(UserMapper()
    source: dictionary)
Heath Borders
источник
Это очень хорошая статья, за которую проголосовали. Пара глупых вопросов: почему решили использовать as!вместо asSwift 1.2? Во-вторых: не могли бы вы сказать мне, почему нам нужно type aliasснова определить (т.е. typealias T = NSDictionary typealias U = UserModel) в классе, который соответствует протоколу? Заранее спасибо.
Unheilig
Не знаю, почему они перешли с asна as!. Проверить форумы.
Heath Borders
typealias T=NSDictionaryи typealias U=UserModelне требуются. Я обновил пример, чтобы отразить это.
Heath Borders
2
в виде! чтобы указать, что он может выйти из строя. Делает понятнее для разработчика.
user965972
Это внизу ответа.
Пустоши Бордерс
4

Чтобы добиться наличия универсальных шаблонов, а также объявить его таким образом, let userMapper: ApiMapperProtocol = UserMapper()вы должны иметь универсальный класс, соответствующий протоколу, который возвращает универсальный элемент.

protocol ApiMapperProtocol {
    associatedtype I
    associatedType O
    func MapFromSource(data: I) -> O
}

class ApiMapper<I, O>: ApiMapperProtocol {
    func MapFromSource(data: I) -> O {
        fatalError() // Should be always overridden by the class
    }
}

class UserMapper: NSObject, ApiMapper<NSDictionary, UserModel> {
    override func MapFromSource(data: NSDictionary) -> UserModel {
        var user = UserModel() as UserModel
        var accountsData:NSArray = data["Accounts"] as NSArray     
        return user
    } 
}

Теперь вы также можете ссылаться на userMapperто, ApiMapperчто имеет конкретную реализацию дляUserMapper :

let userMapper: ApiMapper = UserMapper()
let userModel: UserModel = userMapper.MapFromSource(data: ...)
denis_lor
источник
1
Какой смысл иметь протокол в этом случае? Он не используется в объявлении userMapper.
alekop 07
-1

КАК СОЗДАТЬ И ИСПОЛЬЗОВАТЬ ОБЩИЙ ПРОТОКОЛ

protocol Generic {

associatedtype T
associatedtype U

func operation(_ t:T)->U

}

// использовать общий протокол

struct Test: Generic {

typealias T = UserModel
typealias U = Any

func operation(_ t: UserModel)->Any {
    let dict = ["name":"saurabh"]
    return dict
    
} 

}

Саураб Шарма
источник
-3

Вы можете использовать методы шаблонов со стиранием типа ...

protocol HeavyDelegate : class {
  func heavy<P, R>(heavy: Heavy<P, R>, shouldReturn: P) -> R
}  

class Heavy<P, R> {
    typealias Param = P
    typealias Return = R
    weak var delegate : HeavyDelegate?  
    func inject(p : P) -> R? {  
        if delegate != nil {
            return delegate?.heavy(self, shouldReturn: p)
        }  
        return nil  
    }
    func callMe(r : Return) {
    }
}
class Delegate : HeavyDelegate {
    typealias H = Heavy<(Int, String), String>

    func heavy<P, R>(heavy: Heavy<P, R>, shouldReturn: P) -> R {
        let h = heavy as! H
        h.callMe("Hello")
        print("Invoked")
        return "Hello" as! R
    }  
}

let heavy = Heavy<(Int, String), String>()
let delegate = Delegate()
heavy.delegate = delegate
heavy.inject((5, "alive"))
Dsjove
источник
2
Этот пост не содержит объяснений. Вы также разместили его как есть на stackoverflow.com/questions/28614990/…
user1427799