web-dev-qa-db-fra.com

Pourquoi ne peut pas utiliser le protocole `Encodable` comme type dans le func

J'essaie d'obtenir des données par un modèle d'encodage conforme au protocole Encodable. Mais il n'a pas réussi à appeler func encode comme le code ci-dessous:

// MARK: - Demo2

class TestClass2: NSObject, Encodable {
    var x = 1
    var y = 2
}


var dataSource2: Encodable?

dataSource2 = TestClass2()

// error: `Cannot invoke 'encode' with an argument list of type '(Encodable)'`
let _ = try JSONEncoder().encode(dataSource2!)
//func encode<T>(_ value: T) throws -> Data where T : Encodable

Mais dans une autre démo, ça marche bien, pourquoi?

// MARK: - Demo1

protocol TestProtocol {
    func test()
}

class TestClass1: NSObject, TestProtocol {
    func test() {
        print("1")
    }

    var x = 1
    var y = 2
}


var dataSource1: TestProtocol?

dataSource1 = TestClass1()


func logItem(_ value: TestProtocol) {
    value.test()
}

logItem(dataSource1!)
11
AnZ

Solution 1.

Essayez ce code, qui étend codable

extension Encodable {
    func toJSONData() -> Data? {
        return try? JSONEncoder().encode(self)
    }
}

Solution 2.

Pour éviter de polluer les protocoles fournis par Apple avec des extensions

protocol MyEncodable: Encodable {
    func toJSONData() -> Data?
}

extension MyEncodable {
    func toJSONData() -> Data?{
        return try? JSONEncoder().encode(self)
    }
}

tiliser

var dataSource2: Encodable?
dataSource2 = TestClass2()
let data = dataSource2?.toJSONData()
16
SPatel

Vos 2 exemples sont différents.

JSONEncoder (). Encode () attend une classe concrète conforme au procotol Encodable. Le référence dataSource2 contient un protocole et non une classe concrète.

logItem d'autre part, ne prend qu'un protocole en entrée, et AUCUNE classe concrète conforme au protocole. C'est la différence entre vos exemples et pourquoi votre deuxième cas fonctionne et le premier cas ne fonctionne pas.

Avec votre configuration actuelle, cela ne fonctionnera pas. Vous devez passer dans une classe concrète au JSONEncoder.

1
J. Doe

Il existe un certain nombre d'approches pour résoudre ce problème.

La solution @SPatel d'extension Encodable est une possibilité. Cependant, j'essaie personnellement d'éviter de polluer les protocoles fournis par Apple avec des extensions.

Si je lis entre les lignes, il semble que vous souhaitiez passer toute construction conforme à Encodable à une fonction/méthode dans une autre struct/classe.

Prenons un exemple de ce que je pense que vous essayez de réaliser:

struct Transform {
    static func toJson(encodable: Encodable) throws -> Data {
        return try JSONEncoder().encode(encodable)
    }
}

Cependant, Xcode se plaindra:

Protocol type 'Encodable' cannot conform to 'Encodable' because only concrete types can conform to protocols

Une solution Swift-ier consiste à utiliser un générique contraint sur la fonction:

struct Transform {
    static func toJson<EncodableType: Encodable>(encodable: EncodableType) throws -> Data {
        return try JSONEncoder().encode(encodable)
    }
}

Le compilateur peut maintenant déduire le type conforme à Encodable, et nous pouvons appeler la fonction comme prévu:

let dataSource = TestClass2()
let jsonData = try? Transform.toJson(encodable: dataSource)
1
So Over It

Aussi longtemps que TestClass2 est Encodable vous pouvez utiliser le code suivant. encode devrait savoir quoi encoder. Il fait référence aux propriétés de classe pour ce faire. Encodable lui-même ne contient aucune propriété.

JSONEncoder().encode(dataSource2 as! TestClass2)
0
Sedo