Sopprimere l'avviso "La categoria sta implementando un metodo che verrà implementato anche dalla sua classe primaria"


98

Mi chiedevo come sopprimere l'avviso:

Category sta implementando un metodo che sarà implementato anche dalla sua classe primaria.

Ho questo per una categoria di codice specifica:

+ (UIFont *)systemFontOfSize:(CGFloat)fontSize {
    return [self aCustomFontOfSize:fontSize];
}

Con metodo swizzling. Anche se non lo farei, forse potresti creare una sottoclasse UIFont che sostituisce lo stesso metodo e chiamare superaltrimenti.
Alan Zeino

4
Il tuo problema non è l'avvertimento. Il tuo problema è che hai lo stesso nome del metodo, che porterà a problemi.
gnasher729

Vedere Sostituzione dei metodi utilizzando le categorie in Objective-C per i motivi per cui non si dovrebbero sovrascrivere i metodi utilizzando le categorie e per soluzioni alternative.
Senseful

Se conoscete una soluzione più elegante per impostare il carattere a livello di applicazione, mi piacerebbe davvero sentirla!
To1ne

Risposte:


64

Una categoria consente di aggiungere nuovi metodi a una classe esistente. Se desideri reimplementare un metodo già esistente nella classe, in genere crei una sottoclasse invece di una categoria.

Documentazione Apple: personalizzazione delle classi esistenti

Se il nome di un metodo dichiarato in una categoria è lo stesso di un metodo nella classe originale, o di un metodo in un'altra categoria sulla stessa classe (o anche una superclasse), il comportamento è indefinito per quanto riguarda l'implementazione del metodo utilizzata in runtime.

Due metodi con la stessa identica firma nella stessa classe porterebbero a un comportamento imprevedibile, perché ogni chiamante non può specificare quale implementazione desidera.

Quindi, dovresti usare una categoria e fornire nomi di metodo nuovi e univoci per la classe, o una sottoclasse se vuoi cambiare il comportamento di un metodo esistente in una classe.


1
Sono totalmente d'accordo con quelle idee spiegate sopra e cerco di seguirle durante lo sviluppo. ma ci sono ancora casi in cui potrebbe essere appropriato sostituire i metodi nella categoria. ad esempio, i casi in cui è possibile utilizzare ereditarietà multipla (come in c ++) o interfacce (come in c #). ho appena affrontato ciò nel mio progetto e mi sono reso conto che i metodi di sostituzione nelle categorie sono la scelta migliore.
peetonn

4
Può essere utile durante il test unitario di un codice contenente un singleton. Idealmente, i singleton dovrebbero essere iniettati nel codice come protocollo, consentendo di cambiare l'implementazione. Ma se ne hai già uno incorporato nel codice, puoi aggiungere una categoria del singleton nel tuo unit test e sovrascrivere sharedInstance e i metodi che controlli per trasformarli in oggetti fittizi.
bandejapaisa

Grazie @PsychoDad. Ho aggiornato il collegamento e ho aggiunto una citazione dalla documentazione pertinente a questo post.
appena

Sembra buono. Apple fornisce documentazione sul comportamento dell'utilizzo di una categoria con un nome di metodo esistente?
jjxtra

1
fantastico, non ero sicuro di dover andare con categoria o sottoclasse :-)
kernix

343

Sebbene tutto ciò che bneely detto sia corretto, in realtà non risponde alla tua domanda su come sopprimere l'avvertimento.

Se devi inserire questo codice per qualche motivo (nel mio caso ho HockeyKit nel mio progetto e sovrascrivono un metodo in una categoria UIImage [modifica: non è più il caso]) e devi far compilare il tuo progetto , puoi utilizzare le #pragmaistruzioni per bloccare l'avviso in questo modo:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"

// do your override

#pragma clang diagnostic pop

Ho trovato le informazioni qui: http://www.cocoabuilder.com/archive/xcode/313767-disable-warning-for-override-in-category.html


Molte grazie! Quindi, vedo che i pragmi possono anche sopprimere gli avvertimenti. :-p
Constantino Tsarouhas

Sì, e sebbene queste siano dichiarazioni specifiche di LLVM, ce ne sono di simili anche per GCC.
Ben Baron

1
L'avvertimento nel progetto di test è un avviso del linker, non un avviso del compilatore llvm, quindi il pragma llvm non sta facendo nulla. Tuttavia, noterai che il tuo progetto di test viene ancora compilato con "considera gli avvisi come errori" attivato perché è un avviso del linker.
Ben Baron

12
Questa dovrebbe essere davvero la risposta accettata, dato che risponde effettivamente alla domanda.
Rob Jones

1
Questa risposta dovrebbe essere quella corretta. Ha comunque più voti di quello scelto come risposta.
Juan Catalan

20

Un'alternativa migliore (vedi la risposta di bneely al motivo per cui questo avviso ti sta salvando dal disastro) è usare il metodo swizzling. Utilizzando il metodo swizzling, è possibile sostituire un metodo esistente da una categoria senza l'incertezza di chi "vince" e preservando la capacità di richiamare il vecchio metodo. Il segreto è dare all'override un nome di metodo diverso, quindi scambiarli usando le funzioni di runtime.

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

Quindi definisci la tua implementazione personalizzata:

+ (UIFont *)mySystemFontOfSize:(CGFloat)fontSize {
...
}

Sostituisci l'implementazione predefinita con la tua:

MethodSwizzle([UIFont class], @selector(systemFontOfSize:), @selector(mySystemFontOfSize:));

10

Prova questo nel tuo codice:

+(void)load{
    EXCHANGE_METHOD(Method1, Method1Impl);
}

AGGIORNAMENTO2: aggiungi questa 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

1
Questo non è un esempio completo. Nessuna macro denominata EXCHANGE_METHOD è effettivamente definita dal runtime dell'obiettivo-c.
Richard J. Ross III

@Vitaly stil -1. quel metodo non è implementato per il tipo di classe. Quale framework stai usando?
Richard J. Ross III,

Scusa ancora, prova ho creato il file NSObject + MethodExchange
Vitaliy Gervazuk,

Con la categoria su NSObject perché preoccuparsi anche della macro? Perché non passare semplicemente a "exchangeMethod"?
hvanbrug

5

È possibile utilizzare il metodo swizzling per sopprimere questo avviso del compilatore. Ecco come ho implementato il metodo swizzling per disegnare i margini in un UITextField quando usiamo uno sfondo personalizzato 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

2

Le proprietà over-riding sono valide per un'estensione di classe (categoria anonima), ma non per una categoria normale.

Secondo Apple Docs utilizzando un'estensione di classe (categoria anonima) è possibile creare un'interfaccia privata per una classe pubblica, in modo tale che l'interfaccia privata possa sovrascrivere le proprietà esposte pubblicamente. cioè puoi cambiare una proprietà da readonly a readwrite.

Un caso d'uso per questo è quando si scrivono librerie che limitano l'accesso alle proprietà pubbliche, mentre la stessa proprietà richiede l'accesso completo in lettura e scrittura all'interno della libreria.

Collegamento a Apple Docs: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html

Cerca " Usa estensioni di classe per nascondere le informazioni private ".

Quindi questa tecnica è valida per un'estensione di classe, ma non per una categoria.


1

Le categorie sono una buona cosa, ma possono essere abusate. Quando scrivi le categorie, dovresti come principio NON reimplementare i metodi in uscita. Ciò potrebbe causare strani effetti collaterali poiché ora stai riscrivendo il codice che dipende da un'altra classe. potresti rompere una classe conosciuta e finire per ribaltare il tuo debugger. È semplicemente una cattiva programmazione.

Se hai bisogno di farlo, dovresti davvero sottoclassarlo.

Poi il suggerimento di swizzling, che è un grande NO-NO-NO per me.

Swizzing in fase di esecuzione è un completo NO-NO-NO.

Vuoi che una banana assomigli a un'arancia, ma solo in fase di esecuzione? Se vuoi un'arancia, scrivi un'arancia.

Non fare l'aspetto di una banana e agire come un'arancia. E peggio: non trasformare la tua banana in un agente segreto che saboterà silenziosamente le banane in tutto il mondo a sostegno delle arance.

Yikes!


3
Swizzing in fase di esecuzione può essere utile per deridere i comportamenti nell'ambiente di test, tuttavia.
Ben G

2
Anche se divertente, la tua risposta non fa altro che dire che tutti i metodi possibili sono cattivi. La natura della bestia è che a volte non puoi davvero sottoclassare, quindi ti rimane una categoria e soprattutto se non possiedi il codice per la classe che stai classificando, a volte devi fare lo swizzle, e essendo un metodo indesiderabile non è rilevante.
hvanbrug

1

Ho avuto questo problema quando ho implementato un metodo delegato in una categoria piuttosto che nella classe principale (anche se non c'era l'implementazione della classe principale). La soluzione per me era spostare dal file di intestazione della classe principale al file di intestazione della categoria. Funziona bene

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.