Почему нельзя использовать протокол Encodable в качестве типа в функции?

Я пытаюсь получить данные по модели кодирования, которая соответствует протоколу Encodable. Но не удалось вызвать func encode как код ниже:

// 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

Но в другой демке работает хорошо, почему?

// 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!)

person AnZ    schedule 27.06.2018    source источник
comment
Можно попробовать общую функцию в соответствии с этим ответом   -  person kevin    schedule 21.02.2020


Ответы (5)


Решение 1.

https://github.com/satishVekariya/SVCodable

Попробуйте этот код, который расширяет кодируемые

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

Решение 2.

Чтобы избежать загрязнения протоколов Apple расширениями

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

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

Использовать

var dataSource2: Encodable?
dataSource2 = TestClass2()
let data = dataSource2?.toJSONData()
person SPatel    schedule 27.06.2018
comment
Большой ! Это решает проблему отлично! И что значит self в JSONEncoder().encode(self) ? - person AnZ; 27.06.2018
comment
@Equal, toJSONData - метод, вызываемый экземпляром типа Encodable. Внутри метода экземпляр называется self. - person Eduard; 27.06.2018
comment
Ооо хороший способ, я никогда не думал, что это возможно - person J. Doe; 27.06.2018
comment
Я понимаю. Танки! - person AnZ; 27.06.2018
comment
Encodable не соответствует Encodable (протоколы не всегда соответствуют самим себе), поэтому общий параметр T : Encodable encode(_:) не соответствует принять аргумент Encodable. Обходной путь расширения протокола работает, потому что Swift неявно открывает экзистенциалы при доступе к членам в значениях, типизированных протоколом. См. также форумы . swift.org/t/how-to-encode-objects-of-unknown-type/12253/, чтобы узнать, как создать стирающую оболочку типа AnyEncodable, которую можно передать encode(_:). - person Hamish; 27.06.2018
comment
Вы спасли мой день! - person Natan R.; 16.09.2018
comment
Это решение работает очень хорошо и является идеальным решением для использования переменной Encodable без универсального. В моем случае я использую перечисление case .json(object: Encodable), но получаю сообщение об ошибке при попытке сделать switch case .json(let object) return try? JSONEncoder().encode(object) С этим расширением мой код работает с: switch case .json(let object) return object.jsonData Кстати, я меняю расширение на var jsonData: Data? { return try? JSONEncoder().encode(self) } - person River2202; 02.05.2019

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

func printJSON<T: Encodable>(_ data: T) {
    if let json = try? JSONEncoder().encode(data) {
        if let str = String(data: json, encoding: .utf8) {
            print(str)
        }
    }
}

// Now this should work
var dataSource2 = TestClass2()
printJSON(dataSource2!)
person stevex    schedule 31.10.2019

Существует ряд подходов к решению этой проблемы.

Решение @SPatel по расширению Encodable - одна из возможностей. Однако лично я стараюсь не загрязнять протоколы Apple расширениями.

Если я читаю между строк, кажется, что вы хотите передать любую конструкцию, которая соответствует Encodable, функции/методу в некоторых другая структура/класс.

Давайте рассмотрим пример того, чего, по моему думаю, вы пытаетесь достичь:

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

Однако Xcode будет жаловаться:

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

Более быстрое решение состоит в том, чтобы использовать ограниченную универсальную функцию:

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

Теперь компилятор может определить тип, соответствующий Encodable, и мы можем вызвать функцию, как предполагалось:

let dataSource = TestClass2()
let jsonData = try? Transform.toJson(encodable: dataSource)
person So Over It    schedule 28.06.2019
comment
Это намного чище! Спасибо! - person devjme; 26.05.2021

Ваши 2 примера разные.

JSONEncoder().encode() ожидает конкретный класс, соответствующий протокол Encodable. Ссылка dataSource2 содержит протокол, а не конкретный класс.

logItem, с другой стороны, принимает только протокол в качестве входных данных и НЕТ конкретного класса, который соответствует протоколу. В этом разница между вашими примерами и тем, почему ваш второй случай работает, а первый нет.

С вашими текущими настройками это не сработает. Вам нужно передать конкретный класс в JSONEncoder.

person J. Doe    schedule 27.06.2018
comment
я не совсем понимаю, зачем JSONEncoder().encode() нужен конкретный класс. В объявлении encode() нет особых слов. - person AnZ; 27.06.2018

Пока TestClass2 равно Encodable, вы можете использовать следующий код. encode должен знать, что кодировать. Для этого он ссылается на свойства класса. Encodable сам по себе не содержит никаких свойств.

JSONEncoder().encode(dataSource2 as! TestClass2)
person Eduard    schedule 27.06.2018
comment
Я не могу получить реальный тип TestClass2, это может быть любой другой класс. - person AnZ; 27.06.2018