Resolved Creating selector strings and using performSelector

Discussion in 'iOS Programming' started by MickeyT, Apr 14, 2013.

  1. MickeyT, Apr 14, 2013
    Last edited: Apr 16, 2013

    MickeyT macrumors member

    Joined:
    Apr 26, 2010
    Location:
    Newcastle, United Kingdom
    #1
    I am working through chapter 15 of The Big Nerd Ranch iOS Book 3rd edition, which deals with subclassing table view cells.

    After creating a custom table view cell, a transparent button is added over a thumbnail image on the cell so that the image can be tapped.

    Instead of writing the code that executes following the button tap inside the custom table view cell, it is explained that this code should reside in the table view controller that is controlling the table view the custom cell is part of, so as to follow MVC design.

    So I add an id pointer to the custom cell that points back at the table view controller and another pointer to the table view so that the cell can obtain its own index path:

    Code:
    @interface HomepwnerItemCell : UITableViewCell
    {
        
        
    }
    
    @property (weak, nonatomic) IBOutlet UIImageView *thumbnailView;
    @property (weak, nonatomic) IBOutlet UILabel *nameLabel;
    @property (weak, nonatomic) IBOutlet UILabel *serialNumberLabel;
    @property (weak, nonatomic) IBOutlet UILabel *valueLabel;
    
    @property (weak, nonatomic) id controller;
    @property (weak, nonatomic) UITableView *tableView;
    
    
    - (IBAction)showImage:(id)sender;
    
    @end
    showImage: is connected up to the transparent button (not shown above as an outlet), controller is the table view's delegate view controller and tableView is the table view the cell is in.

    The book then gets me to type out the following code:

    Code:
    - (IBAction)showImage:(id)sender {
        
        NSString *selector = NSStringFromSelector(_cmd);
        selector = [selector stringByAppendingString:@"atIndexPath:"];
        
        SEL newSelector = NSSelectorFromString(selector);
        
        NSIndexPath *indexPath = [[self tableView] indexPathForCell:self];
        /*[[self controller] showImage:sender
                         atIndexPath:indexPath];*/
        
        if (indexPath) {
            
            if ([[self controller] respondsToSelector:newSelector]) {
                
                [[self controller] performSelector:newSelector withObject:sender withObject:indexPath];
                
            }
            
        }
    
    }
    I understand the reason for this to be that if this functionality is implemented this way, then the custom cell can be reused in another project because it checks to see if the necessary method is implemented. What happens when the button is tapped is handled by the delegate view controller, so that code could be different in another project and the custom cell won't care.

    I have to say this all feels a bit complicated. Why could the custom cell not have a set of protocol methods that the table view controller would have to conform to? That way, the cell can just go ahead and call the protocol method showImage:atIndexPath. Better still, this method should be called imageTapped:atIndexPath, so that it is entirely generic. I can't see the worth of the first three lines of code and the performSelector line.

    The book does say that this is implemented in this way so that advantage can be taken of it in later chapters, but for my own piece of mind, would the following be acceptable and offer the same benefits?

    Code:
    @protocol HomepwnerItemCellDelegateProtocol <NSObject>
    
    @required - (void) imageTapped:(id)sender atIndexPath:(NSIndexPath *)ip;
    
    @end
    
    @interface HomepwnerItemCell : UITableViewCell
    {
        
        
    }
    
    @property (weak, nonatomic) IBOutlet UIImageView *thumbnailView;
    @property (weak, nonatomic) IBOutlet UILabel *nameLabel;
    @property (weak, nonatomic) IBOutlet UILabel *serialNumberLabel;
    @property (weak, nonatomic) IBOutlet UILabel *valueLabel;
    
    @property (weak, nonatomic) id<HomepwnerItemCellDelegateProtocol> controller;
    @property (weak, nonatomic) UITableView *tableView;
    
    
    - (IBAction)showImage:(id)sender;
    Code:
    - (IBAction)showImage:(id)sender {
        
        NSIndexPath *indexPath = [[self tableView] indexPathForCell:self];
        
        if (indexPath) {
            
            [[self controller] imageTapped:sender atIndexPath:indexPath];
            
        }
    
    }
     
  2. amorya macrumors 6502

    Joined:
    Jun 17, 2007
    #2
    I'd have thought that was fine. Having the protocol there makes your intent clear, which is the important bit in this situation.

    It's not as defensive at runtime. If someone did hook your item cell up to a controller that didn't implement your protocol, you'd get a crash. But since they'd also get a compiler warning telling them about the unimplemented protocol method, then that's probably not a bad thing.
     
  3. MattInOz macrumors 68030

    MattInOz

    Joined:
    Jan 19, 2006
    Location:
    Sydney
    #3
    The problem is your cell knows nothing of it's indexPath. I mean with cell reuse there are rarely more than 1+ the cells visible in existence. If scrolling really fast the cell could change indexPath several times in a second.

    So if the delegate is set to anything other than the tableViewController then it doesn't work at all. As you cell has no way of knowing what indexPath it's at to tell the delegate.
     
  4. Duncan C macrumors 6502a

    Duncan C

    Joined:
    Jan 21, 2008
    Location:
    Northern Virginia
    #4
    This doesn't make much sense.

    You can indeed make a third object serve as the data source for your table view. It's quite reasonable to make your model object serve as your data source. The data source method tableView:cellForRowAtIndexPath: asks the data source to configure a cell for a given index path. You dequeue a cell, then fill it with model data for that index path and return it. Done.

    ----------


    It looks to me like the code you're posting is using NSStringFromSelector and NSSelectorFromString purely as a teaching exercise to show you the kinds of things you can do with Objective C's runtime binding and introspection. I don't see a particularly good reason to do things this way.

    You CAN use NSStringFromSelector and NSSelectorFromString to construct method names at runtime and then call those methods, but it's very unusual to do so.

    Using respondsToSelector, however, is quite common.

    If you have a protocol that defines some required methods and some optional methods, and a delegate that may or may not implement the optional methods then you use respondsToSelector to check to see if the delegate implements a particular method before calling it.
     
  5. MickeyT thread starter macrumors member

    Joined:
    Apr 26, 2010
    Location:
    Newcastle, United Kingdom
    #5
    Thank you all for your replies.

    You're right. Given that you may not actually want anything to happen when the image is tapped, I should make it optional and then implement repsondsToSelector to check the delegate has implemented the method.

    That's reassuring - I'm pleased the way I thought it should be done is in fact okay and the demonstration of concatenating bits of method names is perhaps just incidental to the exercise.
     
  6. Duncan C macrumors 6502a

    Duncan C

    Joined:
    Jan 21, 2008
    Location:
    Northern Virginia
    #6
    "the demonstration of concatenating bits of method names is perhaps just incidental to the exercise."

    Very well put. I think that's exactly what's going on. I don't have the particular book in question so I can't be sure, but it looks to me like an abstract demo of a technique rather than something specific you will need to do.

    I write a lot of iOS and I can't think of a time when I've sliced-and-diced selector names, built a new method name as a string, converted THAT to a selector, and invoked it like that.
     

Share This Page