#import <objc/runtime.h>

by Louis Tur


Have I mentioned that I love the esoteric? Because I most definitely do.

For a bit of context, I've been playing around with information logging via Apple System Logger API and CocoaLumberJack. And they're pretty neat, despite my wholelly uninterested state generally regarding logging. The overarching goal has been to create a system that allows users to configure different logging levels to provide information to a support team.

Fortunately, my shortsightedness became my greatest ally when trying to get a UIButton to perform a separate class's class method. I've done this many times before to perform a selector that belonged to the class in which the button was being created in, but I hadn't done it using a selector from another class's class method. For example:

UIButton * infoLogsButton = [self createLogButtonForLog:@"Info Logs"  
                                                  color:[UIColor srl_hipsterMaroon]
                                           andAddToView:self.containerView];
[infoLogsButton addTarget:self action:@selector(createLogLevelButtons) forControlEvents:UIControlEventTouchUpInside];

This creates an instance of a UIButton using an instance method I created (createLogButtonForLog:color:andAddToView:) and then assigns that button the action of createLogLevelButtons when pressed. Though I have a class, SRLogConfigManager that has the following method signature +[SRLogConfigManager changeToInfoLogLevel]. So, a straight-forward approach to performing the action I need with this button is to write:

[infoLogsButton addTarget:[SRLogConfigManager class] action:@selector(changeToInfoLogLevel) forControlEvents:UIControlEventTouchUpInside];

Though for some unfortunate reason, fatigue caused me to ignore the addTarget parameter as what would be the invoker of the appropriate selector. And so, the rabbit hole of documentation began.

The ObjC Runtime Environment

I had read about Swizzling some time ago after a different rabbit hole, and the topic itself really intrigued me even though I had no applicable uses for it at the moment. Though the prospect of using it (finally!) clouded my judgement in the most beautiful way possible.

The situation that I faced was that I was receiving compiler errors when trying to call the class's method like:

[infoLogsButton addTarget:self action:@selector([SRLogConfigManager changeToInfoLogLevel]) forControlEvents:UIControlEventTouchUpInside];

It seemed reasonable to me that I'd receive the correct SEL by calling the proper method signature. But after trying to adjust that action: parameter in every creative way possible, I decided it was just not going to happen.

So then I decided to do a search in Dash for anything related to @selector or SEL. I somehow came across NSMethodSignature, NSInvocation and then tried to play around with sel_registerName, but was finding my efforts unsuccessful. Though, sel_registerName lives in the Objective-C Runtime Reference Guide along with many, many other very interesting C-structs and functions. After considering the advice from the Associated Objects article of NSHipster, I made the plunge by calling #import <objc/runtime.h>. With that import, I was now able to access all of the runtime library Objc has to offer.

After some trial and error, I found that the following suited my needs as far as getting the right @selector:

Class logManagerClass = object_getClass([SRLogConfigManager class]);  
Method logManagerMethod = class_getClassMethod(logManagerClass, @selector(changeToDebugLogs));  

The first line gets a runtime reference to the SRLogConfigManager class and stores it in logManagerClass which is a typedef struct of type objc_class *Class (Ok, so technically it's really storing the location in memory of where the class is being defined -- and this is worth calling out given how the Objc runtime works). And the second line gets a pointer reference to the data structure that describes a given class method for the given class (Method class_getClassMethod ( Class cls, SEL name).

Living without Compiler Checks

With the following line of code in place:

[infoLogsButton addTarget:self action:method_getName(logManagerMethod) forControlEvents:UIControlEventTouchUpInside];

I throw an NSLog statement in my [SRLogManager changeToInfoLogLevel] method to make sure stuff is happening. Im elated as my project builds and begins running. I go to click the button in question, eagerly anticpating my NSLog statement to get outputted, and.... crash.

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[SRLoggingAdjustmentViewController changeToDebugLogs]: unrecognized selector sent to instance 0x7fc32480b7b0 

But wait... SRLoggingAdjustmentViewController doesn't implement the changeToDebugLogs method, the SRLogManager does...

So, lesson learned: using runtime functions bypasses the compiler error checking that would tell me there is no public method called changeToDebugLogs in my SRLoggingAdjustmentViewController class. And this is fairly obvious, given that the compiler is unable to check for these errors since the struct references I'm creating are determined at runtime.

Fortunately, at this point the fix becomes obvious: I need to change the target of the action to [SRLogManager class]. I re-run, and get my expected NSLog'd output. Though, I'm now aware of my roundabout means to this end... and I replace method_getName(logManagerMethod) with simply @selector(changeToDebugLogs). Autocompletion is telling me that with my target correctly set to SRLogManager, the changeToDebugLogs is now a valid method. I run my project one last time, click on my button and...

2015-03-02 00:07:11.948 SRDeviceLogs[15827:642853] Log levels are now set to debug!  

...REDEMPTION.

So, there was absolutely no need to use runtime functions for my goal of setting a class method of a button's target. But the spark of curiousity led me to a very different, and fascinating topic.

And for posterity, here's a small sample of the code I've described.

Louis Tur

"How" has been the single most used word in my literary arsenal for as long as I can remember. I've never really been satisfied knowing that something works, but only by knowing how it works.

Read more from this author