213 lines
8.7 KiB
Objective-C
213 lines
8.7 KiB
Objective-C
//
|
|
// MLHandler.m
|
|
// monalxmpp
|
|
//
|
|
// Created by Thilo Molitor on 29.10.20.
|
|
// Copyright © 2020 Monal.im. All rights reserved.
|
|
//
|
|
|
|
#import <Foundation/Foundation.h>
|
|
#import "MLConstants.h"
|
|
#import "MLHandler.h"
|
|
#import "HelperTools.h"
|
|
|
|
#define HANDLER_VERSION 1
|
|
|
|
@interface MLHandler ()
|
|
{
|
|
NSMutableDictionary* _internalData;
|
|
BOOL _invalidated;
|
|
}
|
|
@end
|
|
|
|
NSString* type_to_classname(NSString* type)
|
|
{
|
|
return [type componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"*< "]][0];
|
|
}
|
|
|
|
@implementation MLHandler
|
|
|
|
-(instancetype) init
|
|
{
|
|
self = [super init];
|
|
return self;
|
|
}
|
|
|
|
-(instancetype) initWithDelegate:(id) delegate handlerName:(NSString*) handlerName andBoundArguments:(NSDictionary*) args
|
|
{
|
|
self = [self init];
|
|
return [self INTERNALinitWithDelegate:delegate handlerName:handlerName invalidationHandlerName:nil andBoundArguments:args];
|
|
}
|
|
|
|
-(instancetype) initWithDelegate:(id) delegate handlerName:(NSString*) handlerName invalidationHandlerName:(NSString*) invalidationHandlerName andBoundArguments:(NSDictionary*) args
|
|
{
|
|
self = [self init];
|
|
return [self INTERNALinitWithDelegate:delegate handlerName:handlerName invalidationHandlerName:invalidationHandlerName andBoundArguments:args];
|
|
}
|
|
|
|
-(instancetype) INTERNALinitWithDelegate:(id) delegate handlerName:(NSString*) handlerName invalidationHandlerName:(NSString* _Nullable) invalidationHandlerName andBoundArguments:(NSDictionary* _Nullable) args
|
|
{
|
|
if(![delegate respondsToSelector:[self handlerNameToSelector:handlerName]])
|
|
@throw [NSException exceptionWithName:@"RuntimeException" reason:[NSString stringWithFormat:@"Class '%@' does not provide handler implementation '%@'!", NSStringFromClass(delegate), handlerName] userInfo:@{
|
|
@"delegate": NSStringFromClass(delegate),
|
|
@"handlerSelector": NSStringFromSelector([self handlerNameToSelector:handlerName]),
|
|
}];
|
|
if(invalidationHandlerName && ![delegate respondsToSelector:[self handlerNameToSelector:invalidationHandlerName]])
|
|
@throw [NSException exceptionWithName:@"RuntimeException" reason:[NSString stringWithFormat:@"Class '%@' does not provide invalidation implementation '%@'!", NSStringFromClass(delegate), invalidationHandlerName] userInfo:@{
|
|
@"delegate": NSStringFromClass(delegate),
|
|
@"handlerSelector": NSStringFromSelector([self handlerNameToSelector:handlerName]),
|
|
@"invalidationSelector": NSStringFromSelector([self handlerNameToSelector:invalidationHandlerName]),
|
|
}];
|
|
_internalData = [NSMutableDictionary new];
|
|
_invalidated = NO;
|
|
[_internalData addEntriesFromDictionary:@{
|
|
@"version": @(HANDLER_VERSION),
|
|
@"delegate": NSStringFromClass(delegate),
|
|
@"handlerName": handlerName,
|
|
}];
|
|
if(invalidationHandlerName)
|
|
_internalData[@"invalidationName"] = invalidationHandlerName;
|
|
[self bindArguments:args];
|
|
return self;
|
|
}
|
|
|
|
-(void) bindArguments:(NSDictionary* _Nullable) args
|
|
{
|
|
[self checkInvalidation];
|
|
_internalData[@"boundArguments"] = [self sanitizeArguments:args];
|
|
}
|
|
|
|
-(void) callWithArguments:(NSDictionary* _Nullable) args
|
|
{
|
|
MLAssert(_internalData[@"delegate"] && _internalData[@"handlerName"], @"Tried to call MLHandler while delegate and/or handlerName was not set!", @{@"handler": _internalData});
|
|
[self checkInvalidation];
|
|
args = [self sanitizeArguments:args];
|
|
id delegate = NSClassFromString(_internalData[@"delegate"]);
|
|
SEL sel = [self handlerNameToSelector:_internalData[@"handlerName"]];
|
|
if(![delegate respondsToSelector:sel])
|
|
@throw [NSException exceptionWithName:@"RuntimeException" reason:[NSString stringWithFormat:@"Class '%@' does not provide handler implementation '%@'!", _internalData[@"delegate"], _internalData[@"handlerName"]] userInfo:@{
|
|
@"delegate": _internalData[@"delegate"],
|
|
@"handlerSelector": NSStringFromSelector(sel),
|
|
}];
|
|
DDLogVerbose(@"Calling handler %@...", self);
|
|
NSInvocation* inv = [NSInvocation invocationWithMethodSignature:[delegate methodSignatureForSelector:sel]];
|
|
[inv setTarget:delegate];
|
|
[inv setSelector:sel];
|
|
//arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation
|
|
//default arguments of the caller
|
|
[inv setArgument:(void* _Nonnull)&args atIndex:2];
|
|
//bound arguments of the handler
|
|
//make sure we use a copy because we don't want to leak changes of our bound arguments dict into already running invocations
|
|
NSDictionary* boundArgs = [_internalData[@"boundArguments"] copy];
|
|
[inv setArgument:(void* _Nonnull)&boundArgs atIndex:3];
|
|
//now call it
|
|
[inv invoke];
|
|
}
|
|
|
|
-(void) invalidateWithArguments:(NSDictionary* _Nullable) args
|
|
{
|
|
if(!(_internalData[@"delegate"] && _internalData[@"invalidationName"]))
|
|
return;
|
|
[self checkInvalidation];
|
|
args = [self sanitizeArguments:args];
|
|
id delegate = NSClassFromString(_internalData[@"delegate"]);
|
|
SEL sel = [self handlerNameToSelector:_internalData[@"invalidationName"]];
|
|
if(![delegate respondsToSelector:sel])
|
|
@throw [NSException exceptionWithName:@"RuntimeException" reason:[NSString stringWithFormat:@"Class '%@' does not provide invalidation implementation '%@'!", _internalData[@"delegate"], _internalData[@"invalidationName"]] userInfo:@{
|
|
@"delegate": _internalData[@"delegate"],
|
|
@"handlerSelector": NSStringFromSelector([self handlerNameToSelector:_internalData[@"handlerName"]]),
|
|
@"invalidationSelector": NSStringFromSelector(sel),
|
|
}];
|
|
DDLogVerbose(@"Calling invalidation %@...", self);
|
|
NSInvocation* inv = [NSInvocation invocationWithMethodSignature:[delegate methodSignatureForSelector:sel]];
|
|
[inv setTarget:delegate];
|
|
[inv setSelector:sel];
|
|
//arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation
|
|
//default arguments of the caller
|
|
[inv setArgument:(void* _Nonnull)&args atIndex:2];
|
|
//bound arguments of the handler
|
|
NSDictionary* boundArgs = _internalData[@"boundArguments"];
|
|
[inv setArgument:(void* _Nonnull)&boundArgs atIndex:3];
|
|
//now call it
|
|
[inv invoke];
|
|
_invalidated = YES;
|
|
}
|
|
|
|
-(NSString*) id
|
|
{
|
|
if(!_internalData[@"delegate"] || !_internalData[@"handlerName"])
|
|
return @"{emptyHandler}";
|
|
NSString* extras = @"";
|
|
if(_internalData[@"invalidationName"])
|
|
extras = [NSString stringWithFormat:@"<%@>", _internalData[@"invalidationName"]];
|
|
return [NSString stringWithFormat:@"%@|%@%@", _internalData[@"delegate"], _internalData[@"handlerName"], extras];
|
|
}
|
|
|
|
-(NSString*) description
|
|
{
|
|
NSString* extras = @"";
|
|
if(_internalData[@"invalidationName"])
|
|
extras = [NSString stringWithFormat:@"<%@>", _internalData[@"invalidationName"]];
|
|
return [NSString stringWithFormat:@"{%@, %@%@}", _internalData[@"delegate"], _internalData[@"handlerName"], extras];
|
|
}
|
|
|
|
+(BOOL) supportsSecureCoding
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
-(void) encodeWithCoder:(NSCoder*) coder
|
|
{
|
|
[coder encodeObject:_internalData forKey:@"internalData"];
|
|
[coder encodeBool:_invalidated forKey:@"invalidated"];
|
|
}
|
|
|
|
-(instancetype) initWithCoder:(NSCoder*) coder
|
|
{
|
|
self = [super init];
|
|
_internalData = [coder decodeObjectForKey:@"internalData"];
|
|
_invalidated = [coder decodeBoolForKey:@"invalidated"];
|
|
return self;
|
|
}
|
|
|
|
-(id) copyWithZone:(NSZone*) zone
|
|
{
|
|
MLHandler* copy = [[[self class] alloc] init];
|
|
copy->_internalData = [[NSMutableDictionary alloc] initWithDictionary:_internalData copyItems:YES];
|
|
copy->_invalidated = _invalidated;
|
|
return copy;
|
|
}
|
|
|
|
//this removes NSNull references from arguments altogether
|
|
-(NSMutableDictionary*) sanitizeArguments:(NSDictionary* _Nullable) args
|
|
{
|
|
NSMutableDictionary* retval = [NSMutableDictionary new];
|
|
if(args)
|
|
for(NSString* key in args)
|
|
if(args[key] != [NSNull null])
|
|
retval[key] = args[key];
|
|
return retval;
|
|
}
|
|
|
|
-(void) checkInvalidation
|
|
{
|
|
if(_invalidated)
|
|
@throw [NSException exceptionWithName:@"RuntimeException" reason:@"Tried to call or bind vars to already invalidated handler!" userInfo:@{
|
|
@"handler": _internalData,
|
|
}];
|
|
}
|
|
|
|
-(SEL) handlerNameToSelector:(NSString*) handlerName
|
|
{
|
|
return NSSelectorFromString([NSString stringWithFormat:@"MLHandler_%@_withArguments:andBoundArguments:", handlerName]);
|
|
}
|
|
|
|
+(void) throwDynamicExceptionForType:(NSString*) type andVar:(NSString*) varName andUserData:(id) userInfo andFile:(char*) file andLine:(int) line andFunc:(char*) func
|
|
{
|
|
NSString* text = [NSString stringWithFormat:@"Dynamic unpacking exception triggered for '%@' var '%@' at %@:%d in %s", type, varName, [HelperTools sanitizeFilePath:file], line, func];
|
|
DDLogError(@"%@", text);
|
|
@throw [NSException exceptionWithName:text reason:text userInfo:userInfo];
|
|
}
|
|
|
|
@end
|