Multiple delegation

    In Cocoa, the delegation pattern is very popular . The standard way to implement this pattern is to add a weak property to the delegate that stores the link to the delegate.

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

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


    An example is a bit drawn out, 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 given number of characters.

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

    And here a problem arises. For PTCVerifiableTextField to implement custom behavior, it needs to be a delegate of its superclass (UITextField). But if you do this, you won’t 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 obtain the problem: to make the property
    @property(nonatomic, assign) id delegate  
    it was possible to assign several objects.


    The solution suggests itself. We need 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.
    That is, you need a container that proxies requests to the elements stored in it.

    This solution has one big drawback - if the delegated function returns a value, then you need to somehow determine the result of calling which delegate to consider as 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 the method on the object. Instead, we send him a message. Thus, Message Forwarding refers to a redirect of a message to another object, i.e. its proxying.

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

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

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

    2. Called +(BOOL)resolveInstanceMethod:(SEL)aSELor+(BOOL)resolveClassMethod:(SEL)nameif helmet message to class. This method makes it possible to add the desired selector dynamically. If YES is returned, the runtime again tries to get the IMP and call the method. Otherwise, move on.

    This method is also called when +(BOOL)respondsToSelector:(SEL)aSelectorand +(BOOL)instancesRespondToSelector:(SEL)aSelectorif 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!

    An example of dynamically adding a method:
    + (BOOL)resolveInstanceMethod:(SEL)aSEL
        if (aSEL == @selector(resolveThisMethodDynamically))
              class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
              return YES;
        return [super resolveInstanceMethod:aSel];

    3. The so-called Fast Forwarding is performed. Namely, the method is called. -(id)forwardingTargetForSelector:(SEL)aSelector
    This method returns an object that should be used instead of the current one. In general, a very convenient thing to simulate multiple inheritance. It is fast, 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, there will be an infinite loop. In practice, an infinite loop does not occur: apparently, the runtime has been amended.

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

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

    You can create NSMethodSignature in the following ways:
    • use the method +(NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector
      Note if the class just claims to implement the protocol ( use the Inside method calls [[self class] instanceMethodSignatureForSelector: ...] use the method belonging to the NSMethodSignature class and assemble the NSMethodSignature yourself@interface MyClass : NSObject ), то этот методу уже вернет не nil, а сигнатуру.

      -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

      +(NSMethodSignature *)signatureWithObjCTypes:(const char *)types

    • .

    Also popular now: