Бывают такие ситуации, при которых лучше написать категорию к классу, чем использовать наследование. Так же иногда существует потребность использовать оригинальный метод класса внутри категории, как если бы мы вызвали super при наследовании. О такой возможности иногда любят спрашивать на собеседованиях и почему-то уверены, что этого нельзя сделать. Google так же умалчивает о такой возможности. Кому интересно — добро пожаловать под кат.
Собственно всё началось с поддержки языка с письмом справа налево старым существующим проектом. Первой жертвой перевода стал класс UITableView. После того, как всё более или менее стало работать с наследованием, было принято решение применить категорию для последующего быстрого добавления поддержки такого рода письма в другие проекты.
Поддержка письма справа налево заключалась переопеределении некоторых методов таблицы. В данной статье рассмотрим метод initWithCoder:.
Для начала напишем категорию для UITableView
Нам нужно чем-то проинициализировать таблицу, самый подходящий способ — это вызвать оригинальный метод initWithCoder:. Но как мне ранее говорили, это сделать невозможно. Я решил этому не верить, а посмотреть, что творится в классе, используя функцию Method *class_copyMethodList(Class cls, unsigned int *outCount). Там я увидел, что реализация предопределенного метода встречается 2 раза. Соответственно, если мы получим адреса этих реализаций и проверим, в какой мы находимся, то другая будет нужной нам для вызова. Для поиска нужной нам реализации Был написан следующий метод класса:
Тут всё просто, в функцию передаётся селектор для поиска. Из него мы получаем имя и текущий метод. Далее мы проходим по списку методов и ищем нужную нам реализацию, проверяя соответствие имени и несоответствие методов.
Теперь у нас есть указатель на нужный нам метод, но нам ещё надо знать его реализацию. К счастью она хранится в полученной нами структуре. Но почему-то в Apple решили не помещать в заготовочные файлы полную структуру Method, хотя на developer.apple.com есть её описание. По этому всё прийдётся описать самим.
Теперь у нас всё есть для вызова оригинального метода инициализации.
После сборки и запуска приложения с данным кодом выполнится базовая инициализация таблицы. После этого выполнится наша.
Данный код не претендует на оригинальность и идеальность, это первая версия, которая в ближайшие дни ещё будет доработана. (Даже сейчас вижу, что вызывается копирование списка методов, но не освобождается память).
Спасибо за внимание.
Собственно всё началось с поддержки языка с письмом справа налево старым существующим проектом. Первой жертвой перевода стал класс UITableView. После того, как всё более или менее стало работать с наследованием, было принято решение применить категорию для последующего быстрого добавления поддержки такого рода письма в другие проекты.
Поддержка письма справа налево заключалась переопеределении некоторых методов таблицы. В данной статье рассмотрим метод initWithCoder:.
Для начала напишем категорию для UITableView
@interface UITableView (RTL) @end @implementation UITableView (RTL) - (id)initWithCoder:(NSCoder *)aDecoder{ //self = какая-то инициализация; if (self) { //Здесь что-то выполнится } return self; } @end
Нам нужно чем-то проинициализировать таблицу, самый подходящий способ — это вызвать оригинальный метод initWithCoder:. Но как мне ранее говорили, это сделать невозможно. Я решил этому не верить, а посмотреть, что творится в классе, используя функцию Method *class_copyMethodList(Class cls, unsigned int *outCount). Там я увидел, что реализация предопределенного метода встречается 2 раза. Соответственно, если мы получим адреса этих реализаций и проверим, в какой мы находимся, то другая будет нужной нам для вызова. Для поиска нужной нам реализации Был написан следующий метод класса:
- (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; }
Тут всё просто, в функцию передаётся селектор для поиска. Из него мы получаем имя и текущий метод. Далее мы проходим по списку методов и ищем нужную нам реализацию, проверяя соответствие имени и несоответствие методов.
Теперь у нас есть указатель на нужный нам метод, но нам ещё надо знать его реализацию. К счастью она хранится в полученной нами структуре. Но почему-то в Apple решили не помещать в заготовочные файлы полную структуру Method, хотя на developer.apple.com есть её описание. По этому всё прийдётся описать самим.
struct objc_method { SEL method_name; char *method_types; IMP method_imp; }; typedef struct objc_method *objc_methodPointer;
Теперь у нас всё есть для вызова оригинального метода инициализации.
- (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; }
После сборки и запуска приложения с данным кодом выполнится базовая инициализация таблицы. После этого выполнится наша.
Данный код не претендует на оригинальность и идеальность, это первая версия, которая в ближайшие дни ещё будет доработана. (Даже сейчас вижу, что вызывается копирование списка методов, но не освобождается память).
Спасибо за внимание.
