Calling a non-predefined method in a category after overriding it

There are situations in which it is better to write a category to a class than to use inheritance. Also, sometimes there is a need to use the original class method inside a category, as if we had called super on inheritance. Sometimes they like to ask about such an opportunity at interviews and for some reason are sure that this cannot be done. Google is also silent about this possibility. Who cares - welcome to cat.

Actually it all started with the support of the language with a letter from right to left by an old existing project. The first victim of the translation was the UITableView class . After everything began to work more or less with inheritance, it was decided to apply the category for the subsequent quick addition of support for this type of letter to other projects.

Right-to-left letter support consisted of redeploying some table methods. This article discusses the initWithCoder: method .

First, let's write a category for a UITableView

@interface UITableView (RTL)
@end
@implementation UITableView (RTL)
- (id)initWithCoder:(NSCoder *)aDecoder{
    //self = какая-то инициализация;
    if (self) {
        //Здесь что-то выполнится
    }
    return self;
}
@end

We need to initialize the table with something, the most suitable way is to call the original initWithCoder: method . But as I was told earlier, this is impossible. I decided not to believe this, but to see what was going on in the class using the Method * class_copyMethodList (Class cls, unsigned int * outCount) function . There I saw that the implementation of a predefined method occurs 2 times. Accordingly, if we get the addresses of these implementations and check which one we are in, then the other will be needed for the call. To search for the implementation we need, the following class method was written:

- (objc_methodPointer)methodBySelector:(SEL)sel{
    unsigned int mc = 0;
    Class selfClass = object_getClass(self);
    Method *mList = class_copyMethodList(selfClass, &mc);
    Method currentMethod = class_getInstanceMethod(selfClass, sel);
    NSString *oldName = NSStringFromSelector(sel);
    Method oldMethod = NULL;
    for(int i = 0;i < mc; i++){
        NSString *name = [NSString stringWithUTF8String:sel_getName(method_getName(mList[i]))];
        if ([name isEqualToString:oldName] && currentMethod != mList[i]){
            oldMethod = mList[i];
            break;
        }
    }
    return oldMethod;
}

Everything is simple here, the selector for the search is passed to the function. From it we get the name and current method. Next, we go through the list of methods and look for the implementation we need, checking the name and method mismatch.

Now we have a pointer to the method we need, but we still need to know its implementation. Fortunately, it is stored in the structure we received. But for some reason, Apple decided not to put the complete Method structure in the prep files , although developer.apple.com has a description of it. Therefore, everything will have to be described by ourselves.

struct objc_method {
    SEL method_name;
    char *method_types;
    IMP method_imp;
};
typedef struct objc_method *objc_methodPointer;

Now we have everything to call the original initialization method.

- (id)initWithCoder:(NSCoder *)aDecoder{
    //Получение старого метода при первом вызове
    static objc_methodPointer m = NULL;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        m = [self methodBySelector:_cmd];
    });
    //Проверяем нашли ли мы реализацию
    if (m){
        //Сам вызов старого метода
        self = (void *)m->method_imp(self, m->method_name, aDecoder);
        if (self) {
            //Здесь находится дополнительная собственная инициализация
        }
    }
    return self;
}

After building and starting the application with this code, the basic initialization of the table will be performed. After that, ours will be executed.

This code does not pretend to be original and ideal, this is the first version that will be finalized in the coming days. (Even now I see that the copy of the list of methods is called, but the memory is not freed).

Thanks for attention.

Also popular now: