📜 ⬆️ ⬇️

Anonymous Classes in Objective-C

This article is a continuation of the “Overriding method implementation. Inspired by Java . In the previous article, it was suggested that the decision was too crooked, I didn’t want to leave it in this form and a willful decision was made to bring the undertaking to its logical conclusion and to do everything “as it should.” Although the question of the need for such a functional in Objective-C is still open.
Explanation
The article was published yesterday, but I found a way to make an even more correct implementation, yeah, that's why I hid it for a while.


So, we continue to be similar to Java.


Theory


Anonymous (unnamed) classes :
Declared inside the methods of the main class. Can only be used inside these methods. Unlike local classes, anonymous classes have no name. The main requirement for an anonymous class is that it must inherit an existing class or implement an existing interface. Cannot contain definition (but can inherit) static fields, methods and classes (except constants).

Example:
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(); } } 

Application area:
Anonymous classes are effectively used, as a rule, to implement (redefine) several methods and create their own object methods. This technique is effective when a method override is necessary, but creating a new class is not necessary because of the narrow scope (or one-time) application of the method.


Examples


Like last time, let's start with a demonstration. Most of the examples from the previous article are just as relevant, but with some reservations.
1) Example with delegate for 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) tomfoolery

(in the current version this method is deprecated, although it works)
  NSString *str = [@"Go" overrideMethod:@selector(description) blockImp:^NSString*(){ return @"Stop"; }]; NSLog(@"%@",str); ///log: Stop 

3) Logging in case of adding a new item only to the container of interest

(in the current version this method is deprecated, although it works)
  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 with custom rendering

  UIView *tmpView = [[UIView allocAnonClass:^{ OVERRIDE(@selector(drawRect:), ^void(UIView *vie,CGRect rect){ CGContextRef context = UIGraphicsGetCurrentContext(); ... }); }] initWithFrame:CGRectMake(0, 0, 320, 480)]; 

')

How does it work


When calling the appropriate methods, a new class is automatically generated with the name <old class> _anon_ <anonymous class number> (example NSString_anon_3 ), inherited from the object class. We look at the code:
  //  runtime     newClassStr,    [self class] newClass = objc_allocateClassPair([self class], [newClassStr UTF8String], 0); //    newClass:   IMP   Method .... //  objc_registerClassPair(newClass); 

Further, depending on the method called, the behavior is different
1.1 + (id) allocAnonClass: ^ - allocates memory for an object of an anonymous class
 [[UIView allocAnonClass:^{}] initWithFrame:] //   //   UIView_anon_0 c /  [[UIView_anon_0 alloc] initWithFrame:] 

1.2 + (id) newInstAnonClass: ^ - allocates memory for an object of an anonymous class and sends it an init message
 [UIView newInstAnonClass:^{}] //   //   UIView_anon_0 c /  [UIView_anon_0 new]; //[[UIView_anon_0 alloc] init]; 

2 - (id) modifyMethods: ^ - unlike the previous two methods, this works not with classes, but with objects. After generation, it replaces the current class of the object with an anonymous one through object_setClass (self, newAnonClass);
The first two methods allow you to change the implementation of the object's methods ONLY in the case of its creation (call alloc). This is the correct implementation, but it can not always be applied. For example, through these methods it will not be possible so simply to create an anonymous class inherited from NSMutableArray, UIButton, etc. And this is not because implementation is lame, it’s just conceived .
On the other hand, sometimes you really want to. For this was left the method of paragraph 2 . When using it, you can change the implementation of the methods of any object, not just the id created by you, for example, id. Well, with the modification of NSMutableArray there will be no problems, since at this stage the object of interest to us will already have the correct class installed (and not the abstract NSMutableArray). But, again, not so simple. For example, I was not able to create a UIView with custom drawing via the -drawRect method: (CGRect) rect This is most likely due to the optimization of drawing in iOS, since the drawRect method itself is overridden and executed during direct access. But the system ignores it. I would appreciate it if I heard an explanation of why this is happening.
If someone does not understand what I mean
  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     



How to use


All methods for creating an anonymous class receive a block as input. This block should contain calls to the following C functions:
 //   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); 

Attention, these functions can only be called in the blockOv block of these methods, otherwise you will get an error / exception.
Lastly, the comparison of creating anonymous classes with 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 }); }]; 

Download the test project and see the implementation here: github.com/Flanker4/MMMutableMethods/

Margin notes


Separately, I want to mention Sergey Starukhin (pingvin4eg on github).
Thanks to bsideup , firexel and google comments, it became clear what anonymous classes are and how they work in Java. I even began to implement, but suddenly I found Sergei’s fork on the github with the ready generation of classes. I took a waiting position and just watched, expecting that Sergey would either finish the job, or else pull request. But unfortunately he stopped making new commits, and the attempt to contact him failed (amendment: the github does not allow writing to the user directly, and I just mentioned his nickname in one of the comments, asking me to contact me through Habr. Naturally, I waited The message on Habrahabr didn’t monitor the comment thread on the githaba. As it turned out in vain, it was there that Sergei answered, he simply did not have an account on the habr ...). As a result, I made my implementation based on Sergey's code, with such features as adding methods, no heap generation of anonymous classes in case of overriding several methods, multithreading, validation, etc.). If someone has an extra invite, and this someone thinks that Sergey can be useful to the community, write me a PM and I will send his contact information to you.

Source: https://habr.com/ru/post/170265/


All Articles