Design and implementation of [iOS] multi agent model

Original address: https://www.stephenw.cc/post/5RhkyFSozS

background

Business class applications, local data structure of the shopping cart is relatively complex, and the shopping cart is a shared component of other pages in the data update is difficult to timely synchronized to all references to its instance page, if not pay attention to the design, the shared components can easily cause a very high degree of coupling
the model is a concept that is rarely mentioned in the daily development, but the multi agent has a unique function in these unique scenes.
agent can reduce the coupling degree between the data layer and the view through the middle layer

Avoid using NSNotification to notify data updates in this mode

Alias

This article assumes that the data structure of the shopping cart is called DB
. The view components that need to receive data updates are named in the form of view_count, such as the view_1
programming language: Objective-C

Design

Multi agent means that DB needs to hold the view_1..View_n instance, the data update in turn to notify the results to the proxy method, we need an array to store these instance.

Note that the instance agent needs to be weak reference, otherwise the view release will cause the DB to hold a wild pointer

In order to solve the array of weak reference, I chose the NSPointerArray, Apple documents, such as the introduction:

The NSPointerArray class represents a mutable collection modeled after NSArray, but can also hold nil values. nil values may be inserted or removed and contribute to the object s count. An NSPointerArray object “can also increase and decrease its count directly.

This indicates that NSPointerArray is able to track the object’s memory in the collection, and it is mutable.

In order to reduce the coupling, this method is more general, and I write an independent class to represent the multi-agent model

Class NVMMultidelegate

My namespace is NVM, can be arbitrarily changed, do not care too much

Define interface

#import < Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN; @interface NVMMultiDelegate NSObject @property (nonatomic, readonly) - (void) NSPointerArray *delegates; addDelegate: delegate; (ID) - (void) removeDelegate: (ID) delegate; @end NS_ASSUME_NONNULL_END

External exposure to readonly only delegates, and additions and deletions of delegate method.

Init

- (instancetype) init {if (self = [super init]) {_delegates = [NSPointerArray;} return self;}

When initializing the delegates of Ivars, specify that it follows the pointer in the collection in a weak referenced memory management

Inherited interface

Add object pointer

- (void) addDelegate: (ID) delegate {[_delegates addPointer: (__bridge void*) delegate];}

Unlike NSArray, conceptually, NSPointerArray needs to add a pointer to an object, even though they are both pointers to the operation. So when you add an object, you need to convert it to pointer type, __bridge conversion is very CoreFoundation features

Remove object pointer

- (void) removeDelegate: (ID) delegate index indexOfDelegate:delegate] {NSUInteger = [self; if (index! = NSNotFound) {[_delegates removePointerAtIndex:index]}; [_delegates compact];} - (NSUInteger) indexOfDelegate: (ID delegate) {for (NSUInteger I = 0; I < _delegates.count; I = 1) {if ([_delegates = = pointerAtIndex:i] delegate (__bridge void*) {return I}}); return NSNotFound;}

Removal is a bit of a problem, because NSPointerArray is not a very convenient API like NSArray, which is the result of its own function. So we need to write their own indexOf API, taking into account the multi-agent model of the object will not be very much, we will use the general fast traverse good.

Event Forwarding

In response to the Objc chain, to determine whether an object can perform Selector can use the respondsToSelector method, we first need to override the method, let the caller know the proxy object we can response delegates array it is stored in the object

- (BOOL) respondsToSelector: (SEL) aSelector if ([super respondsToSelector:aSelector]) {{return YES}; for (ID delegate in _delegates (delegate) {if & & [delegate; respondsToSelector:aSelector]) {return YES}}; return NO;}

The caller can start in that response after the call, Objc will get the method signature in the Runtime period, the transcript of this method, we replace the default method signature, signature method to let the caller can get what he wants in an array of delegates objects in the

- (NSMethodSignature) methodSignatureForSelector: (SEL) aSelector *signature methodSignatureForSelector:aSelector] {NSMethodSignature = [super; if (signature) {return signature}; [_delegates compact]; for (ID delegate in _delegates if (delegate) {!}) {continue = [delegate; signature methodSignatureForSelector:aSelector]; if (signature) {break}}}; return signature;

Note that the [_delegates compact], this method can help you get rid of the array inside the wild pointer, avoid you in the fast traverse to get a point to the object does not exist

After getting the method signature, we will call it the same way

- (void) forwardInvocation: (NSInvocation * anInvocation) {SEL selector = [anInvocation selector]; BOOL responded = NO; for (ID delegate in _delegates (delegate) {if & & [delegate; respondsToSelector:selector]) {[anInvocation invokeWithTarget:delegate]; responded = YES;}} (if! Responded) {[self doesNotRecognizeSelector:selector];}}

This can ensure that added in the delegates array can respond to the call to all the proxy object are invoked, if no object can respond to the call, it is certainly will create an unrecognized selector call, this call will generally produce exception, made the application of flash back, if you don’t want to happen in this is not crash get a gray verification class, preferably covering about doesNotRecognizeSelector

Use

When using this class, you only need to declare a property:@property (nonatomic, strong) ID multidelegate

Declaring that it is a ID type just wants build time to be checked by the method, because the multi-agent method does not explicitly declare on this class

For example, you can add the tableview and datasouce of delegate to multidelegate by [multidelegate addDelegate:tableViewDelegate], and then specify

Tableview.delegate = multidelegate; tableview.dataSource = multidelegate

You can, of course this is a demonstration of its usage, the best way is to set up a number of DB pages to multidelegate, DB only need to call in a [multidelegate didUpdateData:data] data update this mode can

Note that the NSPointerArray for tracking memory function, so its performance is not very good, does not recommend the application statement cycle agent added to the same multidelegate, this number may reach thousands, just reflect the number of performance bottleneck is obvious