211 lines
7.5 KiB
Objective-C
211 lines
7.5 KiB
Objective-C
|
|
#import "TDDelegateProxy.h"
|
|
#import "TDLogging.h"
|
|
|
|
#if __has_include(<ThinkingDataCore/TDClassHelper.h>)
|
|
#import <ThinkingDataCore/TDClassHelper.h>
|
|
#else
|
|
#import "TDClassHelper.h"
|
|
#endif
|
|
#if __has_include(<ThinkingDataCore/TDMethodHelper.h>)
|
|
#import <ThinkingDataCore/TDMethodHelper.h>
|
|
#else
|
|
#import "TDMethodHelper.h"
|
|
#endif
|
|
#import "NSObject+TDDelegateProxy.h"
|
|
#import <objc/message.h>
|
|
|
|
static NSString * const kTDNSObjectRemoveObserverSelector = @"removeObserver:forKeyPath:";
|
|
static NSString * const kTDNSObjectAddObserverSelector = @"addObserver:forKeyPath:options:context:";
|
|
static NSString * const kTDNSObjectClassSelector = @"class";
|
|
|
|
@implementation TDDelegateProxy
|
|
|
|
+ (void)proxyDelegate:(id)delegate selectors:(NSSet<NSString *> *)selectors {
|
|
if (object_isClass(delegate) || selectors.count == 0) {
|
|
return;
|
|
}
|
|
|
|
Class proxyClass = [self class];
|
|
NSMutableSet *delegateSelectors = [NSMutableSet setWithSet:selectors];
|
|
|
|
TDDelegateProxyObject *object = [delegate thinkingdata_delegateObject];
|
|
if (!object) {
|
|
object = [[TDDelegateProxyObject alloc] initWithDelegate:delegate proxy:proxyClass];
|
|
[delegate setThinkingdata_delegateObject:object];
|
|
}
|
|
|
|
[delegateSelectors minusSet:object.selectors];
|
|
if (delegateSelectors.count == 0) {
|
|
return;
|
|
}
|
|
|
|
if (object.thinkingClass) {
|
|
[self addInstanceMethodWithSelectors:delegateSelectors fromClass:proxyClass toClass:object.thinkingClass];
|
|
[object.selectors unionSet:delegateSelectors];
|
|
|
|
if (![object_getClass(delegate) isSubclassOfClass:object.thinkingClass]) {
|
|
[TDClassHelper setObject:delegate toClass:object.thinkingClass];
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (object.kvoClass) {
|
|
if ([delegate isKindOfClass:NSObject.class] && ![object.selectors containsObject:kTDNSObjectRemoveObserverSelector]) {
|
|
[delegateSelectors addObject:kTDNSObjectRemoveObserverSelector];
|
|
}
|
|
[self addInstanceMethodWithSelectors:delegateSelectors fromClass:proxyClass toClass:object.kvoClass];
|
|
[object.selectors unionSet:delegateSelectors];
|
|
return;
|
|
}
|
|
|
|
Class thinkingClass = [TDClassHelper allocateClassWithObject:delegate className:object.thinkingClassName];
|
|
[TDClassHelper registerClass:thinkingClass];
|
|
|
|
if ([delegate isKindOfClass:NSObject.class] && ![object.selectors containsObject:kTDNSObjectAddObserverSelector]) {
|
|
[delegateSelectors addObject:kTDNSObjectAddObserverSelector];
|
|
}
|
|
|
|
if (![object.selectors containsObject:kTDNSObjectClassSelector]) {
|
|
[delegateSelectors addObject:kTDNSObjectClassSelector];
|
|
}
|
|
|
|
[self addInstanceMethodWithSelectors:delegateSelectors fromClass:proxyClass toClass:thinkingClass];
|
|
[object.selectors unionSet:delegateSelectors];
|
|
|
|
[TDClassHelper setObject:delegate toClass:thinkingClass];
|
|
}
|
|
|
|
+ (void)addInstanceMethodWithSelectors:(NSSet<NSString *> *)selectors fromClass:(Class)fromClass toClass:(Class)toClass {
|
|
for (NSString *selector in selectors) {
|
|
SEL sel = NSSelectorFromString(selector);
|
|
[TDMethodHelper addInstanceMethodWithSelector:sel fromClass:fromClass toClass:toClass];
|
|
}
|
|
}
|
|
|
|
+ (BOOL)invokeReturnBOOLWithTarget:(NSObject *)target selector:(SEL)selector arg1:(id)arg1 arg2:(id)arg2 arg3:(id)arg3 {
|
|
Class originalClass = target.thinkingdata_delegateObject.delegateISA;
|
|
|
|
struct objc_super targetSuper = {
|
|
.receiver = target,
|
|
.super_class = originalClass
|
|
};
|
|
|
|
BOOL returnValue = NO;
|
|
@try {
|
|
returnValue = ((BOOL (*)(struct objc_super *, SEL, id, id, id))objc_msgSendSuper)(&targetSuper, selector, arg1, arg2, arg3);
|
|
} @catch (NSException *exception) {
|
|
TDLogInfo(@"msgSendSuper with exception: %@", exception);
|
|
} @finally {
|
|
|
|
}
|
|
return returnValue;
|
|
}
|
|
|
|
+ (BOOL)invokeReturnBOOLWithTarget:(NSObject *)target selector:(SEL)selector arg1:(id)arg1 arg2:(id)arg2 {
|
|
Class originalClass = target.thinkingdata_delegateObject.delegateISA;
|
|
|
|
struct objc_super targetSuper = {
|
|
.receiver = target,
|
|
.super_class = originalClass
|
|
};
|
|
|
|
BOOL returnValue = NO;
|
|
@try {
|
|
returnValue = ((BOOL (*)(struct objc_super *, SEL, id, id))objc_msgSendSuper)(&targetSuper, selector, arg1, arg2);
|
|
} @catch (NSException *exception) {
|
|
TDLogInfo(@"msgSendSuper with exception: %@", exception);
|
|
} @finally {
|
|
|
|
}
|
|
return returnValue;
|
|
}
|
|
|
|
+ (void)invokeWithTarget:(NSObject *)target selector:(SEL)selector, ... {
|
|
Class originalClass = target.thinkingdata_delegateObject.delegateISA;
|
|
|
|
va_list args;
|
|
va_start(args, selector);
|
|
id arg1 = nil, arg2 = nil, arg3 = nil, arg4 = nil;
|
|
NSInteger count = [NSStringFromSelector(selector) componentsSeparatedByString:@":"].count - 1;
|
|
for (NSInteger i = 0; i < count; i++) {
|
|
i == 0 ? (arg1 = va_arg(args, id)) : nil;
|
|
i == 1 ? (arg2 = va_arg(args, id)) : nil;
|
|
i == 2 ? (arg3 = va_arg(args, id)) : nil;
|
|
i == 3 ? (arg4 = va_arg(args, id)) : nil;
|
|
}
|
|
struct objc_super targetSuper = {
|
|
.receiver = target,
|
|
.super_class = originalClass
|
|
};
|
|
|
|
@try {
|
|
void (*func)(struct objc_super *, SEL, id, id, id, id) = (void *)&objc_msgSendSuper;
|
|
func(&targetSuper, selector, arg1, arg2, arg3, arg4);
|
|
} @catch (NSException *exception) {
|
|
TDLogInfo(@"msgSendSuper with exception: %@", exception);
|
|
} @finally {
|
|
va_end(args);
|
|
}
|
|
}
|
|
|
|
|
|
+ (void)resolveOptionalSelectorsForDelegate:(id)delegate {
|
|
if (object_isClass(delegate)) {
|
|
return;
|
|
}
|
|
|
|
NSSet *currentOptionalSelectors = ((NSObject *)delegate).thinkingdata_optionalSelectors;
|
|
NSMutableSet *optionalSelectors = [[NSMutableSet alloc] init];
|
|
if (currentOptionalSelectors) {
|
|
[optionalSelectors unionSet:currentOptionalSelectors];
|
|
}
|
|
|
|
if ([self respondsToSelector:@selector(optionalSelectors)] &&[self optionalSelectors]) {
|
|
[optionalSelectors unionSet:[self optionalSelectors]];
|
|
}
|
|
((NSObject *)delegate).thinkingdata_optionalSelectors = [optionalSelectors copy];
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - Class
|
|
@implementation TDDelegateProxy (Class)
|
|
|
|
- (Class)class {
|
|
if (self.thinkingdata_delegateObject.delegateClass) {
|
|
return self.thinkingdata_delegateObject.delegateClass;
|
|
}
|
|
return [super class];
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark - KVO
|
|
@implementation TDDelegateProxy (KVO)
|
|
|
|
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
|
|
[super addObserver:observer forKeyPath:keyPath options:options context:context];
|
|
if (self.thinkingdata_delegateObject) {
|
|
[TDMethodHelper replaceInstanceMethodWithDestinationSelector:@selector(class) sourceSelector:@selector(class) fromClass:TDDelegateProxy.class toClass:object_getClass(self)];
|
|
}
|
|
}
|
|
|
|
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
|
|
BOOL oldClassIsKVO = [TDDelegateProxyObject isKVOClass:object_getClass(self)];
|
|
[super removeObserver:observer forKeyPath:keyPath];
|
|
BOOL newClassIsKVO = [TDDelegateProxyObject isKVOClass:object_getClass(self)];
|
|
|
|
if (oldClassIsKVO && !newClassIsKVO) {
|
|
Class delegateProxy = self.thinkingdata_delegateObject.delegateProxy;
|
|
NSSet *selectors = [self.thinkingdata_delegateObject.selectors copy];
|
|
|
|
[self.thinkingdata_delegateObject removeKVO];
|
|
if ([delegateProxy respondsToSelector:@selector(proxyDelegate:selectors:)]) {
|
|
[delegateProxy proxyDelegate:self selectors:selectors];
|
|
}
|
|
}
|
|
}
|
|
|
|
@end
|