Swift nested generics type does not conform to protocol - swift

I have a Response class contain a value, and I also have a Value class contain data which conform to Mappable protocol.
Now I have a function to handle Response object, but while I try to get the data out from Value object, it show Type "R" does not conform to protocol.
This is my code in playground:
Update
protocol Mappable{
func doSomething()
}
class User: Mappable {
func doSomething() {
}
}
class Book: Mappable {
func doSomething() {
}
}
class Value<T: Mappable>: Mappable{
var data: T?
func doSomething() {
}
}
class Response<T>{
var value: T?
}
func handleResponse< R: Mappable, T:Value<R>>(response:Response<T>, completeHandler: (R?, NSError?)->()){
completeHandler(response.value!.data, nil) //error goes here: Type "R" does not conform to protocol Mappable
}
let response = Response<Value<User>>()
response.value = Value<User>()
response.value?.data = User()
let response2 = Response<Value<Book>>()
response2.value = Value<Book>()
response2.value?.data = Book()
handleResponse(response, completeHandler:{(r,e)in
print(r)
})
handleResponse(response2, completeHandler:{(r,e)in
print(r)
})
Am I doing it right? Or any other way to achieve this.
Thanks

I'm not really sure why your code doesn't work. I assume it's because Swift is having difficulty inferring the type of a generic within a generic.
I managed to get it to compile by wrapping the response type itself, rather than defining a new generic for Value<R>. For example:
func handleResponse<R: Mappable>(response:Response<Value<R>>, completeHandler: (R?, NSError?)->()){
completeHandler(response.value!.data, nil)
}
I would love to know if anyone else knows exactly why your original code doesn't work though!

Heh, accessing response.value in the handleResponse()-function actually crashes the compiler, thats a bug in the compiler for sure. I rewrote your code, so it compiles:
import Foundation
protocol Mappable {
func doSomething()
}
class User: Mappable {
func doSomething() {
}
}
class Value<T: Mappable>: Mappable {
var data: T?
func doSomething() {
}
}
class Response<T> {
var value: T?
}
func handleResponse< T:Value<User>>(response:Response<T>, completeHandler: (User?, NSError?)->())
{
completeHandler(response.value!.data, nil)
}
let response = Response<Value<User>>()
response.value = Value<User>()
response.value?.data = User()
handleResponse(response, completeHandler:{(r,e) in
print(r)
})

Related

Type specific method is unavailable for a var returned with `some` directive

Consider a factory method pattern implementation:
import UIKit
protocol TransportProtocol: CustomStringConvertible {
func techReview()
}
// Make default implemetation opposed to #objc optional methods and properties
extension TransportProtocol {
func techReview() {
print("Reviewing \(type(of: self))")
}
var description: String {
"This is a \(type(of: self))"
}
}
final class Car: TransportProtocol {
func changeOil() {
print("Changed oil")
}
}
final class Ship: TransportProtocol {
}
protocol LogisticProtocol {
associatedtype Transport: TransportProtocol
func createTransport() -> Transport
func delivery(from A: Any, to B: Any)
func techRewiew(for transport: TransportProtocol)
}
extension LogisticProtocol {
func delivery(from A: Any, to B: Any) {
print("Moving \(type(of: self)) from \(A) to \(B)")
}
func techRewiew(for transport: TransportProtocol) {
transport.techReview()
}
}
final class RoadLogistics: LogisticProtocol {
func createTransport() -> some TransportProtocol {
Car()
}
}
final class SeaLogistics: LogisticProtocol {
func createTransport() -> some TransportProtocol {
Ship()
}
}
// Usage:
class Client {
// ...
static func someClientCode<L: LogisticProtocol>(creator: L) -> some TransportProtocol {
let transport = creator.createTransport()
print("I'm not aware of the creator's type, but it still works.\n"
+ transport.description + "\n")
creator.delivery(from: "Source", to: "Destination")
return transport
}
// ...
}
let someTransport = Client.someClientCode(creator: RoadLogistics())
type(of: someTransport.self) // Car
someTransport.changeOil() // Error: Value of type 'some TransportProtocol' has no member 'changeOil'
The question is why I can't call changeOil() on someTransport if the compiler knows it is a Car not just a TransportProtocol.
Are there any benefits we can get from using some directive, not just bare protocol types?
The return type of the method is some TransportProtocol, not Car. So even though the returned instance is of type Car, you cannot call any methods on it that only exist on Car, but not on TransportProtocol.
The runtime knows that the concrete type of someTransport is Car, but the compiler only knows that the return type conforms to TransportProtocol.
If you want to be able to access Car methods on someTransport, you need to downcast it to Car.
if let car = someTransport as? Car {
car.changeOil()
}
Using the some keyword has type-system benefits when used for opaque returns types (introduced in SE-0244 as part of Swift 5.1), since it actually enables returning a protocol with associated types without having to explicitly make the method generic. This is what drives SwiftUI, since it enables you to return some View.
On the other hand, using opaque return types for protocols without associated types holds no benefits, so in your particular example, there's no reason to use it.

How can I type check a generic then cast to it in swift?

I like the ideas presented in this post, about making database-agnostic, protocol-oriented code.
So say I have a protocol such as:
protocol Database {
func loadObjects<T>(matching query: Query) -> [T]
func loadObject<T>(withID id: String) -> T?
func save<T>(_ object: T)
}
where Query is a struct that has filter and sort specifiers.
Then, given a persistence framework, such as Realm or CoreData, I can just support this protocol, as such:
extension NSManagedObjectContext: Database {
...
}
extension Realm: Database {
...
}
extension MockedDatabase: Database {
...
}
extension UITestingDatabase: Database {
...
}
The issue arises when I would want to use CoreData.
If we look at the method:
func loadObjects<T>(matching query: Query) -> [T]
I have no way to 'cast' T to NSManagedObject.
For example, my desired implementation might be something like:
extension NSManagedObjectContext: Database {
func loadObjects<T>(matching query: Query<T>) -> [T] {
// you need a fetch request for these models. This guard statement compiles. How do we make it work with NSFetchRequestResult however?
guard T.self is NSManagedObject.Type else {
return []
}
// This line below Fails compiling. Type 'T' does not conform to protocol 'NSFetchRequestResult'
var request = NSFetchRequest<T>(entityName: String(describing: T))
// then set sortDescriptors and predicate, etc.
var objects: [T] = []
self.performAndWait {
do {
if let results = try self.fetch(request!) as? [T] {
objects = results
}
} catch let error {
print("Error fetching: \(error.localizedDescription)")
}
}
return objects
}
}
So if I can determine if T's Type is a kind of NSManagedObject, then is it possible to instantiate a NSFetchRequest by 'casting' T to something that will work? It would seem I can't cast or force T to be anything.
Database is a technology-agnostic protocol and therefore shouldn't know anything about Core Data. I'd like to do this in the event I need to change my data persistence framework.
How might I accomplish this in Swift? Would I have to add to the Model protocol to return optionals that would be used for the given frameworks I would support? Or make them support NSFetchRequestResult? I would rather that only the implementation of the protocol need to care about the details of the persistence framework.
Rather than checking the type at runtime constrain the type at compile time, for example
extension NSManagedObjectContext: Database {
func loadObjects<T: NSManagedObject>(matching query: Query<T>) -> [T] {
let request = NSFetchRequest<T>(entityName: String(describing: T.self))
var objects = [T]()
self.performAndWait {
do {
objects = try self.fetch(request) // a generic fetch request returns an array of the generic type or throws an error
} catch let error {
print("Error fetching: \(error.localizedDescription)")
}
}
return objects
}
}
It looks like I can answer my own question. One can further constrain generic types by overloading them, so that the calling code can remain the same, but what gets called is dependent on the type you pass into it.
This code below demonstrates this in a playground:
public protocol Action {
func doSomething<T>(to object: T)
}
public class MyActor {
}
extension MyActor: Action {
// works for any type
public func doSomething<T>(to object: T) {
print("was generic")
}
// but if you constrain the type and your object fits that constraint...
// this code is called (same method signature)
public func doSomething<T: NSObject>(to object: T) {
print("was an object")
}
}
class MyObject: NSObject {
var name: String = "Object"
}
struct MyStruct {
var name: String = "Struct"
}
let actor = MyActor()
let object = MyObject()
let value = MyStruct()
actor.doSomething(to: value) // prints 'was generic'
actor.doSomething(to: object) // prints 'was an object'
So in the original example, I would then support Database for CoreData with:
extension NSManagedObjectContext: Database {
func loadObjects<T>(matching query: Query<T>) -> [T] {
return [] // return an empty array because we only support NSManagedObject types
}
func loadObjects<T: NSManagedObject>(matching query: Query<T>) -> [T] {
let request = NSFetchRequest<T>(entityName: String(describing: T.self))
var objects = [T]()
self.performAndWait {
do {
objects = try self.fetch(request) // a generic fetch request returns an array of the generic type or throws an error
} catch let error {
print("Error fetching: \(error.localizedDescription)")
}
}
return objects
}
}

Why can't we use protocol `Encodable` as a type in the func?

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!)
Solution 1.
Try this code, which extend encodable
extension Encodable {
func toJSONData() -> Data? { 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?{ try? JSONEncoder().encode(self) }
}
Use
var dataSource2: Encodable?
dataSource2 = TestClass2()
let data = dataSource2?.toJSONData()
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!)
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)
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.
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)
Could it be you're just looking for this ?
func blah<T>(_ thing: T) where T: Codable {
do {
let data = try JSONEncoder().encode(thing)
.. do something with data
} catch {
print("severe problem in blah, couldn't encode")
}
}

Return type for all implementations of a Protocol

I have a little Problem. I need to specify a return value for a function that can return every implementation of a Protocol. For Example:
My Protocol:
protocol MyProtocol {
//some functions
}
Implementations:
class ClassA: MyProtocol {
}
class ClassB: MyProtocol {
}
The "problem" function:
func getClassByString(_ name: String) -> MyProtocol.Type {
switch name {
case "a":
return ClassA.self
case "b":
return ClassB.self
default:
return ClassC.self
}
}
//EDIT: This is where i need the result
final class Mapper<N: MyProtocol> {
public func map() -> N?{
if let klass = N.self as? MyProtocol.Type {
return klass as? N
}
return nil
}
}
Usage:
let className = "a" //this String comes from a JSON
let result = getClassByString(className)
let mappingResult = Mapper<result>().map() //undeclared identifier 'result' error
let mappingResult = Mapper<ClassA>().map() //works but i do not know if it is ClassA
The Problem is that result isn't really ClassA.Type, what it should be, it is now MyProtocol.Type and I can't pass this to the next Function.
When I give result the specific value of ClassA.self everything works. I can't cast it to as! ClassA.self because i do not know if ist has to be ClassA or ClassB or Class9000
So the question is. Is there another return type like MyProtocol.Type for the function getClassByString() or a completely different way to get ClassA.Type to result ?
I think that your problem here is not exactly as you describe - the result of your example actually does appear to be a ClassA.Type in Playground, but the problem I suspect is what you do with it next. Your protocol does not say how such types are to be instantiated in a generic fashion, so the type returned cannot be instantiated.
I've made a few changes to your example, and it now works ...
protocol MyProtocol {
//some functions
init() // To instantiate generically, there MUST be an accepted pattern for init()
}
class ClassA: MyProtocol {
required init() {}
}
class ClassB: MyProtocol {
required init() {}
}
class ClassC: MyProtocol {
required init() {}
}
func getClassByString(_ name: String) -> MyProtocol.Type {
switch name {
case "a":
return ClassA.self
case "b":
return ClassB.self
default:
return ClassC.self
}
}
let className = "a" //this String comes from a JSON
let result = getClassByString(className) // ClassA.Type
let a = result.init() // ClassA

Implementing Swift protocol with a constrained type parameter

I have a couple of Swift protocols that describe a general interface that I'm trying to implement in multiple ways:
protocol Identifiable
{
var identifier:String { get }
}
protocol ItemWithReference
{
var resolveReference<T:Identifiable>(callback:(T) -> ())
}
Now I want to implement the ItemWithReference protocol using CloudKit as the back end (this will eventually work with an alternate back-end as well, at which time I expect to provide an alternative implementation of the ItemWithReference protocol.
In my CloudKit implementation, I have something like this:
class CloudKitIdentifiable : Identifiable
{
...
}
class CloudKitItemWithReference : ItemWithReference
{
func resolveReference<T:Identifiable>(callback:(T) -> ())
{
// In this implementation, I want to only proceed if `T` is a CloudKitIdentifiable subtype
// But not sure how to enforce that
}
}
What I would like to do is to constrain T to be a CloudKitIdentifiable rather than just a simple Identifiable. I can't do that directly in the resolveReference declaration because then the function wouldn't conform to the ItemWithReference protocol. So instead, I am hoping to confirm that T is indeed a CloudKitIdentifiable and then invoke it's initializer to create a new instance of the class being resolved.
Is there any way in Swift to use T's metatype T.Type and determine if it is a subtype of another type? Furthermore, is there any way to invoke a required initializer that has been declared on that subtype?
try:
class CloudKitIdentifiable : Identifiable {
var identifier:String = ...
required init() {}
// you need `required`.
}
class CloudKitItemWithReference : ItemWithReference {
func resolveReference<T:Identifiable>(callback:(T) -> ()) {
if T.self is CloudKitIdentifiable.Type {
// do work..
let obj = (T.self as CloudKitIdentifiable.Type)()
callback(obj as T)
}
}
}
OR:
class CloudKitItemWithReference : ItemWithReference {
func resolveReference<T:Identifiable>(callback:(T) -> ()) {
if let CKT = T.self as? CloudKitIdentifiable.Type {
// do work..
let obj = CKT()
callback(obj as T)
}
}
}
But, In this case, you have to call resolveReference like this:
let ref = CloudKitItemWithReference()
ref.resolveReference { (obj: CloudKitIdentifiable) -> () in
// ^^^^^^^^^^^^^^^^^^^^ explicit type is necessary.
println(obj.identifier)
return
}
Rathar than that, I would recommend to use Associated Type:
protocol Identifiable {
var identifier:String { get }
}
protocol ItemWithReference {
typealias Item: Identifiable // <-- HERE is associated type
func resolveReference(callback:(Item) -> ())
}
class CloudKitIdentifiable : Identifiable {
var identifier:String
init(identifier: String) {
self.identifier = identifier
}
}
class CloudKitItemWithReference : ItemWithReference {
// `Item` associated type can be inferred from
// the parameter type of `resolveReference()`
//
// typealias Item = CloudKitIdentifiable
func resolveReference(callback:(CloudKitIdentifiable) -> ()) {
let obj = CloudKitIdentifiable(identifier: "test")
callback(obj)
}
}
let ref = CloudKitItemWithReference()
ref.resolveReference { obj in
println(obj.identifier)
return
}