📜 ⬆️ ⬇️

Objective C. Practice. Developments

Event-oriented logic in Objective-C rests on three pillars — protocols, a notification center, and key-value observing. Traditionally, protocols are used to extend the functionality of the base classes without inheritance, key-value observing - for interaction between the visual and logical part of the application, and the notification center - for handling user events.

Naturally, all this beauty can be safely used to build complex applications. There is no real need to invent your own bikes, of course. However, I, as a person who came to the development of Objective C applications from the .NET world, found it very unpleasant that the notification center, which I planned to use for events, breaks the application stack, writing the event to a queue in the UI thread, and the protocols in the classic view not too convenient, therefore, for convenience, I decided to build myself a mechanism that would be much more similar to what we used to do in the world of .NET. Thus was born the idea of ​​implementing the model of multiple signatories through a special class called AWHandlersList.

This article is designed for programmers who have some experience in creating applications on Objective-C and have written similar bikes, or solved similar problems in standard ways. This option is not a silver bullet, but it proved to be a convenient mechanism that minimizes writing code for cracking sets of events with different interfaces and parameters.
')
The idea of ​​a class is quite simple - it contains a list of signers, each element of which consists of two components — target and selector.

What is this bike made for? It seemed to me that it is more convenient than all the standard models presented for some logic related to the transmission of events. Maybe he will help someone to make life easier.

In .NET, the usual event logic model offers a delegate with two parameters - sender of type object and args of type inherited from EventArgs. In order not to break your brain, do the same. To begin with, let's define an empty EventArgs class from which all event arguments will be inherited.

@interface AWEventArgs : NSObject @end 


Now we define a class that will contain a pair of “target object and a method being called”, adding some debugging information to it at the same time so that the logic of events can be easily debugged in the future.

 @interface AWEventHandler : NSObject { @private NSString *_description; } @property (nonatomic, assign) id target; @property (nonatomic, assign) SEL method; +(AWEventHandler *)handlerWithTarget:(id)target method:(SEL)method; @end @implementation AWEventHandler @synthesize method, target; -(id)initWithTarget:(id)t method:(SEL)m; { self = [super init]; if(self) { target = t; method = m; _description = [[NSString alloc] initWithFormat:@"EventHandler, Target=%@, Method=%@", NSStringFromClass([target class]), NSStringFromSelector(method)]; } return self; } -(NSString *)description { return _description; } -(void)dealloc { [_description release]; [super dealloc]; } +(AWEventHandler *)handlerWithTarget:(id)target method:(SEL)method { AWEventHandler *handler = [[[AWEventHandler alloc] initWithTarget:target method:method] autorelease]; return handler; } @end 


As you can see, both target and method are essentially weak links. This is quite natural - weak links are used everywhere in the Objective-C world to avoid circular references and to allow objects to be automatically freed. Unfortunately, this leads to the fact that with careless coding, “dead” pointers to objects that drop an application appear everywhere, so I’ll show a little further one beautiful mechanism that allows you to prevent and eliminate their occurrence.

Now, finally, let's move on to our main class - the list of signatories. There are non-trivial moments in the code, but they are solved by reading the documentation, and if there is no desire to understand the question, then you can simply use it, the code is completely working and removed from the “combat” project.

 @interface AWEventHandlersList : NSObject { NSMutableArray *_handlers; } @property (nonatomic, copy) NSString *name; -(void)addReceiver:(id)receiver delegate:(SEL)delegate; -(void)removeReceiver:(id)receiver delegate:(SEL)delegate; -(void)clearReceivers; -(void)invoke; -(void)invokeWithSender:(id)sender; -(void)invokeWithSender:(id)sender args:(AWEventArgs *)event; @property (nonatomic, retain) NSRunLoop *runLoop; @end 


In brief, I will explain why we need fields of this class.

The first is name. I prefer to name events so that you can see in the logs exactly what event was triggered. Usually, I use the name of the class as the name of the event, along with the name of the class being called in it to raise the method. This is a convenient practice, as it allows you not to scour convulsively around the stack in search of who threw the event, but simply to see this value in the debug console.

The addReceiver and removeRecevier logical - they accept an object and a selector that will accept calls later.

The invoke methods should throw an event, passing it to the signed objects for processing. They are given in three variants - in order not to pass empty nil values ​​in the event that there is no need for any event parameters.

The clearReceivers method clearReceivers internal, it is better to define it in an anonymous section, since the calling code should not be able to unsubscribe other objects from events, but historically it has been rendered to the interface. It is easy to fix, if you think it is wrong.

Finally, the runLoop property runLoop necessary if you are going to make certain events be tied to a specific thread. For example, this is necessary if some code in the worker thread must update the visual part of the application, or vice versa - from the UI thread should be access to any worker thread synchronized through the message queue, that is, if there is a need to throw events and process them in different streams.

Implementing a class is ideologically trivial, but it does require some understanding of how selectors work. Difficult moments I will clarify in the comments in the code itself.

 @implementation AWEventHandlersList @synthesize runLoop = _runLoop; @synthesize name = _name; -(id)init { self = [super init]; if(!self) return nil; _handlers = [[NSMutableArray alloc] init]; return self; } -(void)addReceiver:(id)receiver delegate:(SEL)delegate { /*    ,   ,          ,  *    .      */ [self removeReceiver:receiver delegate:delegate]; [_handlers addObject:[AWEventHandler handlerWithTarget:receiver method:delegate]]; } -(void)removeReceiver:(id)receiver delegate:(SEL)delegate { /*      ,    ,      * (NSLock),     ,            , *      ,         NSLock */ for(AWEventHandler *handler in [[_handlers copy] autorelease]) if(handler.method == delegate && handler.target == receiver) [_handlers removeObject:handler]; } -(void)clearReceivers { [_handlers removeAllObjects]; } -(void)invoke { [self invokeWithSender:nil args:nil]; } -(void)invokeWithSender:(id)sender { [self invokeWithSender:sender args:nil]; } -(void)invokeWithSender:(id)sender args:(AWEventArgs *)event { [self invokeWithSender:sender args:event runLoop:_runLoop]; } -(void)invokeWithSender:(id)sender args:(AWEventArgs *)event runLoop:(NSRunLoop *)runLoop { /*  ,         ,   null    *     */ if(!runLoop) runLoop = [NSRunLoop currentRunLoop]; NSUInteger order = 1; NSArray *handlersCopy = [NSArray arrayWithArray:_handlers]; for(AWEventHandler *handler in handlersCopy) if(runLoop == [NSRunLoop currentRunLoop]) [self internalInvoke:[NSArray arrayWithObjects:handler, sender == nil ? [NSNull null] : sender, event == nil ? [NSNull null] : event, nil]]; else [runLoop performSelector:@selector(internalInvoke:) target:self argument:[NSArray arrayWithObjects:handler, sender == nil ? [NSNull null] : sender, event == nil ? [NSNull null] : event, nil] order:order++ modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]]; } /*            performSelector:target:argument:order:modes: */ -(void)internalInvoke:(NSArray *)data { AWEventHandler *handler = [data objectAtIndex:0]; id sender = [data objectAtIndex:1]; if(sender == [NSNull null]) sender = nil; id args = [data objectAtIndex:2]; if(args == [NSNull null]) args = nil; /*               */ NSMethodSignature *mSig = [handler.target methodSignatureForSelector:handler.method]; if([mSig numberOfArguments] == 2) [handler.target performSelector:handler.method]; else if([mSig numberOfArguments] == 3) [handler.target performSelector:handler.method withObject:sender]; else if ([mSig numberOfArguments] == 4) [handler.target performSelector:handler.method withObject:sender withObject:args]; else @throw [NSException exceptionWithName:@"Invalid selector type" reason:@"This type of selector is not supported" userInfo:nil]; } -(void)dealloc { self.name = nil; [self clearReceivers]; [_handlers release]; [super dealloc]; } @end 


Now we define a pair of auxiliary macros that will allow us to embed the logic of working with events into the class in literally two lines.

 #define DEFINE_EVENT(eventName) \ -(void)add ## eventName ## Handler:(id)receiver action:(SEL)action; \ -(void)remove ## eventName ## Handler:(id)receiver action:(SEL)action #define DEFINE_EVENT_IMPL(eventName, innerVariable) \ -(void)add ## eventName ## Handler:(id)receiver action:(SEL)action \ { \ [innerVariable addReceiver:receiver delegate:action]; \ } \ \ -(void)remove ## eventName ## Handler:(id)receiver action:(SEL)action \ { \ [innerVariable removeReceiver:receiver delegate:action] ; \ } \ 


Now in order to create an event in a class, you need to define an internal list variable:

 AWEventHandlersList *_handlers; 


Define an event in the interface

 DEFINE_EVENT(Event); 


And link the list with the event

 DEFINE_EVENT_IMPL(Event, _handlers) 


Two methods are automatically added to the class — addEventHandler:action: and removeEventHandler:action: and you can trigger an event through the invoke methods of the _handlers object.

Of course, don't forget that the _handlers object needs to be initialized in the constructor.

 _handlers = [AWEventHandlersList new]; 


And destroy in the object's destructor

 [_handlers release]; 


In the second part of the article, I will explain what problems the use of this approach leads to and how to cope with the difficulties of “dead” references that arise in any more or less voluminous application as a result of our own mistakes.

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


All Articles