2009-09-10 12 views
24

Vorrei eseguire l'override di un metodo in una classe Objective C a cui non dispongo dell'origine.Utilizzo di Super in una categoria Objective C?

L'ho esaminato e sembra che le Categorie dovrebbero consentirmi di farlo, ma mi piacerebbe utilizzare il risultato del vecchio metodo nel mio nuovo metodo, usando super per ottenere il risultato dei vecchi metodi.

Ogni volta che provo questo, viene chiamato il mio metodo, ma "super" è nullo ... Qualche idea del perché? Sto facendo lo sviluppo di iPhone con l'SDK XCode 2.2. Sto sicuramente lavorando con un'istanza di una classe, e il metodo della classe è un metodo di istanza.

@implementation SampleClass (filePathResolver) 
-(NSString*) fullPathFromRelativePath:(NSString*) relPath 
{ 
    NSString *result = [super fullPathFromRelativePath: relPath]; 

    ... do some stuff with the old result 

    return result; 
} 

Nota e chiarimenti: Da quello che posso vedere in Apple Docs, mi sembra che questo dovrebbe essere permesso?

Categories docs at developer.apple.com: Quando una categoria sostituisce un metodo ereditato, il metodo nella categoria può, come al solito, invocare la implementazione ereditato tramite un messaggio a super. Tuttavia, se una categoria sostituisce un metodo già esistente nella classe della categoria , lo non consente di richiamare l'implementazione originale .

+0

Hai mai trovare una soluzione a questo? Sto provando a modificare il parametro di testo mentre viene passato nel setText: method di UILabel. Non voglio sottoclasse UILabel (enorme base di codice), quindi una categoria è probabilmente appropriata. Anche io penso che chiamare super setText farebbe il trucco, se il superoggetto fosse in realtà l'UILabel, ma poi, molto probabilmente porterebbe ad un ciclo infinito che chiama setText: definito nella categoria. Quindi è chiaro che questa è probabilmente un'impresa impossibile per una categoria. –

+0

Ehi! Non ho mai trovato altre soluzioni oltre a quelle pubblicate su questa pagina. Usa il metodo swizzling, o un metodo diverso in una categoria ;-( –

risposta

28

Le categorie estendono la classe originale, ma non la sottoclasse, pertanto una chiamata a super non trova il metodo.

Quello che vuoi si chiama Method Swizzling. Ma tieni presente che il tuo codice potrebbe rompere qualcosa. C'è un articolo su Theocacao written by Scot Stevenson su Method Swizzling nel vecchio runtime Objective-C, Cocoa with Love by Matt Gallagher ha un articolo su Method Swizzling nel nuovo runtime Objective-C 2.0 e un semplice sostituto per esso.

In alternativa, è possibile sottoclasse la classe e quindi utilizzare la sottoclasse o utilizzare + (void)poseAsClass:(Class)aClass per sostituire la superclasse. Di Apple scrive:

un metodo definito da una classe in posa può, attraverso un messaggio a super, incorporare il metodo della superclasse IT sostituzioni.

essere consapevoli del fatto che Apple ha deprecato poseAsClass: in Mac OS X 10.5.

+0

Sono un po 'turbato che ci sia un concetto di programmazione chiamato "swizzling" .Tuttavia, è un concetto interessante. –

+1

Come ho aggiunto come chiarimento sopra, cosa fa questo? Quando una categoria sovrascrive un metodo ereditato, il metodo nella categoria può, come al solito, richiamare l'implementazione ereditata tramite un messaggio a super.Tuttavia, se una categoria sostituisce un metodo già esistente nel classe, non c'è modo di invocare l'implementazione originale –

+0

Solo, non vuoi davvero il metodo swizzling a meno che non sia l'unico modo possibile per risolvere il problema. Anche allora, sappi che lo swizzling si accompagna a un mucchio di fragilità e problemi di manutenzione – bbum

8

Se vi sarà la codifica contro questa classe, è sufficiente rinominare il selettore per qualcosa che il tuo codice può usare, e chiamare il selettore originale su self:

@implementation SampleClass (filePathResolver) 
-(NSString*) myFullPathFromRelativePath:(NSString*) relPath 
{ 
    NSString *result = [self fullPathFromRelativePath: relPath]; 

    ... do some stuff with the old result 

    return result; 
} 

Se si desidera ignorare l'implementazione predefinita di questo selettore per quella classe, è necessario utilizzare l'approccio method swizzling.

+0

sì, non controllo tutto il codice scritto contro quella classe ... quindi non funziona per me, ma grazie per il suggerimento! –

+1

^ehm, sì lo fa. Il metodo swizzling kinda sembra un enorme hack e potenziale punto di rottura futuro del tuo programma, ma dovrebbe funzionare esattamente in quella situazione, in cui non hai il controllo sul codice scritto contro la classe. – n13

+0

beh, quello che intendevo è che c'è un altro codice che non controllo che chiama "fullPathFromRelativePath" su quella classe. Non riesco a cambiare ciò che fa l'altro codice, quindi in questo caso non mi sarebbe di aiuto lo swizzling. –

1

Non esattamente nella categoria, ma esiste una soluzione aggiungendo il metodo in modo dinamico in fase di esecuzione.Samuel Défago nel suo articolo descrive un modo pulito per creare implementazione IMP blocco chiamando super, il suo articolo originale può essere trovato here

Il codice di riferimento è:

#import <objc/runtime.h> 
#import <objc/message.h> 

    const char *types = method_getTypeEncoding(class_getInstanceMethod(clazz, selector)); 
    class_addMethod(clazz, selector, imp_implementationWithBlock(^(__unsafe_unretained id self, va_list argp) { 
     struct objc_super super = { 
      .receiver = self, 
      .super_class = class_getSuperclass(clazz) 
     }; 

     id (*objc_msgSendSuper_typed)(struct objc_super *, SEL, va_list) = (void *)&objc_msgSendSuper; 
     return objc_msgSendSuper_typed(&super, selector, argp); 
    }), types); 
Problemi correlati