Returning the subclass in a UIViewController stati

2019-02-20 13:18发布

Consider a base UIViewController class...

class Rooms: UIViewController {
    class func instantiate()->Rooms {
    }

    static func make()->Rooms {
        let emplacedAndSetup = self.instantiate()
        // various kodes here
        // very likely put s.view somewhere
        return emplacedAndSetup
    }

    sundryOtherFunctionality()
}

(Note the self. before instantiate() which seems to be necessary to get "that" instantiator.)

Each subclass knows its own storyboard ID to how use to instantiateViewController:

class Dining: Rooms {
    override class func instantiate()->Dining { // returns a "Dining"
        let d = stbd.instantiateViewController(
            withIdentifier: "Some Specific Scene") as! Dining
        return d
    }
}
class Bath: Rooms {
    override class func instantiate()->Bath { // returns a "Bath"
        let b = stbd.instantiateViewController(
            withIdentifier: "Some Other Scene") as! Bath
        return b
    }
}

You can do this,

let d  = Dining.make()
let r  = Bath.make()

The only minor problem is, it returns the base class. BUT SEE BELOW. So in practice you have to

let d  = Dining.make() as! Dining
let r  = Bath.make() as! Bath

Is there a way to modify the static make so that indeed Dining.make() would return a Dining and Bath.make() would return a Bath?

( @Hamish has pointed out that one could use an init and Self pattern, Method that returns object of type which was called from However, I think that's not possible due to the instantiateViewController. )


So. Say you have code like

let d = Dining.make(blah blah)

in fact. At run time, d does become a "Dining", not a "Room"

that's fantastic.

But. If you do this in the IDE

let d:Dining = Dining.make(blah blah)

it fails - it thinks d is going to be a Room, not a Dining.

So all your code has to look like this:

let d = Dining.make(blah blah) as! Dining

which sucks. How to fix?


Note just TBC the solution is to make the static a generic, rather as in MartinR's answer here https://stackoverflow.com/a/33200426/294884 Example code in answer below.

2条回答
趁早两清
2楼-- · 2019-02-20 13:36

You can do something like this.

class RoomBase: RoomProtocol {
    // things common to every room go here
    required init() {}
}

You can put in RoomBase all the stuff you want the other rooms to inherit.

Next you put the make() method into a protocol extension.

protocol RoomProtocol: class {
    init()
}

extension RoomProtocol where Self: RoomBase {
    static func make() -> Self {
        let room = Self()
        // set up
        return room
    }
}

Now you can write

class Dining: RoomBase {}
class Bath: RoomBase { }

And this code will work

let dining: Dining = Dining.make()
let bath: Bath = Bath.make()
查看更多
来,给爷笑一个
3楼-- · 2019-02-20 13:36

I don't like to provide my own answer, but the solution is ..

So the problem is, at editor time

let d = Dining.make()

"doesn't work", you have to do this

let d = Dining.make() as! Dining

(It DOES work at compile time, d becomes a Dining: it doesn't "work" at editor time.)

So the solution is

static func make()->Rooms {
    let emplacedAndSetup = self.instantiate()
    return emplacedAndSetup
}

becomes

static func make<T: Rooms>()->T {
    let emplacedAndSetup = self.instantiate() as! T
    return emplacedAndSetup
}

So that's it.

Note - it's entirely possible AppzForLife's solution works and/or is better as a general purpose "UIViewController auto-instantiator", but this is the answer to the question per se.

查看更多
登录 后发表回答