0

According to this article For loops in objective C can be optimized using SEL & IMP. I have been toying with the idea for a bit now, and today I've been trying some tests. However what seems to work for one class, does not seem to work for another. Furthermore, I would like to know how exactly the speedup occurs ? By avoiding objC_mesgSent ?

Question 1 How is this:

Cell *cell;
for (NSMutableArray *row in self.data) {
    for (Data *d in row){
        cell = [[Cell alloc] initWithRect:tmp_frame];
        [self addSubview:cell.view];
        [self.cells addObject:cell];

Worse than this:

SEL addCellSel = @selector(addObject:);
IMP addCellImp = [self.cells methodForSelector:addCellSel];
Cell *cell;
for (NSMutableArray *row in self.data) {
    for (Data *d in row){
        cell = [[Cell alloc] initWithRect:tmp_frame];
        [self addSubview:cell.view];
        addCellImp(self.cells,addCellSel,cell);

Question 2 Why does this fail ? (Note self is a class that inherits from UIView)

SEL addViewSel = @selector(addSubview:);
IMP addViewImp = [self methodForSelector:addViewSel];
Cell *cell;
for (NSMutableArray *row in self.data) {
    for (Data *d in row){
        cell = [[Cell alloc] initWithRect:tmp_frame];
        addViewImp(self,addViewSel,cell.view);
        [self.cells addObject:cell];

The error:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[SimpleGridView addSubview:]: unrecognized selector sent to instance 0xaa3c400'

Tells me that method addSubview has not been found in my Class "SimpleGridView". However when I tried:

if ([self respondsToSelector:addViewSel]){
    NSLog(@"self respondsToSelector(AddViewSel)");
    addViewImp(self,addViewSel,cell.view);
} else {
    NSLog(@"self does not respond to selector (addViewSel");
   [self addSubview:cell.view];  
}

I still get the exact same error !

Question 3 Why can't I set a selector & implementation to a Class Init/new method like so:

iContactsGridCell *cell;
SEL initCellSel = @selector(initWithRect:);
IMP initCellImp = [iContactsGridCell methodForSelector:initCellSel];
for (NSMutableArray *row in self.data) {
    for (Data *d in row){
        cell = initCellImp([iContactsGridCell new],initCellSel,tmp_frame);

For reference: Class iContactsGridCell inherits from Class Cell, which defines and implements

- (id) initWithRect:(CGRect)frame;

Furthermore, casting did not help (same error about unrecognized selector)

iContactsGridCell *cell;
SEL initCellSel = @selector(initWithRect:);
IMP initCellImp = [Cell methodForSelector:initCellSel];
for (NSMutableArray *row in self.data) {
    for (Data *d in row){
        cell = (iContactsGridCell *)initCellImp([Cell new],initCellSel,tmp_frame);

Trying different combinations such as:

IMP initCellImp = [Cell methodForSelector:initCellSel];

Or

cell = initCellImp([iContactsGridCell class],initCellSel,tmp_frame);

Produce the exact same error. So, please tell me, what am I missing, how is this beneficial and is it possible to have an IMP/SEL for a class init method ? Also, would a C function pointer be faster compared to this, or is all the above simply an objective-C function pointer wrapper ? Thank you ! PS: I apologize if those are too many questions at once.

Ælex
  • 14,432
  • 20
  • 88
  • 129
  • 1
    Why not switching to pure C then? – Ramy Al Zuhouri Dec 21 '12 at 18:34
  • 4
    @RamyAlZuhouri Assembly. Or manually compiled raw machine code. (Seriously: this is a micro-optimization.) –  Dec 21 '12 at 18:35
  • For the record, you're supposed to cast the IMP to the correct function prototype before invoking it. – Lily Ballard Dec 21 '12 at 18:35
  • 4
    Also, I find it *highly* doubtful that the message sends in this for loop are a bottleneck, which means you're trying to prematurely optimize something that doesn't need it. – Lily Ballard Dec 21 '12 at 18:36
  • @KevinBallard Running this code in Instruments tells me that [Cell initWithRect]; is a bottleneck. I have tried to optimize the init method, but I was hoping this loop may have some room for improvement as grids may be hundreds and it is a method called often. As I said I have been toying with the idea to see if IMP & SEL would help. So how would I cast IMP in this case ? – Ælex Dec 21 '12 at 18:39
  • @RamyAlZuhouri I have seriously considered it, but an entire project is built upon this, so now its a bit too late. – Ælex Dec 21 '12 at 18:39
  • 2
    @Alex: Having `-[Cell initWithRect:]` be a bottleneck has no relation to the *message send* being a bottleneck. That would show up as `objc_mgsSend()`. – Lily Ballard Dec 21 '12 at 18:42
  • @KevinBallard So theres not much I can do in this loop then? Regardless though, why is the above code not working? Out of curiosity, how do I use IMP correctly for an inherited method, and how do I use it correctly for an init method? – Ælex Dec 21 '12 at 18:46
  • 1
    @Alex: You are building a GridView. to avoid too much drawRect calls, you should have some sort of cell reuse. – vikingosegundo Dec 21 '12 at 19:07
  • @vikingosegundo that was the next step :) – Ælex Dec 21 '12 at 19:19
  • @Alex: that should be the first step. – vikingosegundo Dec 21 '12 at 19:22
  • @vikingosegundo thank you for the tip, but this doesn't answer the question :) – Ælex Dec 21 '12 at 19:53
  • @Alex: Did I post it as an answer? – vikingosegundo Dec 21 '12 at 19:56

2 Answers2

4

In that code, there is no way that objc_msgSend() is incurring a measurable amount of overhead. There is far, far, too much other work going on that is potentially significantly expensive including allocations, view hierarchy manipulation, and other operations.

I.e. moving to direct function calls is a complete waste of your time.

It isn't clear why the code is failing. You would need to check the return value of methodForSelector: to make sure it makes sense. Also, note that calling an IMP in that fashion is incorrect; it needs to be typecast to the specific kind of function pointer that it truly is. I.e. void (*impPtr)(id, SEL, CGRect) ... or whatever.

Finally, making this faster is likely going to involve moving away from using so many views and into something a bit more on-demand. This appears to be a performance issue due to architecture.

bbum
  • 162,346
  • 23
  • 271
  • 359
2
  1. Yes, it avoids objc_msgSend(), which (more importantly) has to performs a lookup at runtime for the right IMP based on the receiver's class and selector, even though in this case, the receiver's class and selector are the same every time, so in theory we could just look it up once and re-use it.

  2. It's hard to know what is wrong from what you've described. One possibility is that the object's class dynamically handle methods, such that it doesn't have a specific implementation for a given selector, but when called, can actually handle it (e.g. with forwardInvocation:). This is used in many places, e.g. proxy objects, or a notification object that sends things it receives to multiple targets. And such a class might say YES to respondsToSelector:, since it does respond to it. I am not sure if this is what is happening here.

  3. [iContactsGridCell methodForSelector:initCellSel] will look up a class method with that name, because you are calling methodForSelector: on iContactsGridCell, a class object. To get an instance method with that name, you would either have to call methodForSelector: on an instance of iContactsGridCell, or use the method instanceMethodForSelector: on the class.

newacct
  • 119,665
  • 29
  • 163
  • 224
  • Thank you for the in-depth answer. I had a feeling that it fails not only due to not providing an instance, but because I was trying a dynamic association with the init method. This could explain why it works with instances but not with init/alloc methods ? – Ælex Dec 22 '12 at 19:03