Polymorphism with a final class that implements an associatedtype protocol in swift - 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
}
}
}
}

Related

SOLVED - Swift Enum - Casting Nested Enums to String Enum to allow .rawValue

SOLVED
Thank you #New Dev and #Joakim Danielson for your help. I used #Joakim Danielson's answer to improve my code.
I have an extension method to assign accessibilityIdentifiers to views based on a given String Enum. I updated the method to directly accept String Enum Cases as a parameter, thus COMPLETELY eliminating the need for the AccessibilityId enum class as shown below, awesome!
Changes
Before:
.accessibility(identifier: .home(.clickButton))
// Simplified for StackOverflow.
// Imagine 20 more cases..
enum AccessibilityId {
case home(HomeId)
var rawValue: String {
switch self {
case .home(let id):
return id.rawValue
}
}
}
extension View {
func accessibility(identifier: AccessibilityId) -> ModifiedContent<Self, AccessibilityAttachmentModifier> {
self.accessibility(identifier: identifier.rawValue)
}
}
After:
.accessibility(identifier: HomeId.clickButton)
extension View {
func accessibility<T: RawRepresentable>(identifier: T) -> ModifiedContent<Self, AccessibilityAttachmentModifier> where T.RawValue == String {
self.accessibility(identifier: identifier.rawValue)
}
}
---------------------------------------------------------------
Original Question
What I have
enum Item {
case animal(AnimalId)
case vehicle(VehicleId)
case food(FoodId)
var rawValue: String {
switch self {
case .animal(let id):
return id.rawValue
case .vehicle(let id):
return id.rawValue
case .food(let id):
return id.rawValue
}
}
}
enum AnimalId: String {
case cat
case dog
}
// etc.
// Imagine more cases and more enums.
What I want
enum Item {
case animal(AnimalId)
case vehicle(VehicleId)
case food(FoodId)
var rawValue: String {
switch self {
case self as StringEnum:
return id.rawValue
default:
return ""
}
}
}
Usage
func test() {
foo(.animal(.cat))
foo(.vehicle(.plane))
foo(.food(.tacos))
}
func foo(_ item: Item) {
print(item.rawValue)
}
I am happy with the usage, but I'd like to reduce the amount of duplicate cases in the given switch statement. Notice how they all have return id.rawValue. The above is just an example, in reality I have around 30 cases.
My Question
Is there a way for me to catch all Nested String Enums in a single switch or let case to reduce the duplicate code I have to write without losing the intended usage?
Thank you for your efforts, I hope to find an improvement for my code!
Here is a solution that is not based on Item being an enum but instead a generic struct
struct Item<T: RawRepresentable> where T.RawValue == String {
let thing: T
var rawValue: String {
thing.rawValue
}
}
With this solution you don't need to change your other enums.
Example
let item1 = Item(thing: AnimalId.cat)
let item2 = Item(thing: VehicleId.car)
print(item1.rawValue, item2.rawValue)
outputs
cat car
You need something common between all these associated values, like a conformance to a shared protocol, e.g. protocol RawStringValue:
protocol RawStringValue {
var rawValue: String { get }
}
// String enums already conform without any extra implementation
extension AnimalId: RawStringValue {}
extension VehicleId: RawStringValue {}
extension FoodId: RawStringValue {}
Then you could create a switch self inside like so:
var rawValue: String {
switch self {
case .animal (let id as RawStringValue),
.vehicle (let id as RawStringValue),
.food (let id as RawStringValue):
return id.rawValue
}
}
That being said, enum with associated values isn't the most convenient type to work with, so be sure that it's the right choice.

Swift return different data types from function based on case of enumeration given as argument

I am writing TokenManager class which has a JWT token stored in String. It can be decoded from JSON to object of type ClaimsData
struct ClaimsData: Codable {
let user_id: Int
let username: String
let exp: Int
let email: String
let orig_iat: String
}
Now I would love to have one function responsible for giving particular piece of data. But it should only return data if isAuthorised property of TokenManager is true therefor claimsData isn't nil. So I wrote this piece of code:
enum ClaimsOfInt {
case user_id
case exp
}
enum ClaimsOfString {
case username
case email
case orig_iat
}
enum Result<T> {
case success(T)
case notAuthorised
}
func get(_ claim: ClaimsOfInt) -> Result<Int> {
if let claimsData = claimsData {
switch claim {
case .user_id:
return .success(claimsData.user_id)
case .exp:
return .success(claimsData.exp)
}
} else {
return .notAuthorised
}
}
func get(_ claim: ClaimsOfString) -> Result<String> {
if let claimsData = claimsData {
switch claim {
case .username:
return . success(claimsData.username)
case .email:
return . success(claimsData.email)
case .orig_iat:
return . success(claimsData.orig_iat)
}
} else {
return .notAuthorised
}
}
As you can see I have two functions one for each type. I guess it's good enough for now, but I am wondering is it possible to somehow write this as one functions using for example generics? If so how would it look like? Using Any may be one option but as far as my knowledge goes it isn't very swifty.
You cannot have different return types, depending on the value of a function argument.
But you could write a generic function which takes a key path argument:
func get<T>(claimsData: ClaimsData?, key: KeyPath<ClaimsData, T>) -> Result<T> {
if let claimsData = claimsData {
return .success(claimsData[keyPath: key])
} else {
return .notAuthorised
}
}
Example usage:
let result = get(claimsData: claimsData, key: \.user_id)
Here the result type is inferred as Result<Int>.

Swift - refactoring common code to a protocol

I have multiple classes that have code that calls a common network class to make a GET api call. Below is an example of one:
public typealias Api1Result = (Result<Api1Model>) -> Void
private var path = "the/path/api1"
public enum Api1ServiceError: String, Error {
case error = "Sorry, the api1 service returned something different than expected"
}
extension Api1Model {
public static func getApi1(networkClient: NetworkClient = networkClient, completion: #escaping Api1Result) {
networkClient.getPath(path) { result in
switch result {
case .success(let data):
do {
let api1Model = try JSONDecoder().decode(Api1Model.self, from: data)
completion(.success(api1Model))
} catch {
completion(.failure(Api1ServiceError.error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
}
Here is the Result enum if interested:
public enum Result<Value> {
case success(Value)
case failure(Error)
}
There are several other model classes, and the only difference is the actual model class being decoded (Api1Model in this case), as well as the completion typealias (Api1Result). It does the exact same thing across several others, just makes the call to the networkClient.getPath() method, checks for success/failure, and calls the completion closure.
Curious if there are any protocol experts out there who could assist in simplifying this and refactoring so I don't have the same boiler-plate code across multiple classes?
Use a protocol extension (untested)
protocol ApiModel {
associatedtype ApiType : Decodable = Self
static var path : String { get }
static func getApi1(networkClient: NetworkClient, completion: #escaping (Result<ApiType>) -> Void)
}
extension ApiModel where Self : Decodable {
static func getApi1(networkClient: NetworkClient, completion: #escaping (Result<ApiType>) -> Void) {
networkClient.getPath(path) { result in
switch result {
case .success(let data):
do {
let api1Model = try JSONDecoder().decode(ApiType.self, from: data)
completion(.success(api1Model))
} catch {
completion(.failure(Api1ServiceError.error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
}
Make all your classes conform to ApiModel and add the static path property. The type alias is going to be inferred.

Swift 3 generic function ambiguous return

I need a single function to resolve different dependencies in a class.
But there is a compilation error appears.
Is it possible to create that generic function or there are some compiler constraints in Swift?
import Foundation
protocol Client: class {
var description: String { get }
}
final class ImportantPerson : Client {
var description: String {
return "Important person"
}
}
protocol Order: class {
var description: String { get }
}
final class LastOrder : Order {
var description: String {
return "Last order"
}
}
final class A {
fileprivate func resolveDependency<T>() -> T {
return resolve() as T
}
private func resolve() -> Client {
return ImportantPerson()
}
private func resolve() -> Order {
return LastOrder()
}
}
let a = A()
let client: Client = a.resolveDependency()
let order: Order = a.resolveDependency()
print("Client: \(client.description)")
print("Order: \(order.description)")
EDIT: This question is not about if Swift allows to create two functions that differs only by return type. I know it's possible. I think there are some artificial constraints in the compiler but not in the fundamental logic that should allow to infer needed type from a context.
Let's put yourself into the compiler's shoes. Imagine that this was not causing an error and you had one signature with different outputs.
Whenever you call resolveDependency<T>() -> T, the compiler will return you a type T which is an instance conforming to a protocol in your case.
In your code you call this method with different instances conforming to the same protocol. At that stage the compiler has no idea about this. All it knows is that you have passed an instance of T and it needs to give you a result in shape of T
There is no problem until this point. As soon as you execute
return resolve() as! T
The compiler will be confused. I have a T but I don't know which resolve() I will call... All I know is that I have a T. How would I know if this is an Order or a Client ?
In order to prevent such confusions we have compiler-time errors. At least this is the case for Swift. (I don't know how this works in other languages)
You need to define different methods with different signatures and cast your type accordingly to get a similar result
fileprivate func resolveDependency<T>() -> T {
// check if this is an Order
resolveForOrder()
// check if this is a Client
resolveForClient()
}
private func resolveForOrder() -> Order {
return LastOrder()
}
private func resolveForClient() -> Client {
return ImportantPerson()
}
This is like trying to fix a space shuttle engine with a car mechanic. Yes, they both have an engine, they both run on fuel but the mechanic only knows how to fix your car's engine he is not a rocket scientist(!)
This code works fine:
import Foundation
protocol Client: class {
var description: String { get }
}
final class ImportantPerson : Client {
var description: String {
return "Important person"
}
}
protocol Order: class {
var description: String { get }
}
final class LastOrder : Order {
var description: String {
return "Last order"
}
}
final class A {
fileprivate func resolveDependency<T>() -> T {
if T.self == Client.self {
return resolve() as Client as! T
} else {
return resolve() as Order as! T
}
}
private func resolve() -> Client {
return ImportantPerson()
}
private func resolve() -> Order {
return LastOrder()
}
}
let a = A()
let client: Client = a.resolveDependency()
let order: Order = a.resolveDependency()
print("Client: \(client.description)")
print("Order: \(order.description)")
But I believe that compiler should resolve the if else clause himself, it's not so hard as I suppose.
Also there is some bug in the compiler when it tries to match types like that:
switch T.self {
case is Client:
return resolve() as Client as! T
default:
return resolve() as Order as! T
}

How should I handle parameter validation Swift

I am learning Swift. I am designing a class that needs to do parameter validation in it's initializer. How should I handle this if the value passed falls out of range ? I am really finding it difficult to find an appropiate way to design this, considering that:
Swift does not have exceptions, in languages with exceptions and built-in try/catch mechanism, I would have thrown an exception.
Swift does not allow returning nil / null / nothing from the initializer to indicate an error condition, like we can do in Objective-C.
I feel passing an NSErrorPointer to an initializer is cumbersome and places an unneccessary burden on the consumer of the class.
How would you validate a parameter for an initializer in Swift ?
Now with Swift 2.0 you can throw exceptions. For example:
enum Parameter: ErrorType {
case Empty
case Short
}
And then in your functions you can use the super useful guard to check if the received stuff is valid or not and do something like this:
guard parameter.characters.count > 0 else { throw Parameter.Empty }
And then you have to catch those exceptions:
do {
// call your method with try for example something like 'let var = try MyClass("test")'
} catch .Empty {
} catch .Short {
} catch {
print("Something else went wrong!")
}
You can do it using class functions. See below. There are two points to note - the class function has to return Self? not Self, to allow the nil return, and the class must have an #required init().
class Validate {
class func instanceOrNil(valid: Bool) -> Self? {
if valid {
return self()
} else {
return nil
}
}
#required init() {
}
}
let iv = Validate.instanceOrNil(false) // nil
let v = Validate.instanceOrNil(true) // Validate instance
An actual "practical" example might look more like
class NumberLessThanTen {
var mySmallNumber: Int?
class func instanceOrNil(number: Int) -> NumberLessThanTen? {
if number < 10 {
return NumberLessThanTen(number: number)
} else {
return nil
}
}
#required init() {
}
init(number: Int) {
self.mySmallNumber = number
}
}
let iv = NumberLessThanTen.instanceOrNil(17) // nil
let v = NumberLessThanTen.instanceOrNil(5) // valid instance
let n = v!.mySmallNumber // Some 5
One technique: make a Thing.createThing( ... ) class method that wraps the initializer and returns an optional Thing?. Perform the validation inside that method: if the parameters pass validation, call the initializer with them and return the result. If the validation fails, return nil.