📜 ⬆️ ⬇️

Multiple Delegate

Cocoa is a very popular delegation pattern . The standard way to implement this pattern is to add weak properties to the delegator that stores a reference to the delegate.

Delegation has many different uses. For example, the implementation of a behavior in another class without inheritance. Another delegation is used as a method of transmitting notifications. For example, a UITextField calls the textFieldDidEndEditing: method on the delegate, which informs it that the editing is complete, etc.

Now imagine the task: it is necessary to make the delegator send messages not to one delegate, but to several delegates, and the delegation is implemented by the standard method through a property.

Example


The example is a bit attracted, but still.
')
You need to make a custom UITextField that will check the text entered into it, and if the text is invalid, then the control will change color. Plus, you need to make sure that the user can enter only a specified number of characters.

Ie want something like this:
@protocol PTCTextValidator <NSObject> - (BOOL)textIsValid:(NSString *)text; - (BOOL)textLengthIsValid:(NSString *)text; @end @interface PTCVerifiableTextField : UITextField @property (nonatomic, weak) IBOutlet id<PTCTextValidator> validator; @property (nonatomic, strong) UIColor *validTextColor UI_APPEARANCE_SELECTOR; @property (nonatomic, strong) UIColor *invalidTextColor UI_APPEARANCE_SELECTOR; @property (nonatomic, readonly) BOOL isValid; @end 

And here there is a problem. In order for PTCVerifiableTextField to implement custom behavior, it is necessary that it be a delegate of its superclass (UITextField). But if you do this, you will not be able to touch the delegate property from the outside.
Those. The code below will break the internal logic of PTCVerifiableTextField
 PTCVerifiableTextField *textField = [PTCVerifiableTextField alloc] initWIthFrame:CGrectMake(0, 0, 100 20)]; textField.delegate = self; [self.view addSubview:textField]; 

Thus, we get the task: to make the property
 @property(nonatomic, assign) id<UITextFieldDelegate> delegate 
it was possible to assign several objects.

Decision


The decision suggests itself. It is necessary to make a container that will store several delegates and messages that will not come to it, but to objects that are stored in the container.
Ie need a container that proxies requests to the elements stored in it.

Such a solution has one big drawback - if the delegated function returns a value, then you need to somehow determine the result of the call of which delegate to consider the return value.

So, before you proxy something, you need to figure out what Message Forwarding and NSProxy are.

Message forwarding


Objective-C works with messages. We do not call a method on the object. Instead, we send him a message. Thus, by Message Forwarding is meant a message redirect to another object, i.e. his proxying.

It is important to note that sending a message to the object to which it does not respond gives an error. However, before the error is generated, runtime will give the object another chance to process the message.

Let's take a look at what happens when a message is sent to the object.

1. If an object implements a method, that is, you can get an IMP (for example, using method_getImplementation(class_getInstanceMethod(subclass, aSelecor)) ), then runtime calls the method. Otherwise, go ahead.

2. Called +(BOOL)resolveInstanceMethod:(SEL)aSEL or +(BOOL)resolveClassMethod:(SEL)name if you send a message to the class. This method makes it possible to add the desired selector dynamically. If YES is returned, then runtime again tries to get an IMP and call a method. Otherwise, go ahead.

This method is also called when +(BOOL)respondsToSelector:(SEL)aSelector and +(BOOL)instancesRespondToSelector:(SEL)aSelector if the selector is not implemented. Moreover, this method is called only once for each selector, there will be no second chance to add a method!

Example of dynamic method addition:
 + (BOOL)resolveInstanceMethod:(SEL)aSEL { if (aSEL == @selector(resolveThisMethodDynamically)) { class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:"); return YES; } return [super resolveInstanceMethod:aSel]; } 


3. Running the so-called Fast Forwarding. Namely, the method -(id)forwardingTargetForSelector:(SEL)aSelector invoked -(id)forwardingTargetForSelector:(SEL)aSelector
This method returns an object to use instead of the current one. In general, a very handy thing to simulate multiple inheritance. Fast it, because at this stage you can do forwarding without creating NSInvoacation.

For the object returned by this method, all steps will be repeated. According to the documentation, if you return self, then there will be an infinite loop. In practice, an infinite loop does not arise: apparently, a runtime amendment was made.

4. The two previous steps are forward optimization. After them, runtime creates NSInvocation.
Creating an NSInvocation runtime looks like this:
 NSMethodSignature *sig = ... NSInvocation* inv = [NSInvocation invocationWithMethodSignature:sig]; [inv setSelector:selector]; 

That is, to create NSInvocation, runtime needs to get the method signature (NSMethodSignature). Therefore, the object is called - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector . If the method instead of NSMethodSignature returns nil, then the runtime will call the object -(void)doesNotRecognizeSelector:(SEL)aSelector , i.e. crash will happen.

You can create NSMethodSignature in the following ways:

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


All Articles