Swift 3, is the ".self" in a metatype issue actually correct?
Asked Answered
E

1

3

I have an extension to walk up the view controller chain (even through container views, which is very handy)

public extension UIViewController   // go up to a certain class
    {
    public func above<T>(_ : T.Type)->(T)
        {
        var p:UIResponder = self
        repeat { p = p.next! } while !(p is T)
        return p as! T
        }
    }

(Aside, NB, Swift3 needs the "!" on p.next: unfortunately I'm not sure exactly why.)

So, say you have a view controller class "General", you can

self.above(General).clickedHamburgerMenuButton()

and it will find the first "General" above you. All fine but with Swift 3 you get this warning.......

Missing '.self' for reference to metatype of type 'General'

It seems to want this

self.above(General.self).clickedHamburgerMenuButton()

1) It seems ... dangerous ... to change General to General.self - in fact is it safe and is the meaning the same as General in Swift <3 ?

2) in the extension

    public func above<T>(_ : T.Type)->(T)

why is that a metatype? Did I "do something wrong" and make it ask for a metatype rather than just a type?

2) What the hell is a "metatype"? (I can't, really, find it explained as such anywhere.) That is to say, what can "general" possibly mean there other than "the class itself". (Not an instance, or a static instance, or anything else...)

Enthronement answered 3/10, 2016 at 15:40 Comment(4)
Being able to omit .self for single argument functions was a bug pre-Swift 3, see swift class as parameter without .self. Also see this Q&A about why .self is needed. As for info about metatypes, see this part of the documentation :). In your example, T.Type is the metatypeWreak
thanks @Hamish, that is a beautiful and information dense comment :)Enthronement
Btw, of course your method can be written without forced unwrapping, and note that it would crash if no matching responder is found.Vtarj
Hi @MartinR - thanks for that. if I ever totally understand what "forced unwrapping" is, I will be able to start understanding your comment .. :-) thxEnthronement
R
2

(Aside, NB, Swift3 needs the "!" on p.next: unfortunately I'm not sure exactly why.)

Because .next returns an Optional, but p is not optional. This will crash if you run out of responders.

1) It seems ... dangerous ... to change General to General.self - in fact is it safe and is the meaning the same as General in Swift <3 ?

I'm surprised that worked in earlier versions of Swift without the .self, but yes, .self is required in order to directly reference a metatype. Referencing metatypes is somewhat rare in Swift, and can lead to surprising behaviors if done unintentionally, so it requires an extra piece of syntax to say "yes, I really mean the type."

why is that a metatype? Did I "do something wrong" and make it ask for a metatype rather than just a type?

You did this correctly.

2) What the hell is a "metatype"?

The type of a type. An instance of a metatype is a type. Consider:

func f(x: Int)

To call this, you pass an instance of Int. So similarly:

func f<T>(x: T.Type)

To call this, you pass an instance of the metatype T.Type, which is a type.


Unrelated, but I would probably rethink this code along these lines. First, it is convenient to be able to treat the responder chain as a sequence. Here's one way to do that:

public extension UIResponder {
    public func nextResponders() -> AnySequence<UIResponder> {
        guard let first = next else { return AnySequence([]) }
        return AnySequence(sequence(first: first, next: { $0.next }))
    }
}

Then getting the next responder that matches a type is cleaner and clearer IMO (and works on any responder chain):

public extension UIResponder {
    public func firstResponder<T: UIResponder>(ofType _: T.Type)-> T? {
        return nextResponders()
            .flatMap { $0 as? T }
            .first
    }
}

...

self.firstResponder(ofType: General.self)?.clickedHamburgerMenuButton()
Rochkind answered 3/10, 2016 at 15:55 Comment(3)
TBC if I'm no tmistaken your nextResponders in fact returns THE WHOLE RESPONDER CHAIN .. all classes. (Indeed, using recursion there.) Then, your firstResponder simply picks out all of the desired class (the flatMap) and just returns the first one of those (.first). AwesomeEnthronement
Yes, nextResponders returns the entire responder chain lazily (so it doesn't actually compute the entire chain until they're requested). It's not recursive, though; sequence is iterative.Rochkind
oh I see ... next: { $0.next } is putting in all of them ... wildEnthronement

© 2022 - 2024 — McMap. All rights reserved.