Intercepting Objective-C Calls

Continuing on from my previous post about capturing OpenGL calls in C, I now needed some method to intercept the creation and destruction of contexts. While this is easy for the CGL library since it worked the same as intercepting the OpenGL calls, this wasn’t as easy as I thought it would be for NSOpenGLContext.
Screen Shot 2014-04-27 at 17.56.11 1
I eventually found a C methods for retrieving and replacing objective-c functions with C functions, I think they were class_replaceMethod and class_getInstanceMethod. However this was flawed, every so often I would run the application and permission errors from the operating system, crashing the program.

And even when they did work, I was unable to replace the dealloc method without getting a recursive feedback.

But while reading about these methods, I came by another method that involves not replacing a call, but exchanging it for another. So I wrote a class extension for NSOpenGLContext, with my own variations of the methods I wanted to intercept. Luckily, dealloc just worked for the extension and did not required swizzling.

@implementation NSOpenGLContext (glDebug)
 
//
- (id)init_glDebug_WithFormat:(NSOpenGLPixelFormat *)format shareContext:(NSOpenGLContext *)share
{
	NSLog(@"initWithFormat:shareContext: intercepted\n");
	return [self init_glDebug_WithFormat:format shareContext:share];
}
 
//
- (id)init_glDebug_WithCGLContextObj:(void *)context
{
	NSLog(@"initWithCGLContextObj intercepted\n");
	return [self init_glDebug_WithCGLContextObj:context];
}
 
- (void)dealloc
{
	NSLog(@"Dealloc intercepted\n");
}
 
@end

While this fixed the recursive problem, I still occasionally received permission errors. And while googling for the answer, I found that objective-c classes have a static +(void)load method that the code was suppose to be put in, not the library constructor.

@implementation NSOpenGLContext (glDebug)
 
//
+ (void)load
{
	[NSOpenGLContext swizzleMethod1:@selector(initWithCGLContextObj:)
						withMethod2:@selector(init_glDebug_WithCGLContextObj:)];
 
	[NSOpenGLContext swizzleMethod1:@selector(initWithFormat:shareContext:)
						withMethod2:@selector(init_glDebug_WithFormat:shareContext:)];
}
 
//
+ (void)swizzleMethod1:(SEL)inOriginal withMethod2:(SEL)inSwizzled
{
	Method originalMethod = class_getInstanceMethod([NSOpenGLContext class], inOriginal);
	Method swizzledMethod = class_getInstanceMethod([NSOpenGLContext class], inSwizzled);
	method_exchangeImplementations(originalMethod, swizzledMethod);
}
 
//
+ (void)swizzleStaticMethod1:(SEL)inOriginal withMethod2:(SEL)inSwizzled
{
	Method originalMethod = class_getClassMethod([NSOpenGLContext class], inOriginal);
	Method swizzledMethod = class_getClassMethod([NSOpenGLContext class], inSwizzled);
	method_exchangeImplementations(originalMethod, swizzledMethod);
}
 
@end

This worked great and I haven’t had any troubles since.