Anonymous classes in Objective-C
This article is a continuation of “Overriding the implementation of a method. Inspired by Java . ” In the previous note, the decision was too crooked, I didn’t want to leave it like that, and a strong-willed decision was made to bring our undertaking to its logical conclusion and to do everything “as it should”. Although the question of the need for such functionality in Objective-C is still open.
So continue to be like Java
Anonymous (nameless) classes :
Example:
Application area:
Like last time, let's start with a demonstration. Most of the examples from the previous article are also relevant, but with some reservations.
(in the current version, this deprecated method, although it works)
(in the current version, this deprecated method, although it works)
When you call 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 class of the object. We look at the code:
Further, depending on the method called, the behavior is different
1.1 + (id) allocAnonClass: ^ - allocates memory for an anonymous class object
1.2 + (id) newInstAnonClass: ^ - allocates memory for an anonymous class object and sends it an init message
2 - (id) modifyMethods: ^ - unlike the previous two methods, this one works not with classes, but with objects. After generation, it replaces the current class of the object with the anonymous one through object_setClass (self, newAnonClass);
The first two methods allow you to change the implementation of the methods of the object ONLY in 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 work out so simple to create an anonymous class inherited from NSMutableArray, UIButton, etc. And this is not because the implementation is lame, it's just conceived .
On the other hand, sometimes I really want to feel like it. For this, the method from point 2 was left.. When using it, you can change the implementation of the methods of any object, and not just the one you created, for example, id. Well, there will be no problems with the modification of NSMutableArray, since at this stage the object of interest to us will already have the correct class (and not the abstract NSMutableArray). But, again, not everything is so simple. For example, I was not able to create a UIView in this way with custom rendering via the -drawRect: (CGRect) rect method. Most likely this is due to optimization of rendering in iOS, since the drawRect method itself is redefined and is executed upon direct access. But the system ignores him. I will be grateful if I hear an explanation of why this happens.
All methods for creating an anonymous input class receive a block. This block must contain calls to the following C functions:
Attention, these functions can only be called in the blockOv block of these methods, otherwise you will get an error / exception.
Lastly, a comparison of creating anonymous classes with Java:
You can download a test project and familiarize yourself with the implementation here: github.com/Flanker4/MMMutableMethods/
I would also like to mention Sergey Starukhin (pingvin4eg on github).
Thanks to comments from bsideup , firexeland Google realized what anonymous classes are and how they work in Java. I even started to implement it, but suddenly I found a fork of Sergey on a github with a ready generation of classes. I took a waiting position and just watches, expecting that Sergey will either finish what he started or make a pull request. But unfortunately, he stopped making new commits, and the attempt to contact him failed (correction: the github does not allow writing directly to the user, and I just mentioned his nickname in one of the comments, asking me to contact me via the hub). The PM message on Habrahabr did not begin to monitor the comment thread on the github. As it turned out in vain, it was there that Sergey answered, he simply did not have an account on the hub ...). As a result, I made my implementation based on Sergey’s code, with such features as adding methods, lack of generation of heaps of anonymous classes in case of overriding several methods, multithreading, validation, etc.). If someone has an extra invite, and this someone believes that Sergey can be useful to the community, then write me a PM and I will send his contact information to you.
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 continue to be like Java
Theory
Anonymous (nameless) classes :
Declared inside the methods of the main class. They can only be used inside these methods. Unlike local classes, anonymous classes do not have a name. The main requirement for an anonymous class is that it must inherit an existing class or implement an existing interface. They cannot contain the definition (but can inherit) of static fields, methods and classes (except for 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 (override) several methods and create your own methods of the object. This technique is effective when you need to override the method, but there is no need to create a new class due to the narrow scope (or one-time) of the method.
Examples
Like last time, let's start with a demonstration. Most of the examples from the previous article are also relevant, but with some reservations.
1) Delegate example 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 deprecated method, although it works)
NSString *str = [@"Go" overrideMethod:@selector(description) blockImp:^NSString*(){
return @"Stop";
}];
NSLog(@"%@",str); ///log: Stop
3) Logging in case of adding new item only to the container of interest to us
(in the current version, this deprecated method, 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 you call 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 class of the object. 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 anonymous class object
[[UIView allocAnonClass:^{}] initWithFrame:]
//можно заменить на
//создать анонимный класс UIView_anon_0 c переопределенными/добавленными методами
[[UIView_anon_0 alloc] initWithFrame:]
1.2 + (id) newInstAnonClass: ^ - allocates memory for an anonymous class object 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 one works not with classes, but with objects. After generation, it replaces the current class of the object with the anonymous one through object_setClass (self, newAnonClass);
The first two methods allow you to change the implementation of the methods of the object ONLY in 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 work out so simple to create an anonymous class inherited from NSMutableArray, UIButton, etc. And this is not because the implementation is lame, it's just conceived .
On the other hand, sometimes I really want to feel like it. For this, the method from point 2 was left.. When using it, you can change the implementation of the methods of any object, and not just the one you created, for example, id. Well, there will be no problems with the modification of NSMutableArray, since at this stage the object of interest to us will already have the correct class (and not the abstract NSMutableArray). But, again, not everything is so simple. For example, I was not able to create a UIView in this way with custom rendering via the -drawRect: (CGRect) rect method. Most likely this is due to optimization of rendering in iOS, since the drawRect method itself is redefined and is executed upon direct access. But the system ignores him. I will be grateful if I hear an explanation of why this happens.
If someone did 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 input class receive a block. This block must 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, a 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
});
}];
You can download a test project and familiarize yourself with the implementation here: github.com/Flanker4/MMMutableMethods/
Marginal notes
I would also like to mention Sergey Starukhin (pingvin4eg on github).
Thanks to comments from bsideup , firexeland Google realized what anonymous classes are and how they work in Java. I even started to implement it, but suddenly I found a fork of Sergey on a github with a ready generation of classes. I took a waiting position and just watches, expecting that Sergey will either finish what he started or make a pull request. But unfortunately, he stopped making new commits, and the attempt to contact him failed (correction: the github does not allow writing directly to the user, and I just mentioned his nickname in one of the comments, asking me to contact me via the hub). The PM message on Habrahabr did not begin to monitor the comment thread on the github. As it turned out in vain, it was there that Sergey answered, he simply did not have an account on the hub ...). As a result, I made my implementation based on Sergey’s code, with such features as adding methods, lack of generation of heaps of anonymous classes in case of overriding several methods, multithreading, validation, etc.). If someone has an extra invite, and this someone believes that Sergey can be useful to the community, then write me a PM and I will send his contact information to you.