MikeJfromVA
MikeJfromVA

Reputation: 513

How to cast to class type polymorphically in Swift?

Here, layEgg() wants to reuse chickenFactory(Chicken.Type). The problem is that layEgg returns Self? so that I get an instance of whatever type this is or nil.

However, chickenFactory returns a Chicken, which needs to be cast to whatever type I am.

enum BiologicalGender : String {
    case male = "male"
    case female = "female"
}

class Chicken {
    let gender : BiologicalGender

    required init (_ gender : BiologicalGender) {
        self.gender = gender
    }

    class func chickenFactory(_ chickenType : Chicken.Type) -> Chicken {
        if (arc4random_uniform(2) == 0) {
            return chickenType.init(.male)
        } else {
            return chickenType.init(.female)
        }
    }

    func layEgg() -> Self? {
        if (self.gender == .female) {
            return Chicken.chickenFactory(type(of:self)) // need a cast here
        } else {
            return nil
        }
    }
}

How do I cast polymorphically?

Upvotes: 3

Views: 694

Answers (3)

MikeJfromVA
MikeJfromVA

Reputation: 513

This does not necessarily improve the code, but here is a class function wrapping the original factory that accomplishes the cast to Self that so many of you demonstrated.

The original, improved generic chickenFactory() is also included.

class Chicken {
    let gender : BiologicalGender

    required init (_ gender : BiologicalGender) {
        self.gender = gender
    }

    class func chickenFactory<T:Chicken>(_ chickenType : T.Type) -> T {
        if (arc4random_uniform(2) == 0) {
            return chickenType.init(.male)
        } else {
            return chickenType.init(.female)
        }
    }

    class func poorlyTypedChickenFactory(_ chickenType : Chicken.Type) -> Chicken {
        if (arc4random_uniform(2) == 0) {
            return chickenType.init(.male)
        } else {
            return chickenType.init(.female)
        }
    }

    private class func chickenFromPoorlyTypedFactory<T>(_ chickenType : Chicken.Type) -> T?
    {
        let chicken = poorlyTypedChickenFactory(chickenType) as? T
        return chicken
    }

    func layEgg() -> Self? {
        if (self.gender == .female) {
            return Chicken.chickenFromPoorlyTypedFactory(type(of:self))
        } else {
            return nil
        }
    }
}

Upvotes: 0

Hamish
Hamish

Reputation: 80781

The problem is that chickenFactory(_:) says that it takes a type of type Chicken.Type as an argument and outputs an instance of Chicken. The instance that is outputted need not be of type the type you passed in (for example, it could output an instance of Chicken when you input a type of ChickenSubclass.self).

What you want is not type-casting, but rather a way to say that the instance outputted is the same type as the type you input. To express this, you can use a generic placeholder:

class func chickenFactory<T : Chicken>(_ chickenType : T.Type) -> T {
    if (arc4random_uniform(2) == 0) {
        return chickenType.init(.male)
    } else {
        return chickenType.init(.female)
    }
}

Now

func layEgg() -> Self? {
    if (self.gender == .female) {
        return Chicken.chickenFactory(type(of: self))
    } else {
        return nil
    }
}

compiles just fine, as the compiler knows that an instance of type type(of: self) can be returned from a function returning Self?.

Although that all being said, the type argument to chickenFactory(_:) is seemingly redundant, you could just use the type that it's called on instead through the use of self at static scope:

class func chickenFactory() -> Self {
    if (arc4random_uniform(2) == 0) {
        return self.init(.male)
    } else {
        return self.init(.female)
    }
}

func layEgg() -> Self? {
    if (self.gender == .female) {
        return type(of: self).chickenFactory()
    } else {
        return nil
    }
}

Upvotes: 4

ebby94
ebby94

Reputation: 3152

Another solution for the problem would be this. Check this answer

func layEgg() -> Self? {
    if (self.gender == .female) {
        return makeEgg()
    } else {
        return nil
    }
}

func makeEgg<T>()->T?{
    return Chicken.chickenFactory(type(of:self)) as? T
}

Upvotes: 1

Related Questions