How can I type check a generic then cast to it in swift? - 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
}
}

Related

How to make a constrained extension of Collection generic in Swift?

Context
I have a class Profile which conforms to protocol Entity. I am also adding a search method to all Swift Collections where the Element is a Profile.
However, I would like to make this generic to support not only Profile but every object conforming to Entity.
Code
protocol Entity {
var name: String { get }
}
class Profile: Entity { ... }
extension Collection where Element == Profile {
func searched(with search: String) -> [Profile] {
guard !(search.isEmpty) else { return self }
return self.filter { ($0.name.lowercased().contains(search.lowercased())) }
}
}
Question
How can I make the search method generic to support all objects conforming to Entity?
first, you should change the extension to the below code
extension Collection where Iterator.Element: Entity
next, change the return type of the method to
func searched(with search: String) -> [Iterator.Element]
Finally, the extension can be like this:
extension Collection where Iterator.Element: Entity {
func searched(with search: String) -> [Iterator.Element] {
guard !(search.isEmpty) else { return [Iterator.Element]() }
return self.filter { ($0.name.lowercased().contains(search.lowercased())) }
}
}

Polymorphism with a final class that implements an associatedtype protocol in swift

I'm using Apollo v0.49.0. It's a library for calling graphQL endpoints, and the way it does this is by generating code before you compile your code.
Before I talk about the generated code, I'd like to talk about what the generated code implements. For this question, it's the GraphQLMutation that's relevant. Here's what it looks like:
public enum GraphQLOperationType {
case query
case mutation
case subscription
}
public protocol GraphQLOperation: AnyObject {
var operationType: GraphQLOperationType { get }
var operationDefinition: String { get }
var operationIdentifier: String? { get }
var operationName: String { get }
var queryDocument: String { get }
var variables: GraphQLMap? { get }
associatedtype Data: GraphQLSelectionSet
}
public extension GraphQLOperation {
var queryDocument: String {
return operationDefinition
}
var operationIdentifier: String? {
return nil
}
var variables: GraphQLMap? {
return nil
}
}
public protocol GraphQLQuery: GraphQLOperation {}
public extension GraphQLQuery {
var operationType: GraphQLOperationType { return .query }
}
public protocol GraphQLMutation: GraphQLOperation {}
public extension GraphQLMutation {
var operationType: GraphQLOperationType { return .mutation }
}
This is 80% of the file; the last 20% is irrelevant IMHO. Note how GraphQLMutation implements GraphQLOperation and the latter has an associatedtype.
The library generates classes based on your graphql server endpoints. Here's what they look like:
public final class ConcreteMutation: GraphQLMutation {
...
public struct Data: GraphQLSelectionSet {
...
}
...
}
As far as I know (I'm new to Swift), I have no control over any of the code I've mentioned so far (other than forking the repo and modifying it). I could change them locally, but they would just be overridden every time they were regenerated.
To use any of these generated classes, I have to pass them into this ApolloClient function (also a library class):
#discardableResult
public func perform<Mutation: GraphQLMutation>(mutation: Mutation,
publishResultToStore: Bool = true,
queue: DispatchQueue = .main,
resultHandler: GraphQLResultHandler<Mutation.Data>? = nil) -> Cancellable {
return self.networkTransport.send(
operation: mutation,
cachePolicy: publishResultToStore ? .default : .fetchIgnoringCacheCompletely,
contextIdentifier: nil,
callbackQueue: queue,
completionHandler: { result in
resultHandler?(result)
}
)
}
I can't figure out how to deal with ConcreteMutation in a generic way. I want to be able to write a factory function like so:
extension SomeEnum {
func getMutation<T: GraphQLMutation>() -> T {
switch self {
case .a:
return ConcreteMutation1(first_name: value) as T
case .b:
return ConcreteMutation2(last_name: value) as T
case .c:
return ConcreteMutation3(bio: value) as T
...
}
}
}
The fact that this func is in an enum is irrelevant to me: that same code could be in a struct/class/whatever. What matters is the function signature. I want a factory method that returns a GraphQLMutation that can be passed into ApolloClient.perform()
Because I can't figure out a way to do either of those things, I end up writing a bunch of functions like this instead:
func useConcreteMutation1(value) -> Void {
let mutation = ConcreteMutation1(first_name: value)
apolloClient.perform(mutation: mutation)
}
func useConcreteMutation2(value) -> Void {
let mutation = ConcreteMutation2(last_name: value)
apolloClient.perform(mutation: mutation)
}
...
That's a lot of duplicated code.
Depending on how I fiddle with my getMutation signature -- e.g., <T: GraphQLMutation>() -> T? etc. -- I can get the func to compile, but I get a different compile error when I try to pass it into ApolloClient.perform(). Something saying "protocol can only be used as a generic constraint because it has Self or associated type requirements."
I've researched this a lot, and my research found this article, but I don't think it's an option if the concrete classes implementing the associated type are final?
It's really difficult to figure out if it's possible to use polymorphism in this situation. I can find plenty of articles of what you can do, but no articles on what you can't do. My question is:
How do I write getMutation so it returns a value that can be passed into ApolloClient.perform()?
The fundamental problem you are running into is that this function signature:
func getMutation<T: GraphQLMutation>() -> T
is ambiguous. The reason it's ambiguous is because GraphQLMutation has an associated type (Data) and that information doesn't appear anywhere in your function declaration.
When you do this:
extension SomeEnum {
func getMutation<T: GraphQLMutation>() -> T {
switch self {
case .a:
return ConcreteMutation1(first_name: value) as T
case .b:
return ConcreteMutation2(last_name: value) as T
case .c:
return ConcreteMutation3(bio: value) as T
...
}
}
}
Each of those branches could have a different type. ConcreteMutation1 could have a Data that is Dormouse while ConcreteMutation3 might have a data value that's an IceCreamTruck. You may be able to tell the compiler to ignore that but then you run into problems later because Dormouse and IceCreamTruck are two structs with VERY different sizes and the compiler might need to use different strategies to pass them as parameters.
Apollo.perform is also a template. The compiler is going to write a different function based on that template for each type of mutation you call it with. In order to do that must know the full type signature of the mutation including what its Data associated type is. Should the responseHandler callback be able to handle something the size of a Dormouse, or does it need to be able to handle something the size of an IceCreamTruck?
If the compiler doesn't know, it can't set up the proper calling sequence for the responseHandler. Bad things would happen if you tried to squeeze something the size of an IceCreamTruck through a callback calling sequence that was designed for a parameter the size of a Dormouse!
If the compiler doesn't know what type of Data the mutation has to offer, it can't write a correct version of perform from the template.
If you've only handed it the result of func getMutation<T: GraphQLMutation>() -> T where you've eliminated evidence of what the Data type is, it doesn't know what version of perform it should write.
You are trying to hide the type of Data, but you also want the compiler to create a perform function where the type of Data is known. You can't do both.
Maybe you need to implement AnyGraphQLMutation type erased over the associatedtype.
There are a bunch of resources online for that matter (type erasure), I've found this one pretty exhaustive.
I hope this helps in someway:
class GraphQLQueryHelper
{
static let shared = GraphQLQueryHelper()
class func performGraphQLQuery<T:GraphQLQuery>(query: T, completion:#escaping(GraphQLSelectionSet) -> ())
{
Network.shared.apollo().fetch(query: query, cachePolicy: .default) { (result) in
switch result
{
case .success(let res):
if let data = res.data
{
completion(data)
}
else if let error = res.errors?.first
{
if let dict = error["extensions"] as? NSDictionary
{
switch dict.value(forKey: "code") as? String ?? "" {
case "invalid-jwt": /*Handle Refresh Token Expired*/
default: /*Handle error*/
break
}
}
else
{
/*Handle error*/
}
}
else
{
/*Handle Network error*/
}
break
case .failure(let error):
/*Handle Network error*/
break
}
}
}
class func peroformGraphQLMutation<T:GraphQLMutation>(mutation: T, completion:#escaping(GraphQLSelectionSet) -> ())
{
Network.shared.apollo().perform(mutation: mutation) { (result) in
switch result
{
case .success(let res):
if let data = res.data
{
completion(data)
}
else if let error = res.errors?.first
{
if let dict = error["extensions"] as? NSDictionary
{
switch dict.value(forKey: "code") as? String ?? "" {
case "invalid-jwt": /*Handle Refresh Token Expired*/
default: /*Handle error*/
break
}
}
else
{
/*Handle error*/
}
}
else
{
/*Handle Network error*/
}
break
case .failure(let error):
/*Handle error*/
break
}
}
}
}

Swift 4 Using Generics as Return Value

I have a protocol like so:
protocol ModelProtocol{
func parse<T.Model>(data: Data) -> Array<T>? {}
}
The return is an array of option values. The method takes in data, parses it and returns the array of parsed objects from API data.
The type of data that the API returns is called MyData that has an array as the value of the dictionary.
I parse the JSON like so
func parse<T>(data: Data) -> Array<T>? {
do {
let newJSONDecoder = JSONDecoder()
let menu = try newJSONDecoder.decode(MyData.self, from:data)
let dataArray = menu.data //array
let modelArray = Array<T>()
for object in dataArray {
modelArray.append(object)
}
return modelArray
}
catch {
print("error while parsing:\(error.localizedDescription)")
return nil
}
}
I get error on the line where I append into the array to be returned.
Cannot invoke 'append' with an argument list of type '(MyData.Drinks)'
Ultimately I want the returned array to have objects of the type that is in the array MyData.data- in this case, the type is Drinks. But more broadly, I want the method to return any type that is in any JSON payload. The goal is to create a method that can take in any data and return any object as parsed object of type X in the array.
How do I do this?
First of all the code does not compile:
Protocol methods must not have bodies
so you have to remove the braces:
protocol ModelProtocol{
func parse<T : Decodable>(data: Data) -> Array<T>?
}
To solve your problem create MyData also as generic
struct MyData<T : Decodable> : Decodable {
let data : [T]?
}
and declare parse
func parse<T : Decodable>(data: Data) -> Array<T>? {
do {
let newJSONDecoder = JSONDecoder()
let menu = try newJSONDecoder.decode(MyData<T>.self, from:data)
return menu.data
}
catch {
print("error while parsing: ", error)
return nil
}
}
print always the entire error to get detailed information about the decoding error. localizedDescription is too broad.
If data is expected to be non-optional make parse throw and hand over the decoding error
protocol ModelProtocol{
func parse<T : Decodable>(data: Data) throws -> Array<T>
}
struct MyData<T : Decodable> : Decodable {
let data : [T]
}
func parse<T : Decodable>(data: Data) throws -> Array<T> {
let newJSONDecoder = JSONDecoder()
let menu = try newJSONDecoder.decode(MyData<T>.self, from:data)
return menu.data
}

Using swift protocols with associated types generically

I have a class modelling question with Swift. I have a range of classes that each do the same task (in my example below, Decoding), but they are specialised and each produce a different type of object.
In some cases I will want to be able to talk about my Decoders generally such as getGeneralInfo() or getDecoderForIdentifier(). In other cases, such as where I am doing a decode operation, I will either instantiate the class directly or use as?.
The following code does not work because you can't use Decoder as a return type when it has an associated type.
My solution is to remove decode() from the protocol and have each class just implement its own. I then need to instantiate concrete classes directly where they are needed. This is workable but it makes me sad.
Is there any way I can rejuggle this to have the compiler enforce "all Decoders should have a decode() method according to their associatedtype"?
I have tried using a generic superclass but it requires me to provide a method body for decode(), which is pretty gnarly if your return type isn't optional.
protocol Decoder {
associatedtype Model
func getGeneralInfo() -> GeneralInfo
func decode(sourceData: Data) -> Model
}
// This return type is not allowed because Decoder has an associated type
func getDecoderForIdentifier(id: String) -> Decoder {
if id == "mp3" {
return Mp3Decoder()
}
if id == "wave" {
return WaveDecoder()
}
/* ... */
}
class Mp3Decoder: Decoder {
typealias Model = Mp3Info
func getGeneralInfo() -> GeneralInfo {
let info = GeneralInfo()
/* ... */
return info
}
func decode(sourceData: Data) -> Model {
let result = Mp3Info()
/* ... */
return result
}
}
class WaveDecoder: Decoder {
typealias Model = WaveInfo
/* ... similar to mp3 ... */
}
If you make Model a protocol then you can return Decoder because then it will not need associated types.
protocol Model {
// ...
}
protocol Decoder {
func getGeneralInfo() -> GeneralInfo
func decode(sourceData: Data) -> Model
}
class Mp3Decoder: Decoder {
func getGeneralInfo() -> GeneralInfo {
let info = GeneralInfo()
// ...
return info
}
func decode(sourceData: Data) -> Model {
let result = Mp3Info()
// ...
return result
}
}
func getDecoderForIdentifier(id: String) -> Decoder {
if id == "mp3" {
return Mp3Decoder()
}
// ...
}

Swift nested generics type does not conform to protocol

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