拦截Objective-C在子类中委托消息

我有一个UIScrollView的子类,我需要在内部响应滚动行为。 然而,viewcontroller仍然需要听滚动委托callback,所以我不能彻底窃取我的组件内的委托。

有没有办法保留名为“委托”的属性,只听信息发送,否则以某种方式内部劫持委托属性和运行一些代码后向外转发消息?

是的,但是您必须重写文档中的每个委托方法。 基本上,做第二个委托属性,并实施委托协议。 当你的委托方法被调用时,照顾你的业务,然后从刚刚运行的委托方法调用第二个委托上的相同方法。 例如

- (void)scrollViewDidScroll:(UIScrollView *)scrollView { // Do stuff here if ([self.delegate2 respondsToSelector:@selector(scrollViewDidScroll:)]) { [self.delegate2 scrollViewDidScroll:scrollView]; } } 

为避免手动覆盖所有委托方法,可以使用消息转发。 我刚刚使用中间代理类实现了相同的事情,如下所示:

MessageInterceptor.h

 @interface MessageInterceptor : NSObject { id receiver; id middleMan; } @property (nonatomic, assign) id receiver; @property (nonatomic, assign) id middleMan; @end 

MessageInterceptor.m

 @implementation MessageInterceptor @synthesize receiver; @synthesize middleMan; - (id)forwardingTargetForSelector:(SEL)aSelector { if ([middleMan respondsToSelector:aSelector]) { return middleMan; } if ([receiver respondsToSelector:aSelector]) { return receiver; } return [super forwardingTargetForSelector:aSelector]; } - (BOOL)respondsToSelector:(SEL)aSelector { if ([middleMan respondsToSelector:aSelector]) { return YES; } if ([receiver respondsToSelector:aSelector]) { return YES; } return [super respondsToSelector:aSelector]; } @end 

MyScrollView.h

 #import "MessageInterceptor.h" @interface MyScrollView : UIScrollView { MessageInterceptor * delegate_interceptor; //... } //... @end 

MyScrollView.m (编辑,感谢jhabbott ):

 @implementation MyScrollView - (id)delegate { return delegate_interceptor.receiver; } - (void)setDelegate:(id)newDelegate { [super setDelegate:nil]; [delegate_interceptor setReceiver:newDelegate]; [super setDelegate:(id)delegate_interceptor]; } - (id)init* { //... delegate_interceptor = [[MessageInterceptor alloc] init]; [delegate_interceptor setMiddleMan:self]; [super setDelegate:(id)delegate_interceptor]; //... } - (void)dealloc { //... [delegate_interceptor release]; //... } // delegate method override: - (void)scrollViewDidScroll:(UIScrollView *)scrollView { // 1. your custom code goes here // 2. forward to the delegate as usual if ([self.delegate respondsToSelector:@selector(scrollViewDidScroll:)]) { [self.delegate scrollViewDidScroll:scrollView]; } } @end 

通过这种方法, MessageInterceptor对象将自动将所有委托消息转发给常规委托对象, 在自定义子类中重写的那些除外

e.James的post为大多数观点提供了一个很好的解决scheme。 但是对于像UITextField和UITextView这样的键盘相关的视图,通常会导致无限循环的情况。 为了摆脱它,我修复了一些额外的代码什么检查select器是否包含在特定的协议(S)或不。

WZProtocolInterceptor.h

 #import <Foundation/Foundation.h> @interface WZProtocolInterceptor : NSObject @property (nonatomic, readonly, copy) NSArray * interceptedProtocols; @property (nonatomic, weak) id receiver; @property (nonatomic, weak) id middleMan; - (instancetype)initWithInterceptedProtocol:(Protocol *)interceptedProtocol; - (instancetype)initWithInterceptedProtocols:(Protocol *)firstInterceptedProtocol, ... NS_REQUIRES_NIL_TERMINATION; - (instancetype)initWithArrayOfInterceptedProtocols:(NSArray *)arrayOfInterceptedProtocols; @end 

WZProtocolInterceptor.m

 #import <objc/runtime.h> #import "WZProtocolInterceptor.h" static inline BOOL selector_belongsToProtocol(SEL selector, Protocol * protocol); @implementation WZProtocolInterceptor - (id)forwardingTargetForSelector:(SEL)aSelector { if ([self.middleMan respondsToSelector:aSelector] && [self isSelectorContainedInInterceptedProtocols:aSelector]) return self.middleMan; if ([self.receiver respondsToSelector:aSelector]) return self.receiver; return [super forwardingTargetForSelector:aSelector]; } - (BOOL)respondsToSelector:(SEL)aSelector { if ([self.middleMan respondsToSelector:aSelector] && [self isSelectorContainedInInterceptedProtocols:aSelector]) return YES; if ([self.receiver respondsToSelector:aSelector]) return YES; return [super respondsToSelector:aSelector]; } - (instancetype)initWithInterceptedProtocol:(Protocol *)interceptedProtocol { self = [super init]; if (self) { _interceptedProtocols = @[interceptedProtocol]; } return self; } - (instancetype)initWithInterceptedProtocols:(Protocol *)firstInterceptedProtocol, ...; { self = [super init]; if (self) { NSMutableArray * mutableProtocols = [NSMutableArray array]; Protocol * eachInterceptedProtocol; va_list argumentList; if (firstInterceptedProtocol) { [mutableProtocols addObject:firstInterceptedProtocol]; va_start(argumentList, firstInterceptedProtocol); while ((eachInterceptedProtocol = va_arg(argumentList, id))) { [mutableProtocols addObject:eachInterceptedProtocol]; } va_end(argumentList); } _interceptedProtocols = [mutableProtocols copy]; } return self; } - (instancetype)initWithArrayOfInterceptedProtocols:(NSArray *)arrayOfInterceptedProtocols { self = [super init]; if (self) { _interceptedProtocols = [arrayOfInterceptedProtocols copy]; } return self; } - (void)dealloc { _interceptedProtocols = nil; } - (BOOL)isSelectorContainedInInterceptedProtocols:(SEL)aSelector { __block BOOL isSelectorContainedInInterceptedProtocols = NO; [self.interceptedProtocols enumerateObjectsUsingBlock:^(Protocol * protocol, NSUInteger idx, BOOL *stop) { isSelectorContainedInInterceptedProtocols = selector_belongsToProtocol(aSelector, protocol); * stop = isSelectorContainedInInterceptedProtocols; }]; return isSelectorContainedInInterceptedProtocols; } @end BOOL selector_belongsToProtocol(SEL selector, Protocol * protocol) { // Reference: https://gist.github.com/numist/3838169 for (int optionbits = 0; optionbits < (1 << 2); optionbits++) { BOOL required = optionbits & 1; BOOL instance = !(optionbits & (1 << 1)); struct objc_method_description hasMethod = protocol_getMethodDescription(protocol, selector, required, instance); if (hasMethod.name || hasMethod.types) { return YES; } } return NO; } 

这里是Swift 2版本:

 // // NSProtocolInterpreter.swift // Nest // // Created by Manfred Lau on 11/28/14. // Copyright (c) 2014 WeZZard. All rights reserved. // import Foundation /** `NSProtocolInterceptor` is a proxy which intercepts messages to the middle man which originally intended to send to the receiver. - Discussion: `NSProtocolInterceptor` is a class cluster which dynamically subclasses itself to conform to the intercepted protocols at the runtime. */ public final class NSProtocolInterceptor: NSObject { /// Returns the intercepted protocols public var interceptedProtocols: [Protocol] { return _interceptedProtocols } private var _interceptedProtocols: [Protocol] = [] /// The receiver receives messages public weak var receiver: NSObjectProtocol? /// The middle man intercepts messages public weak var middleMan: NSObjectProtocol? private func doesSelectorBelongToAnyInterceptedProtocol( aSelector: Selector) -> Bool { for aProtocol in _interceptedProtocols where sel_belongsToProtocol(aSelector, aProtocol) { return true } return false } /// Returns the object to which unrecognized messages should first be /// directed. public override func forwardingTargetForSelector(aSelector: Selector) -> AnyObject? { if middleMan?.respondsToSelector(aSelector) == true && doesSelectorBelongToAnyInterceptedProtocol(aSelector) { return middleMan } if receiver?.respondsToSelector(aSelector) == true { return receiver } return super.forwardingTargetForSelector(aSelector) } /// Returns a Boolean value that indicates whether the receiver implements /// or inherits a method that can respond to a specified message. public override func respondsToSelector(aSelector: Selector) -> Bool { if middleMan?.respondsToSelector(aSelector) == true && doesSelectorBelongToAnyInterceptedProtocol(aSelector) { return true } if receiver?.respondsToSelector(aSelector) == true { return true } return super.respondsToSelector(aSelector) } /** Create a protocol interceptor which intercepts a single Objecitve-C protocol. - Parameter protocols: An Objective-C protocol, such as UITableViewDelegate.self. */ public class func forProtocol(aProtocol: Protocol) -> NSProtocolInterceptor { return forProtocols([aProtocol]) } /** Create a protocol interceptor which intercepts a variable-length sort of Objecitve-C protocols. - Parameter protocols: A variable length sort of Objective-C protocol, such as UITableViewDelegate.self. */ public class func forProtocols(protocols: Protocol ...) -> NSProtocolInterceptor { return forProtocols(protocols) } /** Create a protocol interceptor which intercepts an array of Objecitve-C protocols. - Parameter protocols: An array of Objective-C protocols, such as [UITableViewDelegate.self]. */ public class func forProtocols(protocols: [Protocol]) -> NSProtocolInterceptor { let protocolNames = protocols.map { NSStringFromProtocol($0) } let sortedProtocolNames = protocolNames.sort() let concatenatedName = sortedProtocolNames.joinWithSeparator(",") let theConcreteClass = concreteClassWithProtocols(protocols, concatenatedName: concatenatedName, salt: nil) let protocolInterceptor = theConcreteClass.init() as! NSProtocolInterceptor protocolInterceptor._interceptedProtocols = protocols return protocolInterceptor } /** Return a subclass of `NSProtocolInterceptor` which conforms to specified protocols. - Parameter protocols: An array of Objective-C protocols. The subclass returned from this function will conform to these protocols. - Parameter concatenatedName: A string which came from concatenating names of `protocols`. - Parameter salt: A UInt number appended to the class name which used for distinguishing the class name itself from the duplicated. - Discussion: The return value type of this function can only be `NSObject.Type`, because if you return with `NSProtocolInterceptor.Type`, you can only init the returned class to be a `NSProtocolInterceptor` but not its subclass. */ private class func concreteClassWithProtocols(protocols: [Protocol], concatenatedName: String, salt: UInt?) -> NSObject.Type { let className: String = { let basicClassName = "_" + NSStringFromClass(NSProtocolInterceptor.self) + "_" + concatenatedName if let salt = salt { return basicClassName + "_\(salt)" } else { return basicClassName } }() let nextSalt = salt.map {$0 + 1} if let theClass = NSClassFromString(className) { switch theClass { case let anInterceptorClass as NSProtocolInterceptor.Type: let isClassConformsToAllProtocols: Bool = { // Check if the found class conforms to the protocols for eachProtocol in protocols where !class_conformsToProtocol(anInterceptorClass, eachProtocol) { return false } return true }() if isClassConformsToAllProtocols { return anInterceptorClass } else { return concreteClassWithProtocols(protocols, concatenatedName: concatenatedName, salt: nextSalt) } default: return concreteClassWithProtocols(protocols, concatenatedName: concatenatedName, salt: nextSalt) } } else { let subclass = objc_allocateClassPair(NSProtocolInterceptor.self, className, 0) as! NSObject.Type for eachProtocol in protocols { class_addProtocol(subclass, eachProtocol) } objc_registerClassPair(subclass) return subclass } } } /** Returns true when the given selector belongs to the given protocol. */ public func sel_belongsToProtocol(aSelector: Selector, _ aProtocol: Protocol) -> Bool { for optionBits: UInt in 0..<(1 << 2) { let isRequired = optionBits & 1 != 0 let isInstance = !(optionBits & (1 << 1) != 0) let methodDescription = protocol_getMethodDescription(aProtocol, aSelector, isRequired, isInstance) if !objc_method_description_isEmpty(methodDescription) { return true } } return false } public func objc_method_description_isEmpty( var methodDescription: objc_method_description) -> Bool { let ptr = withUnsafePointer(&methodDescription) { UnsafePointer<Int8>($0) } for offset in 0..<sizeof(objc_method_description) { if ptr[offset] != 0 { return false } } return true } 

其实,这对我有效:

 @implementation MySubclass { id _actualDelegate; } // There is no need to set the value of _actualDelegate in an init* method - (void)setDelegate:(id)newDelegate { [super setDelegate:nil]; _actualDelegate = newDelegate; [super setDelegate:(id)self]; } - (id)delegate { return self; } - (id)forwardingTargetForSelector:(SEL)aSelector { if ([_actualDelegate respondsToSelector:aSelector]) { return _actualDelegate; } return [super forwardingTargetForSelector:aSelector]; } - (BOOL)respondsToSelector:(SEL)aSelector { return [super respondsToSelector:aSelector] || [_actualDelegate respondsToSelector:aSelector]; } @end 

…使子类成为e.James给出的令人敬畏的答案中的消息拦截器。