Objective C. Practice. Events and Dead Objects

    Many people probably know that when working with property change events using key-value observing, there is a very convenient mechanism to prevent the appearance of “dead” objects in the application, which are call recipients. In fact, the very first dead object "knocks down" the application, when it receives an event, this is natural, since the object no longer exists and it will no longer be possible to call any methods.

    The search for such objects could be difficult if it weren’t for a wonderful thing called debugging NSKVODeallocateBreak, which allows you to interrupt the execution of the application at the moment when the object signed for events is destroyed in order to track its life time and remove the problem.

    In the process of working on the class that I used for events, I wanted to create a similar mechanism, since errors in the event logic are rather difficult to predict, and insurance will not hurt here.

    This article is intended for developers with experience working with the platform and knowing how the life cycle of an object is determined. If you have certain gaps in this area (and I have repeatedly met even experienced developers who don’t know how the link counter works and who don’t know what it’s unfolding into @synthesize), then you can read my old article on the study of this issue . I ask the others to the table.

    So what do we want? We want as soon as the object that is subscribed to the event throughthe mechanism described in the previous release was destroyed; we received information about this in the debugger.

    What is needed for this? The obvious solution is to somehow intercept the call deallocof the signatory object and inform the developer about this when subscribing to the event . However, this is bad luck - it is impossible to intercept with regular means dealloc(or, at least, I did not find such a way).

    Fortunately, Objective-C makes it possible to get around this limitation pretty nicely with its runtime. I spied the idea of ​​this solution in a note by a certain codeshaker , and it turned out to be incredibly beautiful and elegant. Remaking it to my needs, I got 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 message handler method with our dealloc_override handler, 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 object-signer with the object-event. Use a static dictionary? No, that means increasing the number of problems. Fortunately, runtime will help us here too - it turns out that a dictionary for extension properties is already associated with any object in Objective-C and we just need to use it.

    Define some unique identifier for our property.

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


    Now bind it to the class NSObjectusing 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 type object NSObjecthas a virtual property attachedEventHandlerthat we will use to store the information we need.

    We expand the class code AWEventHandlersListso that it writes an object of the bound event into this field and add a method that returns YESif 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 implement what it was up to - a check at the time the object is deallocated.

    -(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 the object was destroyed while it was subscribed to the event, an exception will be thrown. The best part is that the exception is thrown at 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 a little easier.

    Also popular now: