Swift Generic Factory: Bug?

2019-07-21 13:45发布

问题:

Investigating Swift generics, and stumbling on some really odd behaviour... should I file a radar, or am I misunderstanding something here? Tested with Swift 1.2 beta.

Code speaks best, simple implementation which constrains the factory to instantiating BaseClass and derived classes:

/// Testing factory pattern to instantiate BaseClass and derived
class BaseClassFactory
{
    // Always returns an instance of BaseClass, even if the constant or
    // variable storing the result is explicitly typed( see test )
    class func makeInstance< T : BaseClass >( type: AnyClass ) -> T?
    {
        return T()
    }

    // Returns an instance of DerivedClass only if the constant or variable storing 
    // the result is explicitly typed.
    class func makeInstanceContrived< T : BaseClass >( type: AnyClass ) -> T?
    {
        let instance : T? = T.makeInstance() as? T
        return instance
    }

    // Works fine
    class func makeInstanceAndCallBack< T : BaseClass >( handler: ( instance: T? ) -> Void ) -> Void
    {
        let myInstance : T? = T.makeInstance() as? T

        handler( instance: myInstance )
    }
}

class BaseClass
{
    // Since T() fails... 
    class func makeInstance()->BaseClass
    {
        return BaseClass()
    }
}

class DerivedClass : BaseClass
{
    override class func makeInstance()->BaseClass
    {
        return DerivedClass()
    }
}

Tests, with screenshot of very odd behaviour( despite the compiler warning, the 'is' test does pass ):

// Nope
if let instance = BaseClassFactory.makeInstance( DerivedClass.Type )
{
    if instance is DerivedClass == false
    {
        println( "1: Wrong type..." )
    }
}

// Nope, even when typing the constant. This seems like very dangerous behaviour...
if let instance : DerivedClass = BaseClassFactory.makeInstance( DerivedClass.Type )
{
    if instance is DerivedClass == false
    {
        //compiler even gives a warning here: " 'is' test is always true "
        println( "2: what the???" )
    }
}

// Nope
if let contrivedInstance = BaseClassFactory.makeInstanceContrived( DerivedClass.Type )
{
    if contrivedInstance is DerivedClass == false
    {
        println( "3: Wrong type..." )
    }
}

// Yes, typing the constant does the trick here
if let contrivedInstance : DerivedClass = BaseClassFactory.makeInstanceContrived( DerivedClass.Type )
{
    println( "4: success! type is: \(contrivedInstance )" )
}

// Yes
BaseClassFactory.makeInstanceAndCallBack()
{
    ( instance: DerivedClass? ) -> Void in

    if let callbackInstance = instance
    {
        println( "5: success! type is: \(callbackInstance )" )
    }
}

回答1:

You have two issues here – one is incorrect code, the other is a known bug (my radar of it was closed as a dup of 18518629 which is still open as of 1.2b4).

Firstly, in this construction:

class func makeInstance< T : BaseClass >( type: AnyClass ) -> T?
{
    return T()
}

// then later

BaseClassFactory.makeInstance( DerivedClass.Type )

your argument is not doing anything. It's essentially pointless and not contributing to the type of T (how could it? the argument does not reference T). Instead, the type of T will be chosen from context i.e. if you assign the result to a DerivedClass variable, T will be DerivedClass. If you don't specify, the default behavior is to make T the base class it's constrained to i.e. BaseClass.

What you probably mean is this:

class func makeInstance< T : BaseClass >( type: T.Type ) -> T?
{
    return T()
}

// then later

BaseClassFactory.makeInstance( DerivedClass.self )

This should do the trick of setting T to be the type you want. Except it still won't work, because of a bug caused by the base class not having a dynamically-dispatched initializer (there is only a single runtime implementation of the generic and it relies on the right init being called polymorphically).

If you add required init() { } to BaseType you'll get the correct behaviour.