Анонимные классы в Objective-C

от автора

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

Итак, продолжаем быть похожими на 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<UITableViewDelegate> delegate =[[NSObject alloc] init:^{          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) Дурачество

 NSString *str = [@"Go" overrideMethod:@selector(description) blockImp:^NSString*(){       return @"Stop";  }];  NSLog(@"%@",str); ///log: Stop 

3) Логирование в случае добавления новых item только в интересующий нас контейнер

    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]                      objc_msgSendSuper(SUPER(arr), @selector(addObject:),anObject1);                  });         OVERRIDE(@selector(insertObject:atIndex:),                  ^(id arr,id anObject,NSUInteger index)                  {                      NSLog(@"%@",[anObject description]);                      //[super insertObject:anObject atIndex:index];                      objc_msgSendSuper(SUPER(arr), @selector(insertObject:atIndex:),anObject,index);                                        });     }];     [array1 addObject:@"Test"];     [array2 addObject:@"One"];     [array2 addObject:@"Two"];  Log: //Повторение связано с тем, что метод addObject: вызывает insertObject:atIndex  One  One  Two  Two 

Как работает

При вызове соответствующих методов автоматически генерируется новый класс с именем <старый класс>_anon_<номер анонимного класса> (пример NSString_anon_3), унаследованный от класса объекта. Класс текущего объекта изменяется на новый. Смотрим на код:

        //генерируем в runtime новый класс с именем newClassStr, унаследуем его от [self class]         newClass = objc_allocateClassPair([self class], [newClassStr UTF8String], 0);         //изменяем методы у класса newClass: устанавливаем новую IMP или добавляем Method         ....         //регистрируем класс         objc_registerClassPair(newClass);         //изменяем класс текущего объекта         object_setClass(self, newClass); 

И все.

Как использовать

Как и раньше, если нужно просто изменить реализацию метода, то можно использовать:

-(id) addMethod:(SEL)sel fromProtocol:(Protocol *)p isRequired:(BOOL)isReq blockImp:(id)block; -(id) overrideMethod:(SEL)sel blockImp:(id)block; 

Примечание: класс объекта будет заменен на анонимный.
В случае если необходимо переопределить/добавить несколько методов то следует использовать

- (id)modifyMethods:(void(^)())blockOv; 

blockOv — блок, содержащий вызовы следующих 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 методов -modifyMethods:^,-init:^,+new:^.
Так же в категорию NSObject добавлены методы +(id)new:^ и +(id)init, позволяющие сделать создание анонимных классов максимально похожим на Java:

Button.OnClickListener mTakePicSOnClickListener =  new Button.OnClickListener() {         @Override         public void onClick(View v) {               //body         }     }; 
UIOnClickListener *listener =[UIOnClickListener new:^{                     OVERRIDE(@selector(onClick:), ^void(id selfObj,UIButton* sender){                               //body                           });                 }]; 

Скачать тестовый проект и ознакомится с реализацией можно тут: github.com/Flanker4/MMMutableMethods/

Заметки на полях

Отдельно хочу упомянуть о Sergey Starukhin (pingvin4eg на github).
Благодаря комментариям bsideup, firexel и гуглу стало понятно, что такое анонимные классы и как они работают в Java. Я даже принялся за реализацию, но вдруг обнаружил форк Сергея на гитхабе с готовой генерацией классов. Я занял выжидающую позицию и просто следит, ожидая что Сергей либо закончит начатое, либо же сделает pull request. Но к сожалению он перестал делать новые коммиты, а попытка связаться с ним потерпела неудачу (поправка: гитхаб не позволяет написать на прямую user’у и я просто упомянул его ник в одном из комментариев, с просьбой связаться со мной через хабр. Естественно я ждал ЛС сообщение на Habrahabr и не стал мониторить ветку комментариев на гитхабе. Как оказалось зря, именно туда и ответил Сергей, у него попросту не было аккаунта на хабре…). В итоге я сделал свою реализацию, основанную на коде Сергея, с такими фичами как добавление методов, отсутствие генерации кучи анонимных классов в случае переопределения нескольких методов, многопоточность, проверки и пр). Если у кого-то завалялся лишний инвайт и этот кто-то считает что Сергей может быть полезен сообществу, то напишите мне ЛС и я вышлю его контактную информацию Вам.

ссылка на оригинал статьи http://habrahabr.ru/post/170265/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *