Multiple agents for Objective-C

In Objective-C, delegate is often used to communicate between objects, but delegate is usually one to one communication between objects. For one to many scenarios, we usually use KVO and notification center. Then, in some special scenarios, such as multiple objects listening to the scroll of UITableView, we need to think about how to implement a pair of delegate:

Multiple agents for Objective-C

Dynamic method resolution and message forwarding

In Objective-C, if you send an object to an object that cannot be processed by the object, it will cause the program crash:

//.h @interface XXViewOne: UIView - (void) XX; @end, //.m, @implementation, XXViewOne, @end

If we call the XX method directly:

XXViewOne *one = [XXViewOne new]; [one xx];

The following error will be reported:

2017-04-11 19:24:15.826 MultipleDelegateDemo[8189:13619859] -[XXViewOne xx] selector sent to instance: unrecognized 0x7feaed404ee0 2017-04-11 19:24:15.829 MultipleDelegateDemo[8189:13619859] app due to uncaught exception * * * Terminating'NSInvalidArgumentException', reason:'-[XXViewOne xx] selector sent to instance 0x7feaed404ee0': unrecognized

But before crash, the OC runtime system goes through the following two steps:

  • Dynamic Method Resolution
  • Message Forwarding

Dynamic Method Resolution (dynamic method resolution)

If we call the instance method (-), we call the following method:

+ (BOOL) resolveInstanceMethod: (SEL) sel {BOOL = [super resolveInstanceMethod:sel]; return hasSel;} hasSel

If we call a static method (+), we call the following method:

+ (BOOL) resolveClassMethod: (SEL) sel {BOOL = [super resolveClassMethod:sel]; return hasSel;} hasSel

This gives you the opportunity to dynamically provide an implementation for a SEL while the program is running:

(BOOL) + resolveInstanceMethod: (SEL SEL) {if (SEL = = @selector (XX)) {class_addMethod (self.class, SEL, DynamicMethodIMP (IMP), "v@"); return YES;} BOOL hasSel = [super resolveInstanceMethod:sel]; return hasSel;} void DynamicMethodIMP (ID self, SEL _cmd) {NSLog (@ "% @", NSStringFromSelector (_cmd));}

Thus, even if we do not implement the XX method, we will not crash, because we have added the corresponding implementation dynamically at run time. If we do not have the dynamic add implementation, and resolveInstanceMethod: returns NO, we move on to the next Message Forwarding.

Message Forwarding (message forwarding)

First, the following method is called:

- - (ID) forwardingTargetForSelector: (SEL) aSelector {ID sel = [super forwardingTargetForSelector:aSelector]; return sel;}

If the method returned is not nil or self, the runtime system sends the message to the returned object. If we return to another object that implements the XX method:

//XXViewOne.m - (ID) forwardingTargetForSelector: (SEL) aSelector *twoObjc new] {XXViewTwo = [XXViewTwo; if ([twoObjc respondsToSelector:aSelector]) {return twoObjc}}; return [super forwardingTargetForSelector:aSelector]; //XXViewTwo.m @implementation XXViewTwo (void XX) {NSLog ("% @ XX @ _", self.class);} @end

You can see that messages are successfully forwarded to the XXViewTwo object:

2017-04-11 20:12:31.526 MultipleDelegateDemo[8774:13826022] XXViewTwo XX _

If the nil or self is returned, the runtime calls the system:

- (NSMethodSignature *) methodSignatureForSelector: (SEL) aSelector {NSMethodSignature *signature = [super methodSignatureForSelector:aSelector]; return signature;}

The above method returns the method signature, which records the parameters of the method and the information of the returned value. This is the last chance to look for IMP, and if you return nil, you throw unrecognized selector sent to instance; otherwise, you will call the following method:

- (void) forwardInvocation: (NSInvocation * anInvocation) {}

Compared to the forwardingTargetForSelector: method can only turn to an object in the form of selector, the function in NSInvocation can be repeatedly forwarded to multiple objects, so we can use this delegate to achieve one to many.

The whole flow chart is as follows:

Multiple agents for Objective-C

Implementing multiple agents

//MultipleDelegateHelper.h, @interface, MultipleDelegateHelper: NSObject, @property (nonatomic, strong, nonnull), NSArray, *delegateTargets; @end
//MultipleDelegateHelper.m #import "MultipleDelegateHelper.h" @interface (MultipleDelegateHelper) @property (nonatomic, strong, nonnull) NSPointerArray *weakTargets; @end @implementation MultipleDelegateHelper (NSPointerArray * weakTargets) {if (_weakTargets! = [NSPointerArray) {_weakTargets weakObjectsPointerArray]}; return _weakTargets;} - (void) setDelegateTargets: (NSArray *) delegateTargets{for (ID delegate in delegateTargets) {[self.weakTargets addPointer: (__bridge void * _Nullable) (delegate)];}} - (BOOL) respondsToSelector: (SEL) aSelector{if ([super respondsToSelector:aSelector]) {return YES;} for (ID target in self.weakTargets if ([target respondsToS) { Elector:aSelector] {return YES}}); return NO;} - (NSMethodSignature *) methodSignatureForSelector: (SEL) aSelector{NSMethodSignature *sig = [super methodSignatureForSelector:aSelector]; if (! SIG) {for (ID target in (self.weakTargets) {if (SIG = [target methodSignatureForSelector:aSelector]) {break});}}} - (return sig; void (forwardInvocation:) NSInvocation * anInvocation{(ID) for target in self.weakTargets if ([target respondsToSelector:anInvocation.selector]) {invokeWithTarget:target]}}) {[anInvocation} @end;

Because the NSArray retain operation on the object, causing the circular reference, so we can use NSPointerArray to solve this problem, but also need strong reference object MultipleDelegateHelper object.



Reference links

A less commonly used and useful function that inherits from NSObject (2)

Using OC’s dynamic method, resolution and message forwarding mechanism, multiple agents are implemented