📜 ⬆️ ⬇️

Objective C. Practice. Events and "dead" objects

Many probably know that when working with property change events using key-value observing, there is a very convenient mechanism that prevents the application of “meter” objects that are call recipients. In fact, the first dead object “knocks” the application, when an event arrives at it, this is natural, since the object no longer exists and no methods can be called for it.

The search for such objects could be difficult if it were not for the remarkable thing in the debugging thing called NSKVODeallocateBreak , which allows you to interrupt the execution of the application at the moment when the object subscribed to the events is destroyed in order to track its lifetime and remove the problem.

In the process of working on the class that I used for the events, I wanted to create a similar mechanism, since it is rather difficult to predict errors in the event logic, and insurance would not hurt here.
')
This article is designed for developers who have experience with the platform and who know how the life cycle of an object is determined. If you have certain gaps in this area (and I have even repeatedly met even experienced developers who do not know how the link counter works and doesn’t represent what @synthesize unfolds), then you can read my old article on this issue. . The rest I ask to the table.

So what do we want? We want that as soon as an object that is subscribed to an event through the mechanism described in the previous release is destroyed, we get information about this in the debugger.

What is needed for this? The obvious solution is to somehow intercept the dealloc call of the signatory object when subscribing to an event and report it to the developer. However, here's a bad luck - it is impossible to intercept dealloc standard means (or, at least, I have not found such a method).

Fortunately, Objective C makes it quite beautiful to get around this limitation with its runtime. I spotted the idea of ​​this solution in a note by a certain codeshaker , and it turned out to be incredibly beautiful and elegant. Redoing it to fit my needs, I received the following code:

 @interface NSObject (NSObjectDeallocInfo) -(void)dealloc_override; @end @implementation NSObject (NSObjectDeallocInfo) +(void)load { method_exchangeImplementations(class_getInstanceMethod(self, @selector(dealloc)), class_getInstanceMethod(self, @selector(dealloc_override))); } -(void)dealloc_override { [self dealloc_override]; } @end 


In fact, this code replaces the dealloc handler method with our dealloc handler dealloc, and the seemingly recursive call [self dealloc_override] actually now leads to the standard method.

The second question is where to store information about the connection of our signatory object with an event object. Use static dictionary? No, it means increasing the number of problems. Fortunately, runtime will help us here too - it turns out that a dictionary for properties-extensions is already associated with any object in Objective-C and we just need to use it.

We define some unique identifier of our property.

 static void* AW_EVENTHANDLER_KEY = (void *)0x2781; 


Now we tie it to the NSObject class, using the same category that we used to override the method.

 @interface NSObject (NSObjectDeallocInfo) @property (nonatomic, assign) AWEventHandlersList *attachedEventHandler; -(void)dealloc_override; @end @implementation NSObject (NSObjectDeallocInfo) +(void)load { method_exchangeImplementations(class_getInstanceMethod(self, @selector(dealloc)), class_getInstanceMethod(self, @selector(dealloc_override))); } -(void)dealloc_override { [self dealloc_override]; } -(AWEventHandlersList *)attachedEventHandler { return (AWEventHandlersList *)objc_getAssociatedObject(self, AW_EVENTHANDLER_KEY); } -(void)setAttachedEventHandler:(AWEventHandlersList *)attachedEventHandler { objc_setAssociatedObject(self, AW_EVENTHANDLER_KEY, attachedEventHandler, OBJC_ASSOCIATION_ASSIGN); } @end 


Now any object of type NSObject has a virtual property attachedEventHandler , which we will use to store the information we need.

We extend the code of the AWEventHandlersList class AWEventHandlersList that it writes the object of the AWEventHandlersList in this field and add a method that returns YES if the object is subscribed to the event.

 -(void)addReceiver:(id)receiver delegate:(SEL)delegate { [self removeReceiver:receiver delegate:delegate]; [receiver setAttachedEventHandler:self]; [_handlers addObject:[AWEventHandler handlerWithTarget:receiver method:delegate]]; } -(void)removeReceiver:(id)receiver delegate:(SEL)delegate { [receiver setAttachedEventHandler:nil]; for(AWEventHandler *handler in [[_handlers copy] autorelease]) if(handler.method == delegate && handler.target == receiver) [_handlers removeObject:handler]; } -(BOOL)isReceiverInList:(id)receiver { for(AWEventHandler *handler in _handlers) if(handler.target == receiver) return YES; return NO; } -(void)clearReceivers { for(AWEventHandler *handler in _handlers) [handler.target setAttachedEventHandler:nil]; [_handlers removeAllObjects]; } 


Now it becomes quite simple to realize what it was for the sake of - checking for the moment of the deployment of the object.

 -(void)dealloc_override { AWEventHandlersList *handler = self.attachedEventHandler; if(handler) if([handler isReceiverInList:self]) { NSLog(@"Event handler (%@) target is released while subscribed", handler.name); [NSException raise:@"E_HANDLERRELEASED" format:@"Event handler (%@) target is released while subscribed", handler.name]; } [self dealloc_override]; } 


Now, if an object was destroyed while it is subscribed to an event, an exception will be thrown. Best of all, an exception is thrown in the place where the object's destructor is triggered, which allows you to see the error stack in crash reports.

Thus, in just 15 minutes we have made our debugging life somewhat easier.

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


All Articles