Як створити загальні протоколи в 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

Відповіді:


141

Для протоколів справа дещо інша. Подивіться на "асоційовані типи" в документації 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 ()
Бен,

18
Чому Apple наполягає на тому, щоб зробити все таким протилежним інтуїтивним?
deusprogrammer

@Ben, як можна досягти let x: ApiMapperProtocol = UserMapper () у цьому випадку?
denis_lor

@denis_lor, якщо xлокальний, то вам не потрібно чітко вказувати його тип, тому let x = UserMapper().
Бен Леггієро,

2
@BenLeggiero Я тільки що дізнався , що ви можете зробити такі речі , як , нехай х: ApiMapperProtocol = UserMapper () при використанні в середині загального класу: 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тепер гарантовано приймає лише SourceTypeсумісні з даними ApiMapperProtocol:

let dictionary: NSDictionary = ...
let uses = UsesApiMapperProtocol()
let userModel: UserModel = uses.usesApiMapperProtocol(UserMapper()
    source: dictionary)

Це дуже приємне написання, прихильне. Кілька дурних запитань: чому вони вирішили використовувати as!замість просто asв Swift 1.2? По-друге: не могли б ви сказати мені, чому нам потрібно визначити type aliasзнову (тобто typealias T = NSDictionary typealias U = UserModel) у класі, який відповідає протоколу? Заздалегідь спасибі.
Unheilig

Не знаю, чому вони перейшли з asна as!. Перевірте devforums.
Heath Borders

typealias T=NSDictionaryі typealias U=UserModelне потрібні. Я оновив приклад, щоб відобразити це.
Heath Borders

2
як! щоб вказати, що це може не вдатися. Робить це зрозумілішим для розробника.
user965972

Це внизу відповіді.
Heath Borders

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: ...)

Який сенс мати протокол у цьому випадку? Він не використовується в декларації userMapper.
alekop

-1

ЯК СТВОРИТИ І ВИКОРИСТОВУВАТИ ЗАГАЛЬНИЙ ПРОТОКОЛ

протокол загальний {

associatedtype T
associatedtype U

func operation(_ t:T)->U

}

// використовувати загальний протокол

struct Тест: Загальний {

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"))

2
Цей пост не містить пояснень. Ви також розміщені як на stackoverflow.com/questions/28614990 / ...
user1427799
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.