0

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.

mpounsett
  • 1,174
  • 1
  • 10
  • 30

2 Answers2

1

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
    }
}
vadian
  • 274,689
  • 30
  • 353
  • 361
  • Thanks! I had come across generics before, but only as a feature description, and didn't really get how they'd be useful (so didn't think of them as an option here). This works great, and gives me a bit more insight into generics. – mpounsett Apr 18 '18 at 18:48
1

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.

PGDev
  • 23,751
  • 6
  • 34
  • 88
  • Thanks! While this also seems to solve the issue, I think the generics method seems more Swift-y, so I'm going to mark that one as accepted. This definitely gets an upvote though. – mpounsett Apr 18 '18 at 18:47