Whats new?‎ > ‎Articles‎ > ‎

Introspection, reflection and swizzling in Objective C

Introspection
:
It is the ability to provide information about  objects/classes at runtime . Objective C runtime supports introspection. A small sample of the  type of information provided by the objC  runtime
1) names of methods of class from a class object.
2) information about method arguments
3) implementation(IMP) of individual methods of a class
4) Information about instance variables of a class 

This list is pretty long. Look at the "Objective C runtime reference" section of the Apple documentation for a comprehensive list of information which the objC runtime can provide.

Here is some sample code to test the introspection abilities of the objC runtime. The following code displays the methods supported by a particular class and its super classes including private methods.

#import "ObjC/Runtime.h" //iOS

void classListMethods(Class c)

{

    NSMutableString *classInfo;   

    {

        Class superClass;

        classInfo=[NSMutableString stringWithFormat:@"\nHierarchy: %@ ",c]; 

        superClass=[c superclass];

        while(superClass)

        {

            [classInfo appendFormat:@"-> %@ ",superClass];

            superClass=[superClass superclass];

        }

    }

    

    

    while (c) {

        unsigned int numMethods = 0;

        Method * methods = class_copyMethodList(c, &numMethods);

        [classInfo appendFormat:@"\n\nclass %@ has %d methods\n", c,numMethods];

        for(int i=0;i<numMethods;i++)

        {

            [classInfo appendFormat:@"%0.3d) %s\n",i+1,sel_getName(method_getName(methods[i]))];

        }

        if(methods) free(methods);

        c=[c superclass];

    }


    NSLog(@"%@",classInfo);

    return;

}



A call like classListMethods([UIButton class]) will list all the methods of classes  UIButton -> UIControl->UIView->UIResponder->NSObject.

Reflection: Its the ability to add new classes and to add/modify  interfaces of existing classes. It also includes the ability to modify the relationship between classes  For example, the objC runtime allows new classes to be added, methods to be added to a class and instance variables to be added to a class created at runtime. It also allows the superclass of a class to be replaced by another class.


To demonstrate reflection in objC, lets add  the "description" method to a class.  The "description" method is a part of  the NSObject protocol and it is invoked when a string format specifier  "%@"  pattern is used in some methods. e.g.  NSString :: stringWithFormat. The NSObject class  implements "description" and returns the string  <className:objectPointer> where className is the name of the class to which the objectPointer  refers to.  Our custom description method returns the default NSObject supplied "description" string plus it also lists the instance variables of the class, their values in the specified object and ivar types. 

So to test reflection, create some NSObject derived class and add some instance variables to it. Also add a method called testReflection as shown below.   The test code assumes that the new class has iVars "ivarString"(NSString)  and "ivarNumber" (NSUInteger). You can replace it with anything you want but make sure that the types are supported by myDescriptionIMP.  The implementation currently supports only the types  id and signed/unsigned versions of common scalar types. Adding support to currently unsupported types is mostly trivial. Create an object of the new class and call its testReflection method. You will see reflection in action as your custom method added at runtime is invoked.

NOTE: (null) is displayed for  ivar data type if its value is nil and its an object. "type not supported"  is displayed in place of ivar value if the data type of the ivar is not supported by myDescriptionIMP.

-(void) testReflection
{

    ivarString=@"SomeIvarString";

    ivarNumber=100;

    NSLog(@"Before description: %@",self); // Default NSObject implementation of "description" gets called

    classAddDesciption([self class]);

    NSLog(@"After description: %@",self);  // Now our custom implementation of "description" gets called


}

NSString* myDescriptionIMP(id self, SEL _cmd)

{

    unsigned int ivarCount=0;

    Ivar * ivars=class_copyIvarList([self class], &ivarCount);

    

    Ivar currentIvar; 

    NSMutableString *ivarValues=[NSMutableString stringWithFormat:@"%@\n",self];

    

    if(ivarCount)

    {

        [ivarValues appendString:@"Instance Variables:"];

    for (int i=0; i < ivarCount; i++) {

        currentIvar=ivars[i];

        const char *ivarType=ivar_getTypeEncoding(currentIvar);

        id ivarValue=@"type not supported";

        switch (*ivarType) {

            case '@':

                ivarValue=object_getIvar(self,currentIvar );

                ivarValue=[NSString stringWithFormat:@"%@,%@",ivarValue,[ivarValue class]];                                                

                break;

            case 'i':

            {

                int value=*((int*)((__bridge void*)self + ivar_getOffset(currentIvar)));

                ivarValue=[NSNumber numberWithInt:value];

                ivarValue=[NSString stringWithFormat:@"%@,int",ivarValue];

            }

                break;                                                

            case 's':                

            {

                short value=*((short*)((__bridge void*)self + ivar_getOffset(currentIvar)));

                ivarValue=[NSNumber numberWithShort:value];

                ivarValue=[NSString stringWithFormat:@"%@,short",ivarValue];                

            }

             break;                                

            case 'l':         

            {

                long value=*((long*)((__bridge void*)self + ivar_getOffset(currentIvar)));

                ivarValue=[NSNumber numberWithLong:value];

                ivarValue=[NSString stringWithFormat:@"%@,long",ivarValue];                

            }

                break;     

            case 'q':         

            {

                long long value=*((long long*)((__bridge void*)self + ivar_getOffset(currentIvar)));

                ivarValue=[NSNumber numberWithLong:value];

                ivarValue=[NSString stringWithFormat:@"%@,long long",ivarValue];                

            }

                break;                 

            case 'I':         

            {

                unsigned int value=*((unsigned int*)((__bridge void*)self + ivar_getOffset(currentIvar)));

                ivarValue=[NSNumber numberWithUnsignedInt:value];

                ivarValue=[NSString stringWithFormat:@"%@,unsigned int",ivarValue];                                

            }

                break;          

            case 'S':                

            {

                unsigned short value=*((unsigned short*)((__bridge void*)self + ivar_getOffset(currentIvar)));

                ivarValue=[NSNumber numberWithUnsignedShort:value];

                ivarValue=[NSString stringWithFormat:@"%@,unsigned short",ivarValue];                                                

            }

                break;                                

            case 'L':         

            {

                unsigned long value=*((unsigned long*)((__bridge void*)self + ivar_getOffset(currentIvar)));

                ivarValue=[NSNumber numberWithUnsignedLong:value];

                ivarValue=[NSString stringWithFormat:@"%@,unsigned long",ivarValue];                                                

            }

                break;     

            case 'Q':         

            {

                unsigned long long value=*((unsigned long long*)((__bridge void*)self + ivar_getOffset(currentIvar)));

                ivarValue=[NSNumber numberWithUnsignedLongLong:value];

                ivarValue=[NSString stringWithFormat:@"%@,unsigned long long",ivarValue];                                                

            }

                break;                           

            default:

                break;

        }

        [ivarValues appendFormat:@"\n%s= %@",ivar_getName(currentIvar),ivarValue];

    }

    }

    if(ivars)

        free(ivars);

    return [NSString stringWithString:ivarValues];


}

void classAddDesciption(Class class)

{

    BOOL status=class_addMethod(class, @selector(description),(IMP)myDescriptionIMP , "@@:");

    if(status == NO)

    {

        NSLog(@"classAddDesciption: cannot add method to class %@",class);

    }



Swizzling: The term "Swizzling" in objective C refers to exchanging the implementation of two methods(class or instance) at runtime.  So you apply introspection to access method implementations and reflection to actually exchange the method implementation to achieve swizzling. 

The term "implementation" refers to the actual function pointer to the code(implementation) of the method.  objC runtime maintains a struct called "objc_method" for each method of a class. This struct has the method name, argument and return types of that method and the "implementation" of the method.  So swizzling basically involves exchanging the value the "implementation" field between the objc_method data of two different methods.  It doesn't make sense in most cases to change the "type" field of this structure since that means the caller of the method would also need to change the way the method is called.

NOTE:Calling a method of an object in objectiveC  involves sending a message to that object to execute the specified method with the supplied arguments. This provides the opportunity to reroute the message to another implementation of that method. This is exactly what objC runtime helps us do to realize swizzling.


We can get selectors by using @selector(XnameofselectorX). So how do we turn a selector into a method?  The concept of a instance/class method only exists in the context of a class. So by using a Class object and a selector , we can derive the  METHOD of that selector  corresponding to that specific class. METHOD is a type which hides the underlying obj_method struct from the caller as its considered as an implementation detail.

For example, consider the interface of a class called EZObject

@interface EZObject:NSObject
-(void) method1:(id) argument1;
@end

The following code can be used to get the selector of method1

SEL selMethod1=@selector(method1:);


The following code can be used to get the METHOD of that selector in EZObject.

METHOD methodMethod1=class_getInstanceMethod([EZObject class],selMethod1);

NOTE1: class_getClassMethod is used to get the METHOD of a class method.
NOTE2: class_getInstanceMethod searches for the method in the specified class and all its super classes.


So how would a prototype of a swizzling function look like?  We already know that we need a Class object and two selectors, one is the existing selector and the other is a new selector which will be swizzled with the existing one.  

So a possible prototype can look like this

void methodSwizzle(Class c, SEL swizzledSelector, SEL swizzlingSelector) ;

NOTE1: In practice, you can exchange implementation of methods which exist in totally separate classes. But what if the method implementation refer to ivars which don't exist in the new class they are associated with? If that happens, your app will probably crash or even worse continue running with unpredictable consequences. So do not attempt swizzling if you have not figured out all the runtime implications of switching implementations.  But we focus on the case where both selectors come from the same or super class as this is the most common scenario. 

Now since swizzling applies to both instance method and class method, how does the caller convey that information? We can make a rule that the swizzledSelector is treated as a selector of a instance method of the class "c" . If there is no such instance method, then its treated as a selector to a class method. 

Ok. Now lets go ahead and implement the function


void methodSwizzle(Class c, SEL swizzledSelector, SEL swizzlingSelector)

{

    BOOL isClassMethod=NO;

    Method swizzledMETHOD=class_getInstanceMethod(c, swizzledSelector);

    if(swizzledMETHOD == NULL)

    {

        //Try class method

        swizzledMETHOD=class_getClassMethod(c, swizzledSelector);

        if(swizzledMETHOD == NULL) 

        {

            NSLog(@"Neither class or instance method swizzledSelector=%s exists for %@",(char*)swizzledSelector,c);

            return; 

        }

        isClassMethod=YES;

    }

    Method swizzlingMETHOD;

    if(isClassMethod)

    {

       swizzlingMETHOD=class_getClassMethod(c, swizzlingSelector); 

       if(swizzlingMETHOD == NULL)

       {

           NSLog(@"class method swizzlingSelector= %s does not exist in class %@",(char*)swizzlingSelector,c);

           return;            

       }

    }

    else {

        swizzlingMETHOD=class_getInstanceMethod(c, swizzlingSelector);

        if(swizzlingMETHOD == NULL)

        {

            NSLog(@"instance method swizzlingSelector= %s does not exist in class %@",(char*)swizzlingSelector,c);

            return;            

        }        

    }

    

    method_exchangeImplementations(swizzledMETHOD, swizzlingMETHOD);     

}

The function method_exchangeImplementation does the actual work of exchanging the "implementation" field of the objc_method data associated with the two METHOD's.  


Where can swizzling be used? You may have read in articles about objective C class categories that one disadvantage of using a category method to override an existing method is that there is no way to call the original method so you have to basically  implement the functionality of the original method in the override. This is essentially true from the language syntax point of view. But what if you can make  the call to the original method routed to your own implementation  and from there your code calls original implementation? Swizzling allows you to do such a bait and switch trick. Calling methods of an object in objectiveC  involves sending messages to that object to execute the specified method. This provides the opportunity to reroute the message to another implementation of the method

Lets say you are very much interested in logging all the different types of gesture recognizers being used by the cocoaTouch framework in your app.  Wouldn't it be great if you can override UIView::addGestureRecognizer in your own category method myAddGestureRecognizer and log the presence of gesture recognizers there and still have the ability to call the UIView's implementation of that method?  

The way this trick works is that you exchange the implementation of the UIView::addGestureRecognizer with your own UIView category method called myAddGestureRecognizer and then from within the implementation of myAddGestureRecognizer, call myAddGestureRecognizer! Remember that swizzling switches the implementation(function pointer) but not the name of the method. So when you are calling myAddGestureRecognizer, your message is routed to the original implementation of UIView:addGestureRecognizer.

The UIView category implementation file will look like this


@implementation UIView (Swizzle)


-(void) myAddGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer

{

    [self myAddGestureRecognizer:gestureRecognizer];

    NSLog(@"%@ added %@\n",[self class],gestureRecognizer);

}



@end


In the app delegates  didFinishLaunchingWithOptions method, swizzle the addGestureRecognizer method using the following code

#import "UIView+Swizzle.h"

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

     methodSwizzle([UIView class], 
@selector(addGestureRecognizer:), @selector(myAddGestureRecognizer:));
   .....

   return YES;
}


In my app , i dropped a UITableView in a view and swizzled addGestureRecognizer as described above. This is what i got in the Log window


2012-06-12 14:23:01.324 runtimeLearn[1162:f803] UITableView added <UIScrollViewDelayedTouchesBeganGestureRecognizer: 0x6884980; state = Possible; delaysTouchesBegan = YES; view = <UITableView 0x741a200>; target= <(action=delayed:, target=<UITableView 0x741a200>)>>

2012-06-12 14:23:01.326 runtimeLearn[1162:f803] UITableView added <UIScrollViewPanGestureRecognizer: 0x6a44150; state = Possible; delaysTouchesEnded = NO; view = <UITableView 0x741a200>; target= <(action=handlePan:, target=<UITableView 0x741a200>)>>

2012-06-12 14:23:01.327 runtimeLearn[1162:f803] UITableView added <UISwipeGestureRecognizer: 0x6884d70; state = Possible; view = <UITableView 0x741a200>; target= <(action=handleSwipe:, target=<UITableView 0x741a200>)>; direction = right,left>

2012-06-12 14:23:01.328 runtimeLearn[1162:f803] UITableView added <UIGobblerGestureRecognizer: 0xd134fb0; state = Possible; enabled = NO; view = <UITableView 0x741a200>>

2012-06-12 14:23:01.332 runtimeLearn[1162:f803] UITableViewCellContentView added <UILongPressGestureRecognizer: 0xd236550; state = Possible; view = <UITableViewCellContentView 0xd236860>; target= <(action=_longPressGestureRecognized:, target=<UITableViewCell 0xd236330>)>>

2012-06-12 14:23:01.333 runtimeLearn[1162:f803] UITableViewCellContentView added <UILongPressGestureRecognizer: 0xd236a90; state = Possible; view = <UITableViewCellContentView 0xd237780>; target= <(action=_longPressGestureRecognized:, target=<UITableViewCell 0xd237210>)>>

2012-06-12 14:23:01.466 runtimeLearn[1162:f803] UITableViewCellContentView added <UILongPressGestureRecognizer: 0x6e348e0; state = Possible; view = <UITableViewCellContentView 0x6e34870>; target= <(action=_longPressGestureRecognized:, target=<UITableViewCell 0x6e34740>)>>


AuthorRaj Lokanath


Comments