Joe Blow Joe Blow - 6 months ago 32
Swift Question

Pass in a type to a generic Swift extension, or ideally infer it

Please note that the suggested duplicate question answers a related but different issue, and does not at all touch on the infer/specify question.




Say you have

class Fancy:UIView


you want to find all sibling
Fancy
views. No problem...

for v:UIView in superview!.subviews
{
if let f = v as? Fancy
{ f.hungry = false }
}


So, try an extension,

public extension UIView
{
internal func fancySiblings()->([Fancy])
{
return (self.superview!
.subviews
.filter { $0 != self }
.flatMap { $0 as? Fancy }
)
}
}


Awesome, you can now

for f:Fancy in self.fancySiblings()
{ f.hungry = false }


Fantastic.

But,

How to generalize that extension to work with any UIView subtype?



So, something like ...

public extension UIView
{
internal func siblings<T>( something T )->([T])
{
return (self.superview!
.subviews
.filter { $0 != self }
.flatMap { $0 as? T }
)
}


and then you could call it something like this ...

for f in self.siblings(Fancy)
for p in self.siblings(Prancy)
for b in self.siblings(UIButton)


How can you "tell" a generic extension the type to use, like that??

Now, incredibly, I found you can "infer it backwards", which is basically amazing

public extension UIView
{
internal func incredible<T>()->([T])
{
return (self.superview!
.subviews
.filter { $0 != self }
.flatMap { $0 as? T }
)
}


and amazingly that works perfectly ...

for f:Fancy in self.incredible()

for p:Prancy in self.incredible()


Damn!

It even correctly throws an error: "Generic paramater 'T' could not be inferred" if you do this...

for f in self.incredible()


You can even...

self.siblings().forEach{
(f:Fancy) in
d.hasRingOn = false
}


However I would, I think, still like to know how to "pass in" a type like that?

So that you can call it something like this...

for f in self.siblings(Fancy)


Is it possible? What's the syntax for the call and the extension to do that?

Answer

Simply use the .Type:

internal func siblings<T>( something : T.Type)->([T]) {
    ...
}

Afterwards for f in self.siblings(Fancy) should work exactly as expected.

Full working example:

class Fancy : UIView {}

public extension UIView {
    internal func siblings<T>( _ : T.Type)->([T]) {
        return (self.superview!
            .subviews
            .filter { $0 != self }
            .flatMap { $0 as? T }
        )
    }
}

let superView = UIView()
let view = UIView()
superView.addSubview(view)
superView.addSubview(UIView())
superView.addSubview(Fancy())

print(view.siblings(Fancy))

Correctly outputs the one Fancy view!


To address the requested addition for optionally using the explicit type parameter or take effect of the type inference of the compiler. You can create a second method in the same extension

internal func siblings<T>()->([T]) {
    return siblings(T)
}

That way providing a explicit type parameter calls method one, omitting it will require you to make it inferable and will call the second function which in terms calls the first one internally.


Or, you can surprisingly make the default nil, which, remarkably, will force the inference in that case:

// power extension, it provides both infered or stated typing
internal func siblings<T>(_:T.Type? = nil)->([T])
    {
    return (self.superview!
        .subviews
        .filter { $0 != self }
        .flatMap { $0 as? T }
        )
    }

both

   for f in self.siblings(Fancy)

and

   for f:Fancy in self.siblings()

will now work.

Comments