开发者

Objective-C forwardInvocation:

I often do something like:

CoolViewController *coolViewController = [[CoolViewController alloc] init];
[self.navigationController pushViewController:coolViewController animated:YES];
[coolViewController release];

How would I, in a category of UINavigationController, override forwardInvocation: so that I could just instead do:

[self.navigationController pushCoolViewControllerAnimated:YES];
  1. Please include the relevant code in your answer, not just an explanation. Thank you!

  2. Feel free to comment on whether this is good practice. I'm also asking this for educational purposes, but it seems to me that in this case, the simplification in code may outw开发者_StackOverflow中文版eight the unnoticeable (correct?) cost in processing time & memory usage. Also, I come from a Ruby background and love to use dynamic programming to simplify things, e.g., dynamic finders (e.g., find_by_name) in Rails.

  3. Bonus points if you could implement pushCoolViewControllerAnimated:withBlock and invoke the block after initializing the view controller, allowing me to set certain instance variables on the view controller created.

UPDATE: I just remembered that ARC is coming soon. So this specific example may not be so helpful then, but still a great exercise/example that could be used in other cases, e.g., dynamic finders for Core Data & passing a block to configure the NSFetchRequest.


Use the dynamic method resolution mechanism described in the Objective-C Runtime Programming Guide, specifically, +[NSObject resolveInstanceMethod:]:

@implementation UINavigationController (FWD)
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSString *name = NSStringFromSelector(sel);
    NSString *prefix = @"push";
    NSString *suffix = @"Animated:";
    if ([name hasPrefix:prefix] && [name hasSuffix:suffix]) {
        NSRange classNameRange = {[prefix length],
            [name length] - [prefix length] - [suffix length]}
        NSString *className = [name substringWithRange:classNameRange];
        Class cls = NSClassFromString(className);
        if (cls) {
            IMP imp = imp_implementationWithBlock(
            ^(id me, BOOL animated) {
                id vc = [[cls alloc] init];
                [me pushViewController:vc animated:animated];
                [vc release];
            });
            class_addMethod(cls, sel, imp, "v@:c");
            return YES;
        }
    }
    return [super resolveInstanceMethod:sel];
}
@end

Of course, if UINavigationController already uses +resolveInstanceMethod:, you've now broken it. Doing this in a subclass of UINavigationController, or using method swizzling to enable invoking the original implementation, would solve that problem.

The version accepting a post-creation block is a straightforward extension (change the block parameters, change the type encoding, change the selector name pattern and how you extract the intended class name).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜