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

Overview

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.

You might also like...
A sample project exploring MVVM pattern with SwiftUI/Combine, using Unsplash API (via Picsum.photos API)
A sample project exploring MVVM pattern with SwiftUI/Combine, using Unsplash API (via Picsum.photos API)

CombineUnsplash A sample project exploring MVVM pattern with SwiftUI/Combine, using Unsplash API (via Picsum.photos API) with detail example. Resource

PowerfulCombine - A simple suite of Combine helpers
PowerfulCombine - A simple suite of Combine helpers

A package with powerful combine extensions. 🤟 Motivation The Combine framework

This repository shows how handle Rest API's in SwiftUI and Combine

SwiftUI-Combine-Networking This repository shows how handle Rest API's in SwiftUI and Combine Endpoints enum includes paths which will be added the en

An Integer type that clamps its value to its minimum and maximum instead of over- or underflowing.

ClampedInteger An Integer type that clamps its value to its minimum and maximum instead of over- or underflowing. Examples let big = ClampedIntege

Compatible backports of commonly used type properties for `URL` that are only available from iOS 16.0+ / macOS 13.0+ / tvOS 16.0+ / watchOS 9.0+.

URLCompatibilityKit URLCompatibilityKit is a lightweight Swift package that adds compatible backports of commonly used type properties, type and insta

Demonstration of how to integrate AppleScript/Cocoa scripting into a Catalyst app
Demonstration of how to integrate AppleScript/Cocoa scripting into a Catalyst app

CatalystAppleScript Trivial demonstration showing how to build support for AppleScript into a Catalyst app. Showcases multiple commands and variables

A simple class that wraps the process of saving or loading a struct or class into a single file

EZFile This is a simple class that wraps the process of saving or loading a stru

Import files into your Swift scripts!
Import files into your Swift scripts!

Swiftmix is a tool aimed to make better use of Swift scripts. Right now there is no decent way to import source files in scripts without using SPM. Sw

Owner
Tsungyu Yu
iOS Developer, Dvorak user. keto eater, and a liver.
Tsungyu Yu
🧲🔗 Open Magnet Links in Put.io

Magnet Links extension Open Magnet Links in Put.io Tired of seeing "Safari cannot open the page because the address is invalid." alerts when you try t

girlfriend.technology 5 Oct 26, 2022
Codepath-Parstagram-Assignment - Parstagram assignment for CodePath iOS Tech Fellow course

Parstagram - Part I This is an Instagram clone with a custom Parse backend that

Yuchong Lee 0 Jan 9, 2022
PunkAPI(BrewDog) 을 이용한 RxSwift-MVVM 예제 (Naver Tech Concert)

BringMyOwnBeer ?? RxSwift를 이용한 MVVM 패턴 예제 Contents About BringMyOwnBeer ?? Concept Contact Me About BringMyOwnBeer ?? 생소한 RxSwift와 MVVM 개념을 보다 쉽게 이해할

Bo-Young PARK 78 Dec 17, 2022
HotCoffee is learning project with MVVM, Generic,Swift 5, TableView

HotCoffee A simple Coffee Ordering app built using TableView, MVVM design pattern and Swift5. Tools Xcode Version 13.2.1 Framework -UIKit Architecture

Ghullam Abbas 2 Aug 26, 2022
daemon DICOMweb store proxying to pacs in c-store protocol

POSTCSTORE versionado versión autor comentario 2021-11-02 Jacques Fauquex versión inicial 2021-11-02 Jacques Fauquex aetLocal en la ruta del servicio.

null 1 Mar 18, 2022
iOS protocol-oriented MVVM examples

MVVM-Example Protocol-Oriented MVVM example apps. Sample projects: MVVM-Example - A Settings screen that has one settings – put your app in Minion Mod

Ivan Magda 52 May 5, 2022
Mini-application iOS native avec Xcode et Swift exploitant l'architecture MVVM et le framework Combine d'Apple pour la mise en place de la programmation réactive fonctionnelle, le tout avec UIKit.

iOS (Swift 5): Test MVVM avec Combine et UIKit L'architecture MVVM et la programmation réactive fonctionnelle sont très utlisées dans le développement

Koussaïla BEN MAMAR 2 Nov 5, 2022
iOS native app demo with Xcode and Swift using MVVM architecture and Apple's Combine framework for functional reactive programming, all with UIKit

iOS (Swift 5): MVVM test with Combine and UIKit MVVM and functional reactive programming (FRP) are very used in iOS development inside companies. Here

Koussaïla BEN MAMAR 2 Dec 31, 2021
A demo demonstrates how to use combine and MVVM in the SwiftUI app

SwiftUI-MVVM-Combine A demo demonstrates how to use combine and MVVM in the Swif

Asa. Ga 7 Jul 5, 2022
MVVM-RXSWIFT-COMBINE- - Demo application populating posts from network request using

MVVM => RXSWIFT + COMBINE Demo application populating posts from network request

Amr Al-khayat 0 Jan 2, 2022