GithubHelp home page GithubHelp logo

william-weng / fork-genericprotocolvariable Goto Github PK

View Code? Open in Web Editor NEW

This project forked from ytyubox/genericprotocolvariable

0.0 1.0 0.0 13 KB

Tech blog about Put Generic Protocol as Variable Type. How Combine Publisher put into AnyPublisher

Swift 100.00%

fork-genericprotocolvariable's Introduction

How to Put Generic Protocol as Variable Type

Have you ever put a Protocol on a variable? I believe you do. The Delegate pattern is using a lot in UIKit, and we are get use to it. For example, you can put a UITableViewDelegate in a variable like var delegate: UITableViewDelegate?. Simple right?

However when it come to Generic protocol a.k.a. protocol with associatedtype, put it in a variable become nearly impossible. For example, var publish: Publisher<Int, Never> is an invalid Swift code.

Protocol 'Publisher' can only be used as a generic constraint because it has Self or associated type requirements

Usually we put that under AnyPublisher<Int, Never>, which is :

A publisher that performs type erasure by wrapping another publisher.

AnyPublisher - Apple Developer Documentation

Wait, wait, wait, but HOW to do that? Do we need magic to achieve that? the short answer is we need AbstractClass, Dependency injection


Before start, here is a protocol: Generic

To narrow down this is a Swift topic, not an Apple framework topic, we can start with a hand-make protocol, I call it Generic.

Generic has

  1. An associated type without constraint,
  2. A function that is return the associated type.
// Swift

public protocol Generic {
    associatedtype AnyType
    func getter() -> AnyType
}

First attempt: By Dependency Injection

So I first came up with a idea, if we can put that Generic protocol into a wrapper type, that is a concrete type should be able to put on variable.

// Swift
class AnyGeneric:Generic {
    var wrappedGeneric: Generic 
    
    func getter() -> AnyType {
        return wrappedGeneric.getter()
    }
}

This should work. Really? No! the wrappedGeneric still show as the same error, since we just move code around. But I like it, it tell us that on the Clint side, we don't need to worry about the error, and the Class name is easy to switch (only add prefix Any...).

Second Attempt: Generic by <T> syntax

By the documentation on Swift.org, we can specify the Generic either on the class / struct / enum / actor or on a function. So maybe we change the AnyGeneric with a <GenericType>:

// Swift
class AnyGeneric<GenericType>:Generic where GenericType: Generic {

    typealias AnyType = GenericType.AnyType

    var wrappedGeneric: GenericType

    
    func getter() -> AnyType {
        return wrappedGeneric.getter()
    }
}

And we are done. NOPE, I am sorry. The question is on the client side. Can you tell me how the client side put these code?

// Swift

/** client side */

struct AGeneric<T>: Generic {
    var t:T
    func getter() -> T { t }
}

let sut: AGeneric<Int> = AGeneric(42)
let anyGeneric: AnyGeneric<AGeneric> = AnyGeneric(sut)  

Senior Developer: AnyPublisher<Success,Failure> required the associatedtype, not the who its wrapping. nice try.

Pull request rejected

Third Attempt: Depend on A type, but Store its Subclass

Last attempt is creative, but not enough. I took me a few days to think about how it can be. I felt like I was limited by the knowledge I have. The more I knew, the less I can do. Therefore I tried gather information on the clues.

  1. A generic container syntax can be shared within the class
  2. A subclass can be generic container,even if its super is not.
  3. A generic container subclass can fix in super class variable, even if super class is not a generic container.

So I draw this URL digram. And start working on it.

                ┌────────────────────────┐           
                │  <Generic / AnyType>   │           
                └────────────────────────┘           
                             ▲                       
                             │                       
           ┌─────────────────┴───────────────┐       
           │                                 │       
           │                                 │       
┌────────────────────┐                ┌─────────────┐
│                    │                │             │
│   ABSGenericBox    │◁───────────────│ AnyGeneric  │
│                    │                │             │
└────────────────────┘                └─────────────┘
           ▲                                         
           │                                         
           │                                         
┌────────────────────┐                               
│                    │                               
│GenericBox<Generic> │                               
│                    │                               
└────────────────────┘                               
// swift

class AbstractClass<AnyType>: Generic {
    func getter() -> AnyType {
        abstractFunction()
    }
}


class AnyGenericContainer<GenericType: Generic>: AbstractClass<GenericType.AnyType> {
    private let wrappedValue: GenericType

    init(_ wrappedValue: GenericType) {
        self.wrappedValue = wrappedValue
    }

    override func getter() -> AnyType {
        wrappedValue.getter()
    }
}

Then I replace AnyGeneric's wrappedGeneric to AbstractClass

var wrappedGeneric: AbstractClass<AnyType>

But that is not all, in init, we need to using another generic syntax on function.

init<G:Generic>(wrappedGeneric: G) where G.AnyType == AnyType {
    self.wrappedGeneric = AnyGenericContainer(wrappedGeneric)
}

With extension to make it fluent.

// Swift
extension Generic {
    func eraseToAnyGeneric() -> AnyGeneric<AnyType> {
        AnyGeneric<AnyType>(self)
    }
}

So my AnyGeneric becoming look like this. A class with two <T>, one on class annotation, one on init function.

// Swift
class AnyGeneric<AnyType>:Generic {
    
    private var wrappedGeneric: AbstractClass<AnyType>
    
    init<G:Generic>(wrappedGeneric: G) where G.AnyType == AnyType {
        self.wrappedGeneric = AnyGenericContainer(wrappedGeneric)
    }
    
    func getter() -> AnyType {
        return wrappedGeneric.getter()
    }
}
extension Generic {
   func eraseToAnyGeneric() -> AnyGeneric<AnyType> {
       AnyGeneric<AnyType>(self)
   }
}

So I make another Pull request. Surprisingly, I have a change request, he never do that to me, I must did something...

LGTM, but I think that you can change that into struct, it make more sense.
    ```different
    struct AnyGeneric<AnyType>: Generic {
    ```

final Attempt: change into struct, what's different?

So I start to investigate what is the "sense" in his mind. What if the client using many many, many eraseToAnyGeneric, like:

// Swift

struct AGeneric<T>: Generic {
    var t:T
    func getter() -> T { t }
}
let sut: AnyGeneric<Int> = AGeneric(t: 1)
                            .eraseToAnyGeneric()
                            .eraseToAnyGeneric()
                            ...
                            .eraseToAnyGeneric()
                            .eraseToAnyGeneric()

I know it's crazy, but hey, clients are like a baby we need to take care!

The memory will keep alloc the AnyGeneric again and again, make it struct will to alloc th.... no it still cost memory for those struct! I start to realize that it is a Swift language grammar issue.

  1. In any class init function, a new object must be created.
  2. In any struct init function, self can be a copy of another one

Therefore I made a change request to that change request:

// Swift
struct AnyGeneric<AnyType>:Generic {
    
    private var wrappedGeneric: AbstractClass<AnyType>
    
    init<G:Generic>(wrappedGeneric: G) where G.AnyType == AnyType {
      if let erased = wrappedGeneric as? AnyGeneric<AnyType> {
            self = erased
        } else {
            box = AnyGenericContainer(wrappedGeneric)
        }
    }
    
    func getter() -> AnyType {
        return wrappedGeneric.getter()
    }
}
extension Generic {
   func eraseToAnyGeneric() -> AnyGeneric<AnyType> {
       AnyGeneric<AnyType>(self)
   }
}

This is a work of fiction.

To be honest, I did not came out this idea by my self, I have study a lot of open source code and have I personal opinion on it. Wish you enjoy reading this, and feel free to share, comment.

BTW, there is AnyEquatable is the source code, go digging, go, go, go.

fork-genericprotocolvariable's People

Contributors

ytyubox avatar

Watchers

James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.