Swift Protocol Constraint Required - Advanced

Please help me with this, I need to put a protocol requirement constraint somewhere in my code but I don’t know where

Business case: A generic filter manager class that can manage a list of filters

A filter is defined by a protocol, the filter specification should have the field type (string, int, date …) and the array type, the filter class implementing the protocol will be responsible to extract the distinct value from that array

protocol SimpleFilter {
    associatedtype ArrayType: Collection
    associatedtype ValueType: Comparable

    var values: [ValueType] { get set }

    func extractValues(from array: ArrayType)
}

let’s define an object type for the array we would like to filter as an example

struct City {
    let code: String
    let country: String
    let region: String
}

An array of cities could be filtered by country & region, we will define 2 filter fields

class CountryFilter: SimpleFilter {
    var values = [String]()

    func extractValues(from array: [City]) {
        // remove duplicates
    }
}

class RegionFilter: SimpleFilter {
    var values = [String]()

    func extractValues(from array: [City]) {
        // remove duplicates
    }
}

with Swift 5.7 we can now use Any to store these filters in the same array

let filters: [any SimpleFilter] = [CountryFilter(), RegionFilter()]

Now we need to build a filter manager, this filter manager will accept a generic array to be filtered and the filter fields, a protocol requirement is required on the Array Type I guess, this is where I need help ...

in the initializer, I would like to unbox SimpleFilter and pass it the generic array but it does not work

class FiltersManager<T: Collection> {
    private var originalArray: T
    private var filteredArray: T
    private(set) var filters: [any SimpleFilter]

    public init(array: T, filters: [any SimpleFilter]) {
        self.originalArray = array
        self.filteredArray = array
        self.filters = filters

        for filter in filters {
            //filter.extractValues(from: array) <— Missing a constraint here
        }
    }
}

I tried something like this but it does not work either

class TestFiltersManager<T: Collection, F: SimpleFilter> where F.ArrayType == T {

thanks for help

alex

Don't use any as you have no use case for it.

class FiltersManager<T: Collection, U: SimpleFilter> where T == U.ArrayType {
    private var originalArray: T
    private var filteredArray: T
    private(set) var filters: [U]

    public init(array: T, filters: [U]) {
        self.originalArray = array
        self.filteredArray = array
        self.filters = filters
        for filter in filters {
            filter.extractValues(from: array)
        }
    }
}

Or if you really need to use any then just apply it to the filter property.

class FiltersManager<T: Collection, U: SimpleFilter> where T == U.ArrayType {
    private var originalArray: T
    private var filteredArray: T
    private(set) var filters: [any SimpleFilter]

    public init(array: T, filters: [U]) {

        self.originalArray = array
        self.filteredArray = array
        self.filters = filters

        for filter in filters {
            filter.extractValues(from: array)
        }
    }
}

This is what I had to do in the end. Each filter had to be cast to its concrete type to call or access any property or method.

protocol SimpleFilter {

    associatedtype ArrayType: Collection

    associatedtype ValueType: Comparable



    var values: [ValueType] { get set }



    func extractValues(from array: ArrayType)

}



extension SimpleFilter {

    func extractValues(from array: [City]) {

        print(array.count, values.count)

    }

}

struct City {

    let code: String

    let country: String

    let region: String

}



struct CountryFilter: SimpleFilter {

    var values: [String]

    

    typealias ArrayType = [City]

    typealias ValueType = String

}



struct RegionFilter: SimpleFilter {

    var values: [String]

    

    typealias ArrayType = [City]

    typealias ValueType = String

}



typealias anySimpleFilter = any SimpleFilter



class FiltersManager<T: Collection> {

    

    private var originalArray: T

    private var filteredArray: T

    private(set) var filters: [anySimpleFilter]

    

    public init(array: T, filters: [anySimpleFilter]) {

        self.originalArray = array

        self.filteredArray = array

        self.filters = filters

        

        for filter in filters {

            if let filter = filter as? CountryFilter {

                filter.extractValues(from: array as! [City])

            } else if let filter = filter as? RegionFilter {

                filter.extractValues(from: array as! [City])

            }

        }

        

    }

}



let filters = [RegionFilter(values: []), CountryFilter(values: [])] as [any SimpleFilter]

let cities = [City(code: "0", country: "CA", region: "NA")]

let manager = FiltersManager(array: cities, filters: filters)

A much simpler approach:

protocol ProtocolThing {

    associatedtype Item

    var values: Item { get set }

    func things() -> Item

}



struct Thing1: ProtocolThing {

    typealias Item = String

    var values: Item

    func things() -> Item {

        values

    }

}



struct Thing2: ProtocolThing {

    typealias Item = String

    var values: Item

    func things() -> Item {

        values

    }

}



struct Doctor {

    var things: [any ProtocolThing]

    func thingAMeJig() -> String {

        let thing = things[Int.random(in: 0..<things.count)]

        return "\(thing.values) - \(thing.things())"

    }

}



let exam = Doctor(things: [Thing1(values: "Hat"), Thing2(values: "Cat")])

print(exam.thingAMeJig())

here is my implementation (before swift 5.7) and I would like to do it with the new Any / Unboxing feature of Swift 5.7

the goal is to have a generic class FilterManager that accepts a generic array to be filtered and a list of filters, inside the init we loop on the filter fields and pass the array, each filter is then responsible to extract the values (remove duplicates)

thanks

public protocol ManagedFilter: AnyObject {
    associatedtype ArrayType

    typealias Condition = ((ArrayType) -> Bool)

    var name: String { get }

    var condition: Condition? { get }

    func extractValues(from array: [ArrayType])
}

public protocol FilterSpecification: ManagedFilter {
    associatedtype ValueType: Comparable

    var values: [ValueType] { get set }
}

// MARK: - Abstract Base
private class _AnyFilterBase<ArrayType>: ManagedFilter {
    init() {
        guard type(of: self) != _AnyFilterBase.self else {
            fatalError("_AnyFilterBase<ArrayType> instances can not be created; create a subclass instance instead")
        }
    }

    var name: String { fatalError("Must override") }

    var condition: Condition? {
        fatalError("Must override")
    }

    func extractValues(from array: [ArrayType]) {
        fatalError("Must override")
    }
}

// MARK: - Private Box
fileprivate class _AnyFilterBox<Base: ManagedFilter>: _AnyFilterBase<Base.ArrayType> {
    var base: Base

    init(_ base: Base) {
        self.base = base
    }

    fileprivate override var name: String { base.name }

    fileprivate override var condition: Condition? {
        base.condition
    }

    fileprivate override func extractValues(from array: [Base.ArrayType]) {
        base.extractValues(from: array)
    }
}

// MARK: - Public Wrapper
final public class AnyFilter<ArrayType>: ManagedFilter {
    private let box: _AnyFilterBase<ArrayType>

    public init<Base: ManagedFilter>(_ base: Base) where Base.ArrayType == ArrayType {
        box = _AnyFilterBox(base)
    }

    public var name: String { box.name }

    public var condition: ((ArrayType) -> Bool)? {
        box.condition
    }

    public func extractValues(from array: [ArrayType]) {
        box.extractValues(from: array)
    }
}

public class FiltersManager1<T> {
    private var originalArray: [T]
    private var filteredArray: [T]
    private(set) var filters: [AnyFilter<T>]

    public init(array: [T], filters: [AnyFilter<T>]) {
        self.originalArray = array
        self.filteredArray = array
        self.filters = filters

        for filter in filters {
            filter.extractValues(from: array)
        }
    }
}

struct City1 {
    let code: String
    let country: String
    let region: String
}

class CountryFilter1: FilterSpecification {
    var name = "Country"
    var values = [String]()

    var condition: ((City1) -> Bool)? {
        guard !values.isEmpty else { return nil }

        return { city in
            self.values.contains(city.country)
        }
    }

    func extractValues(from array: [City1]) {
        // remove duplicates
    }
}

class RegionFilter1: FilterSpecification {
    var name = "Region"
    var values = [String]()

    var condition: ((City1) -> Bool)? {
        guard !values.isEmpty else { return nil }

        return { city in
            self.values.contains(city.region)
        }
    }

    func extractValues(from array: [City1]) {
        // remove duplicates
    }
}

let cities = [City1]()

let manager = FiltersManager1(array: cities, filters: [AnyFilter(CountryFilter1()), AnyFilter(RegionFilter1())])

Here is my version minus the manual abstraction allowing the any keyword to do what it was meant to do. We define a protocol for the data model, City, allowing it to participate in the compiler's existential abstraction. Runs without issue.

import Cocoa

public protocol ManagedFilter: AnyObject {
    typealias Condition = ((any DataModel) -> Bool)
    var name: String { get }
    var condition: Condition? { get }
    func extractValues(from array: [any DataModel])
}

public protocol FilterSpecification: ManagedFilter {
    associatedtype ValueType: Comparable
    var values: [ValueType] { get set }
}

public protocol DataModel {
    var code: String { get }
    var country: String { get }
    var region: String { get }
}

public class FiltersManager {
    private var originalArray: [any DataModel]
    private var filteredArray: [any DataModel]
    private(set) var filters: [any FilterSpecification]

    public init(array: [any DataModel], filters: [any FilterSpecification]) {
        self.originalArray = array
        self.filteredArray = array
        self.filters = filters
        self.filters.forEach {
            $0.extractValues(from: array)
        }
    }
}

struct City1: DataModel {
    let code: String
    let country: String
    let region: String
}

struct City2: DataModel {
    let code: String
    let country: String
    let region: String
}

class CountryFilter1: FilterSpecification {
    var name = "Country"
    var values = [String]()
    var condition: ((any DataModel) -> Bool)? {
        guard !values.isEmpty else { return nil }
        return { city in
            self.values.contains(city.country)
        }
    }

    func extractValues(from array: [any DataModel]) {
        // remove duplicates
    }
}

class RegionFilter1: FilterSpecification {
    var name = "Region"
    var values = [String]()
    var condition: ((any DataModel) -> Bool)? {
        guard !values.isEmpty else { return nil }
        return { city in
            self.values.contains(city.region)
        }
    }

    func extractValues(from array: [any DataModel]) {
        // remove duplicates
    }
}

let cities = [City1(code: "001", country: "US", region: "EST"), City2(code: "002", country: "CA", region: "MST")]
let manager = FiltersManager(array: cities, filters: [CountryFilter1(), RegionFilter1()])
Swift Protocol Constraint Required - Advanced
 
 
Q