Даная статья является продолжением «Переопределение реализации метода. Вдохновленный Java'ой». В предыдущей заметке было предложено слишком уж кривое решение, оставлять в таком виде не хотелось и было принято волевое решение довести свое начинание до логического завершения и сделать все «как надо». Хотя вопрос нужности такого функционала в Objective-C до сих пор открыт.
Итак, продолжаем быть похожими на Java

Анонимные (безымянные) классы:
Пример:
Область применения:
Как и в прошлый раз, начнем с демонстрации. Большинство примеров из предыдущей статьи так же актуальны, но с некоторыми оговорками
(в текущей версии данный метод deprecated, хоть и работает)
(в текущей версии данный метод deprecated, хоть и работает)
При вызове соответствующих методов автоматически генерируется новый класс с именем <старый класс>_anon_<номер анонимного класса> (пример NSString_anon_3), унаследованный от класса объекта. Смотрим на код:
Далее, в зависимости от вызванного метода, поведение отличается
1.1 + (id) allocAnonClass: ^ — выделяет память под объект анонимного класса
1.2 + (id) newInstAnonClass:^ — выделяет память под объект анонимного класса и посылает ему сообщение init
2 — (id) modifyMethods:^ — в отличии от предыдущих двух методов этот работает не с классами, а с объектами. После генерации он заменяет текущий класс объекта на анонимный через object_setClass(self, newAnonClass);
Первые два метода позволяют изменить реализацию методов объекта ТОЛЬКО в случае его создания (вызова alloc). Это правильная реализация, НО она не всегда может быть применена. К примеру через эти способы не получится так просто создать анонимный класс, унаследованный от NSMutableArray, UIButton и т.п. И это не потому что хромает реализация, просто так задумано.
С другой стороны иногда очень хочется хочется. Для этого был оставлен метод из пункта 2. При его использовании можно изменить реализацию методов любого объекта, а не только созданного Вами, к примеру id. Ну и с модификацией NSMutableArray не будет проблем, так как на этом этапе у интересующего нас объекта будет уже установлен корректный класс (а не абстрактный NSMutableArray). Но, опять же, не все так просто. К примеру мне не удалось таким образом создать UIView с кастомной отрисовкой через метод -drawRect:(CGRect)rect Скорей всего это связано с оптимизацией отрисовки в iOS, так как сам метод drawRect переопределяется и при прямом обращении выполняется. Но вот система его игнорирует. Я буду признателен, если услышу объяснение почему так происходит.
Все методы создания анонимного класса на вход получают блок. Этот блок должен содержать вызовы следующих C-функций:
Внимание, данные функции могут быть вызваны только в блоке blockOv этих методов иначе вы получите ошибку/исключение.
На последок сравнение создание анонимных классов с Java:
Скачать тестовый проект и ознакомится с реализацией можно тут: github.com/Flanker4/MMMutableMethods/
Отдельно хочу упомянуть о Sergey Starukhin (pingvin4eg на github).
Благодаря комментариям bsideup, firexel и гуглу стало понятно, что такое анонимные классы и как они работают в Java. Я даже принялся за реализацию, но вдруг обнаружил форк Сергея на гитхабе с готовой генерацией классов. Я занял выжидающую позицию и просто следит, ожидая что Сергей либо закончит начатое, либо же сделает pull request. Но к сожалению он перестал делать новые коммиты, а попытка связаться с ним потерпела неудачу (поправка: гитхаб не позволяет написать на прямую user'у и я просто упомянул его ник в одном из комментариев, с просьбой связаться со мной через хабр. Естественно я ждал ЛС сообщение на Habrahabr и не стал мониторить ветку комментариев на гитхабе. Как оказалось зря, именно туда и ответил Сергей, у него попросту не было аккаунта на хабре...). В итоге я сделал свою реализацию, основанную на коде Сергея, с такими фичами как добавление методов, отсутствие генерации кучи анонимных классов в случае переопределения нескольких методов, многопоточность, проверки и пр). Если у кого-то завалялся лишний инвайт и этот кто-то считает что Сергей может быть полезен сообществу, то напишите мне ЛС и я вышлю его контактную информацию Вам.
Объяснение
Статья была опубликована вчера, но я нашел способ сделать еще более правильную реализацию, ага, поэтому и скрыл ее на время
Итак, продолжаем быть похожими на Java

Теория
Анонимные (безымянные) классы:
Декларируются внутри методов основного класса. Могут быть использованы только внутри этих методов. В отличие от локальных классов, анонимные классы не имеют названия. Главное требование к анонимному классу — он должен наследовать существующий класс или реализовывать существующий интерфейс. Не могут содержать определение (но могут наследовать) статических полей, методов и классов (кроме констант).
Пример:
class OuterClass { public OuterClass() {} void methodWithLocalClass (final int interval) { // При определении анонимного класса применен полиморфизм - переменная listener // содержит экземпляр анонимного класса, реализующего существующий // интерфейс ActionListener ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent event) { System.out.println("Эта строка выводится на экран каждые " + interval + " секунд"); } }; Timer t = new Timer(interval, listener); // Объект анонимного класса использован внутри метода t.start(); } }
Область применения:
Анонимные классы эффективно используются, как правило, для реализации (переопределения) нескольких методов и создания собственных методов объекта. Этот прием эффективен в случае, когда необходимо переопределение метода, но создавать новый класс нет необходимости из-за узкой области (или одноразового) применения метода.
Примеры
Как и в прошлый раз, начнем с демонстрации. Большинство примеров из предыдущей статьи так же актуальны, но с некоторыми оговорками
1) Пример с делегатом для UITableView
id delegate =[NSObject newInstAnonClass:^{ ADD_METHOD(@selector(tableView:didSelectRowAtIndexPath:), @protocol(UITableViewDelegate), NO, ^(id selfObj,UITableView* tv,NSIndexPath* path) { NSLog(@"did select row %i",path.row); }); ADD_METHOD(@selector(tableView:willSelectRowAtIndexPath:), @protocol(UITableViewDelegate), NO, ^NSIndexPath*(id selfObj,UITableView* tv,NSIndexPath* path) { NSLog(@"will select row %i",path.row); return path; }); }]; self.tableView.delegate=delegate;
2) Дурачество
(в текущей версии данный метод deprecated, хоть и работает)
NSString *str = [@"Go" overrideMethod:@selector(description) blockImp:^NSString*(){ return @"Stop"; }]; NSLog(@"%@",str); ///log: Stop
3) Логирование в случае добавления новых item только в интересующий нас контейнер
(в текущей версии данный метод deprecated, хоть и работает)
NSMutableArray * array1 = [NSMutableArray arrayWithCapacity:10]; NSMutableArray * array2= [NSMutableArray arrayWithCapacity:10]; [array2 modifyMethods:^{ OVERRIDE(@selector(addObject:), ^(id arr,id anObject1) { NSLog(@"%@",[anObject1 description]); //[super addObject:anObject1] struct objc_super superInfo = {arr,[arr superclass]}; objc_msgSendSuper(&superInfo, @selector(addObject:),anObject1); }); OVERRIDE(@selector(insertObject:atIndex:), ^(id arr,id anObject,NSUInteger index) { NSLog(@"%@",[anObject description]); //[super insertObject:anObject atIndex:index]; struct objc_super superInfo = {arr,[arr superclass]}; objc_msgSendSuper(&superInfo, @selector(insertObject:atIndex:),anObject,index); }); }]; [array1 addObject:@"Test"]; [array2 addObject:@"One"]; [array2 addObject:@"Two"]; Log: //Повторение связано с тем, что метод addObject: вызывает insertObject:atIndex One One Two Two
4) UIView с кастомной отрисовкой
UIView *tmpView = [[UIView allocAnonClass:^{ OVERRIDE(@selector(drawRect:), ^void(UIView *vie,CGRect rect){ CGContextRef context = UIGraphicsGetCurrentContext(); ... }); }] initWithFrame:CGRectMake(0, 0, 320, 480)];
Как работает
При вызове соответствующих методов автоматически генерируется новый класс с именем <старый класс>_anon_<номер анонимного класса> (пример NSString_anon_3), унаследованный от класса объекта. Смотрим на код:
//генерируем в runtime новый класс с именем newClassStr, унаследуем его от [self class] newClass = objc_allocateClassPair([self class], [newClassStr UTF8String], 0); //изменяем методы у класса newClass: устанавливаем новую IMP или добавляем Method .... //регистрируем класс objc_registerClassPair(newClass);
Далее, в зависимости от вызванного метода, поведение отличается
1.1 + (id) allocAnonClass: ^ — выделяет память под объект анонимного класса
[[UIView allocAnonClass:^{}] initWithFrame:] //можно заменить на //создать анонимный класс UIView_anon_0 c переопределенными/добавленными методами [[UIView_anon_0 alloc] initWithFrame:]
1.2 + (id) newInstAnonClass:^ — выделяет память под объект анонимного класса и посылает ему сообщение init
[UIView newInstAnonClass:^{}] //можно заменить на //создать анонимный класс UIView_anon_0 c переопределенными/добавленными методами [UIView_anon_0 new]; //[[UIView_anon_0 alloc] init];
2 — (id) modifyMethods:^ — в отличии от предыдущих двух методов этот работает не с классами, а с объектами. После генерации он заменяет текущий класс объекта на анонимный через object_setClass(self, newAnonClass);
Первые два метода позволяют изменить реализацию методов объекта ТОЛЬКО в случае его создания (вызова alloc). Это правильная реализация, НО она не всегда может быть применена. К примеру через эти способы не получится так просто создать анонимный класс, унаследованный от NSMutableArray, UIButton и т.п. И это не потому что хромает реализация, просто так задумано.
С другой стороны иногда очень хочется хочется. Для этого был оставлен метод из пункта 2. При его использовании можно изменить реализацию методов любого объекта, а не только созданного Вами, к примеру id. Ну и с модификацией NSMutableArray не будет проблем, так как на этом этапе у интересующего нас объекта будет уже установлен корректный класс (а не абстрактный NSMutableArray). Но, опять же, не все так просто. К примеру мне не удалось таким образом создать UIView с кастомной отрисовкой через метод -drawRect:(CGRect)rect Скорей всего это связано с оптимизацией отрисовки в iOS, так как сам метод drawRect переопределяется и при прямом обращении выполняется. Но вот система его игнорирует. Я буду признателен, если услышу объяснение почему так происходит.
Если кто не понял о чем я вообще
UIView *tmpView=[[UIView alloc] initWithFrame:CGRectMake(0,0, 320, 480)]; [tmpView overrideMethod:@selector(drawRect:) blockImp:^void(UIView* selfView,CGRect rect){ NSLog(@"draw"); }]; [self.view addSubview:tmpView]; //drawRect у tmpView не будет вызван системой, т.е. мы не получим то, чего хотели; setNeedsDisplay не поможет... [tmpView drawRect:CGRectZero]; //будет вызван UIView * tmpView2 =[[[tmpView class] alloc] initWithFrame:CGRectMake(0, 100, 320, 380)]; [self.view addSubview:tmpView2]; //а вот у tmpView2 drawRect переопределенный уже будет вызван
Как использовать
Все методы создания анонимного класса на вход получают блок. Этот блок должен содержать вызовы следующих C-функций:
//Позволяет переопределить метод sel c новой реализацией blockIMP BOOL OVERRIDE(SEL sel,id blockIMP); //Позволяет добавить новый метод sel, с описанием сигнатуры в протоколе p и реализацией blockIMP BOOL ADD_METHOD(SEL sel,Protocol *p, BOOL isReq, id blockIMP); //Позволяет добавить новый метод sel, с описанием сигнатуры в классе с и реализацией blockIMP BOOL ADD_METHOD_C(SEL sel,Class c,id blockIMP);
Внимание, данные функции могут быть вызваны только в блоке blockOv этих методов иначе вы получите ошибку/исключение.
На последок сравнение создание анонимных классов с Java:
Button.OnClickListener mTakePicSOnClickListener = new Button.OnClickListener() { @Override public void onClick(View v) { //body } };
UIOnClickListener *listener =[UIOnClickListener newInstAnonClass:^{ OVERRIDE(@selector(onClick:), ^void(id selfObj,UIButton* sender){ //body }); }];
Скачать тестовый проект и ознакомится с реализацией можно тут: github.com/Flanker4/MMMutableMethods/
Заметки на полях
Отдельно хочу упомянуть о Sergey Starukhin (pingvin4eg на github).
Благодаря комментариям bsideup, firexel и гуглу стало понятно, что такое анонимные классы и как они работают в Java. Я даже принялся за реализацию, но вдруг обнаружил форк Сергея на гитхабе с готовой генерацией классов. Я занял выжидающую позицию и просто следит, ожидая что Сергей либо закончит начатое, либо же сделает pull request. Но к сожалению он перестал делать новые коммиты, а попытка связаться с ним потерпела неудачу (поправка: гитхаб не позволяет написать на прямую user'у и я просто упомянул его ник в одном из комментариев, с просьбой связаться со мной через хабр. Естественно я ждал ЛС сообщение на Habrahabr и не стал мониторить ветку комментариев на гитхабе. Как оказалось зря, именно туда и ответил Сергей, у него попросту не было аккаунта на хабре...). В итоге я сделал свою реализацию, основанную на коде Сергея, с такими фичами как добавление методов, отсутствие генерации кучи анонимных классов в случае переопределения нескольких методов, многопоточность, проверки и пр). Если у кого-то завалялся лишний инвайт и этот кто-то считает что Сергей может быть полезен сообществу, то напишите мне ЛС и я вышлю его контактную информацию Вам.
