sistema que programacion para operativo desde desarrollo crear con cero aprendiendo aprende apps aplicaciones ios objective-c objective-c-runtime

ios - que - swift 4. aprende a crear apps para iphone y ipad pdf



Interceptar mensajes delegados de Objective-C dentro de una subclase (4)

Tengo una subclase de UIScrollView en la que necesito responder internamente al comportamiento de desplazamiento. Sin embargo, el controlador de vista todavía tendrá que escuchar devoluciones de llamadas de delegados de desplazamiento, por lo que no puedo robar directamente el delegado dentro de mi componente.

¿Hay alguna manera de mantener la propiedad llamada "delegado" y solo escuchar los mensajes que se envían a lo largo de ella, o de alguna manera internamente secuestrar la propiedad de delegado y reenviar mensajes hacia afuera después de ejecutar algún código?


En realidad, esto funcionó para mí:

@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

... haciendo que la subclase sea el interceptor del mensaje en la asombrosa respuesta dada por e.James.


La publicación de e.James dio una excelente solución para la mayoría de las vistas. Pero para las vistas dependientes del teclado como UITextField y UITextView, a menudo resulta en una situación de ciclo infinito. Para deshacerse de él, lo arreglé con un código adicional que comprueba si el selector está contenido en protocolos específicos o no.

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

Y aquí está la versión 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 }


Para evitar anular todos los métodos de delegado de forma manual, puede usar el reenvío de mensajes. Acabo de implementar lo mismo usando una clase proxy intermedia de la siguiente manera:

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 (Editado, gracias a 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

Con este enfoque, el objeto MessageInterceptor reenviará automáticamente todos los mensajes delegados al objeto delegado normal, excepto los que sobrescriba en su subclase personalizada.


Sí, pero deberá anular todos los métodos delegados en los documentos . Básicamente, crea una segunda propiedad de delegado e implementa el protocolo de delegado. Cuando se invocan sus métodos de delegado, cuide su negocio y luego llame al mismo método en su segundo delegado desde el método de delegado que acaba de ejecutarse. P.ej

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