问题
I'm trying to get data by encode model which conforms to Encodable
protocol. But it's failed to invoke func encode
like code below:
// 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
But in another demo, it works well, why?
// 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!)
回答1:
Solution 1.
Try this code, which extend encodable
extension Encodable {
func toJSONData() -> Data? {
return try? JSONEncoder().encode(self)
}
}
Solution 2.
To avoid polluting Apple-provided protocols with extensions
protocol MyEncodable: Encodable {
func toJSONData() -> Data?
}
extension MyEncodable {
func toJSONData() -> Data?{
return try? JSONEncoder().encode(self)
}
}
Use
var dataSource2: Encodable?
dataSource2 = TestClass2()
let data = dataSource2?.toJSONData()
回答2:
There are a number of approaches to solving this problem.
@SPatel solution of extending Encodable
is one possibility. However, I personally try to avoid polluting Apple-provided protocols with extensions.
If I am reading between the lines, it appears what you are wanting is to pass any construct that conforms to Encodable
to a function/method in some other struct/class.
Let's take an example of what I think you are trying to achieve:
struct Transform {
static func toJson(encodable: Encodable) throws -> Data {
return try JSONEncoder().encode(encodable)
}
}
However, Xcode will complain:
Protocol type 'Encodable' cannot conform to 'Encodable' because only concrete types can conform to protocols
A Swift-ier solution is to use a constrained generic on the function:
struct Transform {
static func toJson<EncodableType: Encodable>(encodable: EncodableType) throws -> Data {
return try JSONEncoder().encode(encodable)
}
}
Now the compiler can infer the type that conforms to Encodable
, and we can call the function as intended:
let dataSource = TestClass2()
let jsonData = try? Transform.toJson(encodable: dataSource)
回答3:
Your 2 examples are different.
JSONEncoder().encode() expects a concrete class which conforms to the procotol Encodable
. The reference dataSource2
holds a protocol and not a concrete class.
logItem
on the other hands, only takes a protocol as input, and NO concrete class which conforms to the protocol. This is the difference between your examples and why your second case is working and the first case does not.
With your current setup, it will not work. You need to pass in a concrete class to the JSONEncoder.
回答4:
As long as TestClass2
is Encodable
you can use following code. encode
should know what to encode. It refers to class properties to do that. Encodable
itself doesn't contain any properties.
JSONEncoder().encode(dataSource2 as! TestClass2)
回答5:
You can't pass a protocol but you can use generics to require a class that conforms to one:
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!)
来源:https://stackoverflow.com/questions/51058292/why-can-not-use-protocol-encodable-as-a-type-in-the-func