mpounsett
mpounsett

Reputation: 1194

Pass a class type for use inside a method

In order to reduce cut-and-paste code in this app, I'm trying to pass class names around in order to tell a method which way it should process some data. I have something like the following:

class MyClass : NSObject {
    var name : String = ""
}

class OneClass : MyClass {
    override init() {
        super.init()
        self.name = "One"
    }
}

class TwoClass : MyClass {
    override init() {
        super.init()
        self.name = "Two"
    }
}


class Thing : NSObject {
    func doStuff(withClass cls: AnyClass) -> String {
        let x = cls.init()
        return x.name
    }
}

let z = Thing()
print(z.doStuff(withClass: OneClass))
print(z.doStuff(withClass: TwoClass))

Passing withClass cls: AnyClass the parser pushed me to change let x = cls() to let x = cls.init(). But I've got an Expected member name or constructor call after type name error for the last two lines. The recommended fixes both cause other problems.

The first suggestion, adding the () constructor after the class name, causes new errors on those lines: Cannot convert value of type 'OneClass' to expected argument type 'AnyClass' (aka 'AnyObject.Type')

Taking the second suggestion and changing them to OneClass.self and TwoClass.self gets rid of the parser errors, but when I execute the code it just runs forever.. never erroring out, and never completing.

I found a recommendation elsewhere that suggests I should change the Thing.doStuff() parameters to expect MyClass instead of AnyClass, but that causes another set of new problems.

First, the parser starts complaining about the cls.init() call, and the series of fixes it suggests eventually lead to something that makes no sense: let x = cls.type(of:;; init)(). The parser ends up in a suggestion loop where it keeps adding more semi-colons in the middle of the statement.

Second, I'm back to type mismatch errors on the calls to doStuff() in the last two lines: Cannot convert value of type 'OneClass.Type' to expected argument type 'MyClass'.

There's obviously something I'm not getting here about passing types as arguments, but none of the googling I've done has landed me on something that explains the problems I'm seeing.

Upvotes: 0

Views: 115

Answers (2)

PGDev
PGDev

Reputation: 24341

To get this working, you must call init on cls after typecasting it to NSObject.Type. Also, x.name only works if cls Class type contains that particular property. This is the reason x is then typecasted to MyClass.

class Thing : NSObject
{
    func doStuff(withClass cls: AnyClass) -> String?
    {
        let x = (cls as? NSObject.Type)?.init()
        if let x = x as? MyClass
        {
            return x.name
        }
        return nil
    }
}

Call doStuff with ClassType.self

print(z.doStuff(withClass: OneClass.self))
print(z.doStuff(withClass: TwoClass.self))

Let me know if you still face any issues.

Upvotes: 1

vadian
vadian

Reputation: 285079

How about the generic Swift way.

The code constrains the generic type T to MyClass since it must have a name property.

class MyClass : NSObject {
    var name : String

    override required init() {
        self.name = ""
        super.init()
    }
}

class OneClass : MyClass {
    required init() {
        super.init()
        self.name = "One"
    }
}

class TwoClass : MyClass {
    required init() {
        super.init()
        self.name = "Two"
    }
}


class Thing : NSObject {
    func doStuff<T : MyClass>(withClass cls: T.Type) -> String {
        let x = cls.init()
        return x.name
    }
}

let z = Thing()
print(z.doStuff(withClass: OneClass.self))
print(z.doStuff(withClass: TwoClass.self))

Or use a protocol.

protocol Nameable {
    var name : String { get }
    init()
}

class MyClass : NSObject, Nameable { ...

...

class Thing : NSObject {
    func doStuff<T : Nameable>(withClass cls: T.Type) -> String {
        let x = cls.init()
        return x.name
    }
}

Upvotes: 1

Related Questions