George L
George L

Reputation: 1738

Method Swizzling not firing correctly?

I am following this article to better understand how method swizzling works. I have my main view controller (this is a new project) like this:

#import "GLViewController.h"
#import <objc/runtime.h>

@implementation UIViewController (Tracking)

+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    Class class = [UIViewController class];

    SEL originalSelector = @selector(viewWillAppear:);
    SEL swizzledSelector = @selector(xxx_viewWillAppear:);

    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

    BOOL didAddMethod =
    class_addMethod(class,
                    originalSelector,
                    method_getImplementation(swizzledMethod),
                    method_getTypeEncoding(swizzledMethod));

    if (didAddMethod) {
        class_replaceMethod(class,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }
});
}

#pragma mark - Method Swizzling

- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}

@end


@implementation GLViewController

-(void)viewWillAppear:(BOOL)animated
{
  NSLog(@"glviewappear");
}

 - (void)viewDidLoad
{
    [super viewDidLoad];

}
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

If I run this, then it prints glviewappear, and if I remove

-(void)viewWillAppear:(BOOL)animated
{
  NSLog(@"glviewappear");
}

then it prints viewWillAppear: <GLViewController: 0x9d11a90>. My project needs to be able to fire on both of these methods. Is there a way to do this?

Upvotes: 0

Views: 574

Answers (1)

Alessandro Orr&#249;
Alessandro Orr&#249;

Reputation: 3513

There is a simple reason. You are not calling the UIViewController implementation since you are not calling [super viewWillAppear:animated].

Just do:

-(void)viewWillAppear:(BOOL)animated
{
  [super viewWillAppear:animated];
  NSLog(@"glviewappear");
}

Here is a more detailed explanation

Each method in an Objective C class is nothing more than a simple function, referenced through a pointer saved in a dispatch table: the keys are the selectors, and their associated values are the pointers to each method implementation (the IMP).

When you swizzle, you just swap two pointers in the dispatch table, in order to swap the referenced functions. Since the dispatch table is attached to the class, swizzling happens only in the class on which you perform it, and not in the subclasses.

In your case, there are 3 different functions in play: UIViewController has the pointers to the following functions in the dispatch table. These functions get swapped at runtime through swizzling.

  • viewWillAppear: (now pointing to xxx_viewWillAppear: implementation)
  • xxx_viewWillAppear: (now pointing to viewWillAppear: implementation)

GLViewController has another pointer, to its own implementation of viewWillAppear:.

If you don't call super, you are not accessing the dispatch table of the UIViewController class, so you are not invoking the implementation at all.

When you delete your viewWillAppear: method, obviously it works, since the super implementation gets automatically called.

Upvotes: 5

Related Questions