5
votes

I need to check if an object's class has a member which could be used for assigning a value. Let's say there's a class:

class MyClass: NSObject {

  var myVar : AnyObject!

  // ...
}

I tried this objC style but properties are a different beast in Swift and second line always returns false:

let myClass: MyClass = MyClass()
let hasClassMember = myClass.respondsToSelector(Selector("setMyVar:"))

if hasClassMember {

  myClass.performSelector("setMyVar:", withObject: "Context" as! AnyObject)
} 

I assume Swift doesn't have a powerful reflection as ObjC has, but is there any solution?

UPDATE It turns out my code works, however I have simplified it for demo purposes. The weird part is respondsToSelector stops working as soon as I change the type of myVar to be an instance of some custom class, let say ClassB. Feels it a bit magic... :)

UPDATE2 Finally I found the problem. I haven't wrote the full code, thought the problem is elsewhere, anyway, here's the full code. MyClass variable is actually a type of some other AnotherClass which implements AnotherClassProtocol

protocol AnotherClassProtocol {
  //…
}

class AnotherClass: AnotherClassProtocol {
  //…
}

class MyClass: NSObject {

  var myVar : AnotherClass!
  // ...
}

This was causing myClass.respondsToSelector(Selector("setMyVar:")) always return false. It turns out the problem was because I omitted extending of NSObject in my AnotherClass declaration. It starts working as expected after I fixed that:

class AnotherClass: NSObject, AnotherClassProtocol {
  //…
}

I'm still learning Swift and thought NSObject is not needed as the compiler was not complaining and everything extend from NSObject anyway (at least in ObjC). Well, I'm glad I found this nasty bug.

4
I tried this code in a playground and it worked correctly for me. I had to remove the as! AnyObject and I also remove the selector() around the setMyVar: as it isn't needed. hasClassMember was also true/false based on the property namePaulw11

4 Answers

10
votes

A slightly nicer way than checking for selectors would be to use a protocol.

protocol MyClassProtocol {
    var myVar : AnyObject! {get set}
}

class MyClass: NSObject, MyClassProtocol {
     var myVar : AnyObject!
     // ...
}

...

let c = MyClass()

if let conformingMyClass = c as? MyClassProtocol { // returns true
    print("yay")
    conformingMyClass.myVar = "Context"
} else {
    print("nay")
}

This way it's a lot more explicit which classes do and don't have your property and you don't have to muck about with selectors (much more Swift-y).

5
votes
import Foundation
class MyClass: NSObject {

    var myVar1 : AnyObject!

    // ...
}

let myClass: MyClass = MyClass()
let hasClassMemberMyVar1 = myClass.respondsToSelector(Selector("setMyVar1:")) // true
let hasClassMemberMyVar2 = myClass.respondsToSelector(Selector("setMyVar2:")) // false

it works for me ...

UPDATE, based on OP notes

import Foundation
class C:NSObject {}
class MyClass: NSObject {
    var myVar1 : C? // Objective-C representable
    var i: Int = 0  // Objective-C representable
    var j: Int? = 10
}

let myClass: MyClass = MyClass()
let hasClassMemberMyVar1 = myClass.respondsToSelector(Selector("setMyVar1:")) // true
let hasClassMemberMyVar2 = myClass.respondsToSelector(Selector("setMyVar2:")) // false
let hasClassMemberI = myClass.respondsToSelector(Selector("setI:")) // true
let hasClassMemberJ = myClass.respondsToSelector(Selector("setJ:")) // false, because Optional<Int> is not representable in Objective-C !!!
print(myClass.i.dynamicType, myClass.j.dynamicType) // Int Optional<Int>

with class type properties only

import Foundation
class C:NSObject {}
class C1 {}
class MyClass: NSObject {
    var c : C?
    var cO1: C = C()
    var cO2: C!
    var c1: C1 = C1()
    var c2: C1?
    var c3: C1!
}

let myClass: MyClass = MyClass()
let hasClassMemberC = myClass.respondsToSelector(Selector("setC:")) // true
let hasClassMemberCO1 = myClass.respondsToSelector(Selector("setCO1:")) // true
let hasClassMemberCO2 = myClass.respondsToSelector(Selector("setCO2:")) // true
let hasClassMemberC1 = myClass.respondsToSelector(Selector("setC1:")) // false, class C1 is not Objective-C representable ...
let hasClassMemberC2 = myClass.respondsToSelector(Selector("setC2:")) // false, Optional<C1> is not Objective-C representable ...
let hasClassMemberC3 = myClass.respondsToSelector(Selector("setC3:")) // false, ImplicitlyUnwrappedOptional<C1> is not Objective-C representable ...
4
votes

Swift 3 version of Ryan Huubert's answer:

class MyClass: NSObject {

    var myVar : AnyObject!
    // ...
}

let myClass = MyClass()

myClass.responds(to: Selector("myVar")) // returns true
myClass.responds(to: Selector("myVar:")) // returns false
1
votes
class MyClass: NSObject {

    var myVar : AnyObject!
    // ...
}

let myClass = MyClass()

myClass.respondsToSelector("myVar") // returns true
myClass.respondsToSelector("myVar:") // returns false