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

发布于 2024-09-14 06:51:19 字数 156 浏览 5 评论 0原文

我有一个 UIScrollView 的子类,其中我需要在内部响应滚动行为。但是,视图控制器仍然需要侦听滚动委托回调,因此我无法直接窃取组件内的委托。

有没有办法保留名为“delegate”的属性并仅侦听沿其发送的消息,或者以某种方式在内部劫持委托属性并在运行某些代码后向外转发消息?

I have a subclass of UIScrollView in which I need to internally respond to scrolling behaviour. However, the viewcontroller will still need to listen to scrolling delegate callbacks, so I can't outright steal the delegate within my component.

Is there a way to keep the property named "delegate" and just listen to messages sent along it, or else somehow internally hijack the delegate property and forward messages outward after running some code?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

懷念過去 2024-09-21 06:51:19

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

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对象会自动将所有委托消息转发到常规委托对象,除了您在自定义子类中重写的消息。

To avoid overriding all of the delegate methods manually, you can use message forwarding. I just implemented the same thing using an intermediate proxy class as follows:

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 (Edited, with thanks to 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

With this approach, the MessageInterceptor object will automatically forward all delegate messages to the regular delegate object, except for the ones that you override in your custom subclass.

黎歌 2024-09-21 06:51:19

e.James 的帖子为大多数视图提供了一个很好的解决方案。但对于像 UITextField 和 UITextView 这样依赖键盘的视图,通常会导致无限循环的情况。为了摆脱它,我用一些额外的代码修复了它,这些代码检查选择器是否包含在特定协议中。

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
}

The post from e.James gave an excellent solution for most views. But for keyboard dependent views like UITextField and UITextView, it often results in a situation of infinite loop. To get rid of it, I fixed it with some additional code what checks whether the selector is contained in specific protocol(s) or not.

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;
}

And here is the Swift 2 version:

//
//  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
}
仅此而已 2024-09-21 06:51:19

实际上,这对我有用:

@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 给出的精彩答案中使子类成为消息拦截器。

Actually, this worked for me:

@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

...making the subclass to be the message interceptor in the awesome answer given by e.James.

扮仙女 2024-09-21 06:51:19

是的,但是您必须重写 文档。基本上,创建第二个委托属性并实现委托协议。当调用委托方法时,请处理您的事务,然后从刚刚运行的委托方法中调用第二个委托上的相同方法。例如

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

Yes, but you'll have to override every delegate method in the docs. Basically, make a second delegate property and implement the delegate protocol. When your delegate methods are called, take care of your business and then call the same method on your second delegate from the delegate method that was just run. E.g.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    // Do stuff here
    if ([self.delegate2 respondsToSelector:@selector(scrollViewDidScroll:)]) {
        [self.delegate2 scrollViewDidScroll:scrollView];
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文