Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why use performSelector:withObject:withObject at runtime if you know both the selector and its arguments at compile time?

I've just come across some code in Three20 that looks like this:

  SEL sel = @selector(textField:didAddCellAtIndex:);
  if ([self.delegate respondsToSelector:sel]) {
    [self.delegate performSelector:sel withObject:self withObject:(id)_cellViews.count-1];
  }

On LLVM 2.0, this causes the compilation error:

error: arithmetic on pointer to interface 'id', which is not a constant size in non-fragile ABI

I know why that error is occurring and I know how to fix it. I just need to invoke the method directly, like so:

  SEL sel = @selector(textField:didAddCellAtIndex:);
  if ([self.delegate respondsToSelector:sel]) {
    [self.delegate textField:self didAddCellAtIndex:(_cellViews.count - 1)];
  }

My question is, if you know both the selector and its arguments at compile time, why would you need to use performSelector:withObject:withObject: at runtime? I don't see why the code was written this way in the first place. If the selector and arguments were dynamically passed into the method, I may understand, but they're not, the selector and its arguments are hard coded, (even if the index does change during run time, its method of obtaining the index is hard coded.)

If someone could explain to me a good reason why this would be necessary, I'd be grateful. Otherwise, I'll be over here changing all this code.

like image 524
Jasarien Avatar asked Feb 05 '11 00:02

Jasarien


2 Answers

After a little more digging, it looks like the TTPickerTextField class that this code is found in is an indirect subclass of a UITextField.

As such, it is piggy-backing on UITextFields delegate property, which doesn't conform to the TTPickerTextFieldDelegate protocol where the method textField:didAddCellAtIndex: is declared.

I have come to the conclusion that this code is just laziness. No reason why the UITextFields delegate property had to be piggy-backed, making this confusing, error prone code necessary.

My own approach would have been to leave UITextFields delegate property alone, and add my own property in my specific subclass that handled the specific delegate methods.

Just to clarify - the 'solution' I mentioned in the question fixes the compiler error, but generates a warning that the method can't be found and will be assumed to return id. This is what the original code was 'solving' but that only worked in GCC. No longer with LLVM 2.0.

Last edit, I promise:

My final solution to combat this laziness and get rid of the warning and error is an ugly hack:

[(id <TTPickerTextFieldDelegate>)self.delegate textField:self didAddCellAtIndex:(_cellViews.count - 1)];

Cast UITextFields delegate to an id that conforms to TTPickerTextFieldDelegate and then invoke the method directly.

Please don't be lazy :(

like image 72
Jasarien Avatar answered Oct 01 '22 01:10

Jasarien


That respondsToSelector/performSelector combo is an idiom for optional delegate methods. The delegate isn't guaranteed to have that method defined, so a direct call to it would cause a compiler warning.

What the compiler was actually complaining about in this case:

[self.delegate performSelector:sel withObject:self withObject:(id)_cellViews.count-1];

error: arithmetic on pointer to interface 'id', which is not a constant size in non-fragile ABI

is risky pointer arithmetic... 'id' is a pointer type, so:

(id)_cellViews.count-1

tells the compiler it's going to subtract one from a pointer instead of an integer....which is probably not the intent of that code. The withObject argument of performSelector has to be a pointer, it can't be a primitive. You can get around this by wrapping _cellViews.count - 1 in an NSNumber, and unwrapping it in the delegate method.

[self.delegate performSelector:sel withObject:self withObject:[NSNumber numberWithInt:_cellViews.count-1]];
like image 37
dstnbrkr Avatar answered Oct 01 '22 00:10

dstnbrkr