Swift perform Selector returns the object that was passed

Hi
So I notices that:
func perform(_ aSelector: Selector!, with object: Any!) -> Unmanaged<AnyObject>!
Will return the object itself when the selector is a void function:
So I have something like that:

func performPendoSelector(selectorName: String, with object: Any? = nil) -> Any? {
        let selector = NSSelectorFromString(selectorName)
        if self.responds(to: selector) {
            let result = self.perform(selector, with: object)
            if let result = result {
                return result.takeUnretainedValue()
            }
        }
        return nil
    }

It calls 2 methods (currently). one method returns a UIVIew and the other returns void in objc :

- (void)trackPageAppeared:(nonnull NSString *)pageId {
// calling void method 
}

- (nonnull UIView *)pndPixelView:(nonnull NSString *) pageId {
return [[PNDPixelView alloc] initWithPageId:pageId];
}

For some reason when trackPageAppeared is passed as selectorName the result is Umanaged with the value of the parameter. That's kinda strange I would anticipate it to be nil
and then return nil. Instead it will continue result.takeUnretainedValue() which is less desirable.
Also would you think in that case the code is crash safe ?

A void function makes no promises about its return value. What you’re seeing is whatever was left in the return register after the method’s implementation completed. I agree nil would make more sense here, but then the computer would be spending extra time zeroing out a value that will never be used by the caller anyway.

1 Like

Thanks @jrose
That make sense. I have a concern when I apply:

let result = self.perform(selector, with: object)
if let result = result {
          return result.takeUnretainedValue()
}

Then I saw a crash result.takeUnretainedValue() with EXC BAD Access.
Kinda of those memory crashes that's hard to debug. Now after I restarted the simulator I couldn't reproduce it any more no matter what I tried.
Other simulators, Devices , all possible Xcode tools like zombies memory allocations leaks sanitizers but the crash has gone.
And now I am hesitated if the code is safe. What would you recommend from your experience?

This is not code that works unless you know the return type is an object. If it’s void, you get garbage with no guarantees; if it’s NSInteger you need to convert from Unmanaged to a raw pointer and then get its bit pattern; if it is an object but it was returned at +1, you’ve now leaked it; etc.

Swift refuses to provide of the operations necessary to work (more) safely with selectors; if you really need to do this, write your helper in ObjC and use NSInvocation. But even then you have to know yourself if the return value is retained or not. So I’d also suggest “don’t use selectors for this” if possible, or “only use selectors that all have the same return type” (not enforced by the compiler, but something you can promise yourself).

2 Likes