Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Неизвестные классы в Objective-C

Anna | 2.07.2014 | нет комментариев

Даная статья является продолжением «Переопределение реализации способа. Вдохновленный 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 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:^ — в различии от предыдущих 2-х способов данный работает не с классами, а с объектами. Позже генерации он заменяет нынешний класс объекта на неизвестный через 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).
Вследствие комментариям bsideupfirexel и гуглу стало ясно, что такое неизвестные классы и как они работают в Java. Я даже принялся за реализацию, но внезапно нашел форк Сергея на гитхабе с готовой генерацией классов. Я занял ждущую позицию и легко следит, ждя что Сергей либо завершит начатое, либо же сделает pull request. Но к сожалению он перестал делать новые коммиты, а попытка связаться с ним потерпела неудачу (поправка: гитхаб не разрешает написать на прямую user’у и я легко упомянул его ник в одном из комментариев, с просьбой связаться со мною через прогр. Безусловно я ожидал ЛС сообщение на Habrahabr и не стал мониторить ветку комментариев на гитхабе. Как оказалось напрасно, именно туда и ответил Сергей, у него просто не было аккаунта на прогре…). В выводе я сделал свою реализацию, основанную на коде Сергея, с такими фичами как добавление способов, неимение генерации кучи неизвестных классов в случае переопределения нескольких способов, многопоточность, проверки и пр). Если у кого-то завалялся ненужный инвайт и данный кто-то считает что Сергей может быть пригоден сообществу, то напишите мне ЛС и я вышлю его контактную информацию Вам.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB