objective c - Suprimir advertencia "Categoría está implementando un método que también será implementado por su clase principal"
objective-c clang (8)
Me preguntaba cómo suprimir la advertencia:
La categoría está implementando un método que también será implementado por su clase primaria.
Tengo esto para una categoría de código específica:
+ (UIFont *)systemFontOfSize:(CGFloat)fontSize {
return [self aCustomFontOfSize:fontSize];
}
Aunque todo lo que se dice correctamente es correcto, en realidad no responde a su pregunta de cómo suprimir la advertencia.
Si tiene que tener este código por alguna razón (en mi caso tengo HockeyKit en mi proyecto y ellos anulan un método en una categoría de UIImage [edit: este ya no es el caso]) y necesita que su proyecto compile , puedes usar las sentencias #pragma
para bloquear la advertencia de la siguiente manera:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
// do your override
#pragma clang diagnostic pop
Encontré la información aquí: http://www.cocoabuilder.com/archive/xcode/313767-disable-warning-for-override-in-category.html
Las categorías son buenas, pero pueden ser abusadas. Al escribir categorías, como principio, NO debe volver a implementar los métodos de salida. Si lo hace, puede causar un efecto secundario extraño ya que ahora está reescribiendo el código que otra clase depende de uno. podrías romper una clase conocida y terminar convirtiendo tu depurador de adentro hacia afuera. Es simplemente mala programación.
Si necesitas hacerlo, realmente deberías subclasificarlo.
Entonces la sugerencia de swizzling, eso es un gran NO-NO-NO para mí.
Swizzing en tiempo de ejecución es un completo NO-NO-NO.
¿Quieres que un plátano se vea como una naranja, pero solo en tiempo de ejecución? Si quieres una naranja, entonces escribe una naranja.
No hagas un aspecto de plátano y actúa como una naranja. Y lo que es peor: no convierta su plátano en un agente secreto que saboteará tranquilamente los plátanos de todo el mundo en apoyo de las naranjas.
¡Ay!
Las propiedades generales son válidas para una extensión de clase (categoría anónima), pero no para una categoría normal.
De acuerdo con los Apple Docs que usan una extensión de clase (categoría anónima), puede crear una interfaz privada para una clase pública, de modo que la interfaz privada pueda anular las propiedades expuestas públicamente. es decir, puede cambiar una propiedad de readonly a readwrite.
Un caso de uso para esto es cuando se escriben bibliotecas que restringen el acceso a propiedades públicas, mientras que la misma propiedad necesita acceso de escritura de lectura completa dentro de la biblioteca.
Enlace de Apple Docs: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html
Busque " Usar extensiones de clase para ocultar información privada ".
Entonces esta técnica es válida para una extensión de clase, pero no para una categoría.
Prueba esto en tu código:
+(void)load{
EXCHANGE_METHOD(Method1, Method1Impl);
}
ACTUALIZACIÓN2: agregue esta macro
#import <Foundation/Foundation.h>
#define EXCHANGE_METHOD(a,b) [[self class]exchangeMethod:@selector(a) withNewMethod:@selector(b)]
@interface NSObject (MethodExchange)
+(void)exchangeMethod:(SEL)origSel withNewMethod:(SEL)newSel;
@end
#import <objc/runtime.h>
@implementation NSObject (MethodExchange)
+(void)exchangeMethod:(SEL)origSel withNewMethod:(SEL)newSel{
Class class = [self class];
Method origMethod = class_getInstanceMethod(class, origSel);
if (!origMethod){
origMethod = class_getClassMethod(class, origSel);
}
if (!origMethod)
@throw [NSException exceptionWithName:@"Original method not found" reason:nil userInfo:nil];
Method newMethod = class_getInstanceMethod(class, newSel);
if (!newMethod){
newMethod = class_getClassMethod(class, newSel);
}
if (!newMethod)
@throw [NSException exceptionWithName:@"New method not found" reason:nil userInfo:nil];
if (origMethod==newMethod)
@throw [NSException exceptionWithName:@"Methods are the same" reason:nil userInfo:nil];
method_exchangeImplementations(origMethod, newMethod);
}
@end
Puede utilizar el método Swizzling para suprimir esta advertencia del compilador. Aquí es cómo implementé el método swizzling para dibujar márgenes en un UITextField cuando usamos un fondo personalizado con UITextBorderStyleNone:
#import <UIKit/UIKit.h>
@interface UITextField (UITextFieldCatagory)
+(void)load;
- (CGRect)textRectForBoundsCustom:(CGRect)bounds;
- (CGRect)editingRectForBoundsCustom:(CGRect)bounds;
@end
#import "UITextField+UITextFieldCatagory.h"
#import <objc/objc-runtime.h>
@implementation UITextField (UITextFieldCatagory)
+(void)load
{
Method textRectForBounds = class_getInstanceMethod(self, @selector(textRectForBounds:));
Method textRectForBoundsCustom = class_getInstanceMethod(self, @selector(textRectForBoundsCustom:));
Method editingRectForBounds = class_getInstanceMethod(self, @selector(editingRectForBounds:));
Method editingRectForBoundsCustom = class_getInstanceMethod(self, @selector(editingRectForBoundsCustom:));
method_exchangeImplementations(textRectForBounds, textRectForBoundsCustom);
method_exchangeImplementations(editingRectForBounds, editingRectForBoundsCustom);
}
- (CGRect)textRectForBoundsCustom:(CGRect)bounds
{
CGRect inset = CGRectMake(bounds.origin.x + 10, bounds.origin.y, bounds.size.width - 10, bounds.size.height);
return inset;
}
- (CGRect)editingRectForBoundsCustom:(CGRect)bounds
{
CGRect inset = CGRectMake(bounds.origin.x + 10, bounds.origin.y, bounds.size.width - 10, bounds.size.height);
return inset;
}
@end
Tuve este problema cuando implementé un método delegado en una categoría en lugar de la clase principal (aunque no hubo implementación de clase principal). La solución para mí fue mover el archivo de encabezado de clase principal al archivo de encabezado de categoría Esto funciona bien
Una categoría le permite agregar nuevos métodos a una clase existente. Si desea volver a implementar un método que ya existe en la clase, normalmente crea una subclase en lugar de una categoría.
Documentación de Apple: Personalización de clases existentes
Si el nombre de un método declarado en una categoría es igual que un método en la clase original, o un método en otra categoría en la misma clase (o incluso una superclase), el comportamiento no está definido en cuanto a qué implementación de método se usa en tiempo de ejecución
Dos métodos con la misma firma exacta en la misma clase conducirían a un comportamiento impredecible, porque cada persona que llama no puede especificar qué implementación desean.
Por lo tanto, debe usar una categoría y proporcionar nombres de método que sean nuevos y únicos para la clase o subclase si desea cambiar el comportamiento de un método existente en una clase.
Una mejor alternativa (vea la respuesta de bneely a por qué esta advertencia lo está salvando del desastre) es usar el método swizzling. Al utilizar el método Swizzling, puede reemplazar un método existente de una categoría sin la incertidumbre de quién "gana", y al mismo tiempo conserva la capacidad de llamar al método anterior. El secreto es dar a la anulación un nombre de método diferente, luego cambiarlos usando funciones de tiempo de ejecución.
#import <objc/runtime.h>
#import <objc/message.h>
void MethodSwizzle(Class c, SEL orig, SEL new) {
Method origMethod = class_getInstanceMethod(c, orig);
Method newMethod = class_getInstanceMethod(c, new);
if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
else
method_exchangeImplementations(origMethod, newMethod);
}
Luego defina su implementación personalizada:
+ (UIFont *)mySystemFontOfSize:(CGFloat)fontSize {
...
}
Anule la implementación predeterminada con la suya:
MethodSwizzle([UIFont class], @selector(systemFontOfSize:), @selector(mySystemFontOfSize:));