5

I'm trying to run this code but it's yielding unexpected results.

class Test: NSObject {
    @objc var property: Int = 0
}

var t = Test()

t.perform(#selector(setter: Test.property), with: 100)
print(t.property)

The value being printed is some junk number -5764607523034233277. How can I set the value of a property using the perform method?

Rob C
  • 4,877
  • 1
  • 11
  • 24

2 Answers2

4

The performSelector:withObject: method requires an object argument, so Swift is converting the primitive 100 to an object reference.

The setProperty: method (which is what #selector(setter: Test.property) evaluates to) takes a primitive NSInteger argument. So it is treating the object reference as an integer.

Because of this mismatch, you cannot invoke setProperty: using performSelector:withObject: in a meaningful way.

You can, however, use setValue:forKey: and a #keyPath instead, because Key-Value Coding knows how to convert objects to primitives:

t.setValue(100, forKey: #keyPath(Test.property))
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • That's a good answer but don't forget that swift doesn't exactly have primitive types. I get what you are saying though. – Rob C Apr 07 '18 at 00:32
  • 2
    If you wanted to use keypaths, you can also do `t[keyPath: \Test.property] = 42`. This is a strongly typed way of doing it. (I just wanted to see how many different Rob's could contribute to one answer.) – Rob Apr 07 '18 at 00:37
  • @Rob I actually need a very generic way to set values on a variety of different objects. Because KeyPaths are strongly typed they aren't going to work for me. I just discovered that setValue and value(forKey:) will allow me to do what I need. – Rob C Apr 07 '18 at 00:48
0

You can't use perform(_:with:) to do that since Int is not a NSObject subclass which is necessary for the arg. To specify the type you can workaround it with:

let setterSelector = #selector(setter: Test.property)
let setterIMP = t.method(for: setterSelector)
//Invoking setter
unsafeBitCast(setterIMP,to:(@convention(c)(Any?,Selector,Int)->Void).self)(t, setterSelector,100)

let getterSelector = #selector(getter: Test.property)
let getterIMP = t.method(for: getterSelector)
//Invoking getter
let intValue = unsafeBitCast(getterIMP,to:(@convention(c)(Any?,Selector)->Int).self)(t, getterSelector)

You can find more details and examples in my answer here

Kamil.S
  • 5,205
  • 2
  • 22
  • 51