@interface FLWeakProxy : NSProxy
+(instancetype)weakProxyForObject:(id)targetObject;@end@interfaceFLWeakProxy()@property(nonatomic, weak) id target;@end@implementation FLWeakProxy
#pragmamark Life Cycle// This is the designated creation method of an `FLWeakProxy` and// as a subclass of `NSProxy` it doesn't respond to or need `-init`.+(instancetype)weakProxyForObject:(id)targetObject {
FLWeakProxy *weakProxy =[FLWeakProxy alloc];
weakProxy.target = targetObject;return weakProxy;}#pragmamark Forwarding Messages-(id)forwardingTargetForSelector:(SEL)selector {// Keep it lightweight: access the ivar directlyreturn _target;}#pragmamark - NSWeakProxy Method Overrides#pragmamark Handling Unimplemented Methods-(void)forwardInvocation:(NSInvocation *)invocation {// Fallback for when target is nil. Don't do anything, just return 0/NULL/nil.// The method signature we've received to get here is just a dummy to keep `doesNotRecognizeSelector:` from firing.// We can't really handle struct return types here because we don't know the length.void*nullPointer =NULL;[invocation setReturnValue:&nullPointer];}-(NSMethodSignature *)methodSignatureForSelector:(SEL)selector {// We only get here if `forwardingTargetForSelector:` returns nil.// In that case, our weak target has been reclaimed. Return a dummy method signature to keep `doesNotRecognizeSelector:` from firing.// We'll emulate the Obj-c messaging nil behavior by setting the return value to nil in `forwardInvocation:`, but we'll assume that the return value is `sizeof(void *)`.// Other libraries handle this situation by making use of a global method signature cache, but that seems heavier than necessary and has issues as well.// See https://www.mikeash.com/pyblog/friday-qa-2010-02-26-futures.html and https://github.com/steipete/PSTDelegateProxy/issues/1 for examples of using a method signature cache.return[NSObject instanceMethodSignatureForSelector:@selector(init)];}@end
@interface IGListAdapter : NSObject
.../**
The object that receives `UICollectionViewDelegate` events.
@note This object *will not* receive `UIScrollViewDelegate` events. Instead use scrollViewDelegate.
*/@property(nonatomic, nullable, weak) id <UICollectionViewDelegate> collectionViewDelegate;/**
The object that receives `UIScrollViewDelegate` events.
*/@property(nonatomic, nullable, weak) id <UIScrollViewDelegate> scrollViewDelegate;...@end
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {for(GCDMulticastDelegateNode *node in delegateNodes){
id nodeDelegate = node.delegate;#if__has_feature(objc_arc_weak)&&!TARGET_OS_IPHONEif(nodeDelegate ==[NSNull null])
nodeDelegate = node.unsafeDelegate;#endif
NSMethodSignature *result =[nodeDelegate methodSignatureForSelector:aSelector];if(result != nil){return result;}}// This causes a crash...// return [super methodSignatureForSelector:aSelector];// This also causes a crash...// return nil;return[[self class] instanceMethodSignatureForSelector:@selector(doNothing)];}-(void)forwardInvocation:(NSInvocation *)origInvocation {
SEL selector =[origInvocation selector];
BOOL foundNilDelegate = NO;for(GCDMulticastDelegateNode *node in delegateNodes){
id nodeDelegate = node.delegate;#if__has_feature(objc_arc_weak)&&!TARGET_OS_IPHONEif(nodeDelegate ==[NSNull null])
nodeDelegate = node.unsafeDelegate;#endifif([nodeDelegate respondsToSelector:selector]){// All delegates MUST be invoked ASYNCHRONOUSLY.
NSInvocation *dupInvocation =[self duplicateInvocation:origInvocation];dispatch_async(node.delegateQueue,^{@autoreleasepool {[dupInvocation invokeWithTarget:nodeDelegate];}});}elseif(nodeDelegate == nil){
foundNilDelegate = YES;}}if(foundNilDelegate){// At lease one weak delegate reference disappeared.// Remove nil delegate nodes from the list.// // This is expected to happen very infrequently.// This is why we handle it separately (as it requires allocating an indexSet).
NSMutableIndexSet *indexSet =[[NSMutableIndexSet alloc] init];
NSUInteger i =0;for(GCDMulticastDelegateNode *node in delegateNodes){
id nodeDelegate = node.delegate;#if__has_feature(objc_arc_weak)&&!TARGET_OS_IPHONEif(nodeDelegate ==[NSNull null])
nodeDelegate = node.unsafeDelegate;#endifif(nodeDelegate == nil){[indexSet addIndex:i];}
i++;}[delegateNodes removeObjectsAtIndexes:indexSet];}}-(void)doesNotRecognizeSelector:(SEL)aSelector {// Prevent NSInvalidArgumentException}-(void)doNothing {}
// Adds a block of code before/instead/after the current `selector` for a specific class.+(id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;// Adds a block of code before/instead/after the current `selector` for a specific instance.-(id<AspectToken>)aspect_hookSelector:(SEL)selector
withOptions:(AspectOptions)options
usingBlock:(id)block
error:(NSError **)error;
综合上述两点以及 Normal forwarding path 的执行过程,可以比较轻松地联想到 -forwardInvocation: 方法非常适合作为这个入口。结合 Aspects 源码,来看下其实现中,和消息转发相关的两个步骤:
staticvoidaspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error){NSCParameterAssert(selector);
Class klass =aspect_hookClass(self, error);
Method targetMethod =class_getInstanceMethod(klass, selector);
IMP targetMethodIMP =method_getImplementation(targetMethod);if(!aspect_isMsgForwardIMP(targetMethodIMP)){// Make a method alias for the existing method implementation, it not already copied.constchar*typeEncoding =method_getTypeEncoding(targetMethod);
SEL aliasSelector =aspect_aliasForSelector(selector);if(![klass instancesRespondToSelector:aliasSelector]){
__unused BOOL addedAlias =class_addMethod(klass, aliasSelector,method_getImplementation(targetMethod), typeEncoding);NSCAssert(addedAlias,@"Original implementation for %@ is already copied to %@ on %@",NSStringFromSelector(selector),NSStringFromSelector(aliasSelector), klass);}// We use forwardInvocation to hook in.class_replaceMethod(klass, selector,aspect_getMsgForwardIMP(self, selector), typeEncoding);AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass,NSStringFromSelector(selector));}}static Class aspect_hookClass(NSObject *self, NSError **error){NSCParameterAssert(self);...aspect_swizzleForwardInvocation(subclass);...}staticvoidaspect_swizzleForwardInvocation(Class klass){NSCParameterAssert(klass);// If there is no method, replace will act like class_addMethod.
IMP originalImplementation =class_replaceMethod(klass,@selector(forwardInvocation:),(IMP)__ASPECTS_ARE_BEING_CALLED__,"v@:@");if(originalImplementation){class_addMethod(klass,NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation,"v@:@");}AspectLog(@"Aspects: %@ is now aspect aware.",NSStringFromClass(klass));}staticvoid__ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation){NSCParameterAssert(self);NSCParameterAssert(invocation);...// Before hooks.aspect_invoke(classContainer.beforeAspects, info);aspect_invoke(objectContainer.beforeAspects, info);// Instead hooks.
BOOL respondsToAlias = YES;if(objectContainer.insteadAspects.count || classContainer.insteadAspects.count){aspect_invoke(classContainer.insteadAspects, info);aspect_invoke(objectContainer.insteadAspects, info);}else{
Class klass =object_getClass(invocation.target);do{if((respondsToAlias =[klass instancesRespondToSelector:aliasSelector])){[invocation invoke];break;}}while(!respondsToAlias &&(klass =class_getSuperclass(klass)));}// After hooks.aspect_invoke(classContainer.afterAspects, info);aspect_invoke(objectContainer.afterAspects, info);...}