2010-11-10 8 views
30

Ho una situazione in cui sembra che sia necessario aggiungere variabili di istanza a una categoria, ma dai documenti di Apple so che non posso farlo. Quindi mi sto chiedendo quale sia la migliore alternativa o soluzione alternativa.Variabili di istanza per le categorie C oggettivo

Quello che voglio fare è aggiungere una categoria che aggiunge funzionalità a UIViewControllers. Lo troverei utile in tutti i miei diversi UIViewControllers, indipendentemente dalla specifica sottoclasse di UIViewController che estendono, quindi penso che una categoria sia la soluzione migliore. Per implementare questa funzionalità, ho bisogno di diversi metodi e ho bisogno di tracciare i dati tra loro, quindi è questo che mi ha spinto a voler creare metodi di istanza.

Nel caso sia utile, ecco cosa voglio fare in particolare. Voglio rendere più facile tenere traccia quando la tastiera del software si nasconde e mostra, in modo da poter ridimensionare i contenuti nella mia vista. Ho scoperto che l'unico modo per farlo in modo affidabile è inserire il codice in quattro diversi metodi di UIViewController e tracciare dati aggiuntivi nelle variabili di istanza. Quindi quei metodi e variabili di istanza sono ciò che mi piacerebbe inserire in una categoria, quindi non devo copiare e incollare ogni volta che ho bisogno di gestire la tastiera del software. (Se esiste una soluzione più semplice per questo problema esatto, va anche bene - ma mi piacerebbe comunque conoscere la risposta alle variabili di istanza di categoria per riferimento futuro!)

+5

Puoi creare una sottoclasse? – kennytm

risposta

0

Perché non creare semplicemente una sottoclasse di UIViewController, aggiungere la funzionalità a quello, quindi usi quella classe (o una sua sottoclasse) invece?

+6

'UITableViewController' è una sottoclasse di' UIViewController'. Una categoria aggiungerà funzionalità a entrambi, mentre una sottoclasse non lo farà. –

+1

Sospetto che l'OP abbia incontrato sottoclassi di 'UIViewController' che non controllano. – Justin

+0

Vedo il tuo punto. Qualcosa sulla formulazione mi ha fatto pensare che si trattasse di sottoclassi UIViewController direttamente dell'OP stesso. Sospetto che KennyTM (basato sul suo commento) lo abbia letto nello stesso modo. :-) –

0

A seconda di ciò che si sta facendo, si consiglia di utilizzare i metodi di categorie statiche.

Quindi, suppongo che hai questo tipo di problema:

ScrollView ha un paio di textedits in loro. Tipi di utente su modifica del testo, si desidera scorrere la vista di scorrimento in modo che la modifica del testo sia visibile sopra la tastiera.

+ (void) staticScrollView: (ScrollView*)sv scrollsTo:(id)someView 
{ 
    // scroll view to someviews's position or some such. 
} 

il ritorno da questo non richiede necessariamente la vista di tornare indietro, e quindi non è necessario memorizzare nulla.

Ma questo è tutto quello che posso pensare senza esempi di codice, mi dispiace.

0

Credo che sia possibile aggiungere variabili a una classe utilizzando il runtime Obj-C.
Ho trovato anche this discussion.

+0

Questo è diventato iOS? Non ho tenuto il passo con questa nuova funzione (mi sembra così sporca :-)). –

7

Credo che sia ora possibile aggiungere proprietà sintetizzate a una categoria e le variabili di istanza vengono create automaticamente, ma non l'ho mai provato quindi non sono sicuro che funzionerà.

Una soluzione più hacky:

Creare un Singleton NSDictionary che avrà l'UIViewController come la chiave (o meglio il suo indirizzo avvolto come un NSValue) e il valore della vostra proprietà come valore.

Creare getter e setter per la proprietà che effettivamente va al dizionario per ottenere/impostare la proprietà.

@interface UIViewController(MyProperty) 

@property (nonatomic, retain) id myProperty; 
@property (nonatomic, readonly, retain) NSMutableDcitionary* propertyDictionary; 

@end 

@implementation UIViewController(MyProperty) 

-(NSMutableDictionary*) propertyDictionary 
{ 
    static NSMutableDictionary* theDictionary = nil; 
    if (theDictionary == nil) 
    { 
     theDictioanry = [[NSMutableDictionary alloc] init]; 
    } 
    return theDictionary; 
} 


-(id) myProperty 
{ 
    NSValue* key = [NSValue valueWithPointer: self]; 
    return [[self propertyDictionary] objectForKey: key]; 
} 

-(void) setMyProperty: (id) newValue 
{ 
    NSValue* key = [NSValue valueWithPointer: self]; 
    [[self propertyDictionary] setObject: newValue forKey: key];  
} 

@end 

Due potenziali problemi con l'approccio di cui sopra:

  • non c'è modo per rimuovere le chiavi di controller di vista che sono stati disallocati. Finché tieni traccia solo di una manciata, questo non dovrebbe essere un problema.Oppure potresti aggiungere un metodo per eliminare una chiave dal dizionario una volta che sai di averne finito.
  • Non sono sicuro al 100% che isEqual: il metodo di NSValue confronta il contenuto (ovvero il puntatore avvolto) per determinare l'uguaglianza o se si confronta solo per vedere se l'oggetto di confronto è esattamente lo stesso NSValue. In quest'ultimo caso, sarà necessario utilizzare NSNumber anziché NSValue per i tasti (NSNumber numberWithUnsignedLong: eseguirà il trucco su entrambe le piattaforme a 32 bit e 64 bit).
+4

.... oppure puoi semplicemente usare un riferimento associato –

26

Sì, è possibile fare questo, ma dal momento che si sta chiedendo, devo chiedere: Sei assolutamente sicuro che è necessario? (Se dici "sì", quindi torna indietro, scopri cosa vuoi fare e vedi se c'è un modo diverso per farlo)

Tuttavia, se vuoi davvero inserire la memoria in una classe che non usi t controllo, utilizzare un associative reference.

+0

Non sapevo di riferimenti associativi, bel consiglio! –

+4

Nuovo collegamento di riferimento associativo: http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocAssociativeReferences.html – lilbyrdie

+0

@lilbyrdie grazie ho risolto il collegamento :) –

14

Recentemente, avevo bisogno di fare questo (aggiungere stato a una categoria). @Dave DeLong ha la prospettiva corretta su questo. Nella ricerca dell'approccio migliore, ho trovato un ottimo blog post di Tom Harrington. Mi piace l'idea di @ JeremyP di usare le dichiarazioni @property sulla categoria, ma non la sua particolare implementazione (non un fan del global singleton o dei riferimenti globali). I riferimenti associativi sono la strada da percorrere.

Ecco il codice per aggiungere (ciò che sembra essere) ivars alla tua categoria. Ho bloggato su questo in dettaglio here.

Nel file.h, il chiamante vede solo il pulito, di alto livello di astrazione:

@interface UIViewController (MyCategory) 
@property (retain,nonatomic) NSUInteger someObject; 
@end 

In File.m, siamo in grado di implementare il @property (NOTA: Queste non possono essere @ synthesize'd) :

@implementation UIViewController (MyCategory) 

- (NSUInteger)someObject 
{ 
    return [MyCategoryIVars fetch:self].someObject; 
} 

- (void)setSomeObject:(NSUInteger)obj 
{ 
    [MyCategoryIVars fetch:self].someObject = obj; 
} 

Inoltre, è necessario dichiarare e definire la classe MyCategoryIVars. Per facilità di comprensione, l'ho spiegato per ordine di compilazione. L'interfaccia @ deve essere posizionata prima di Category @ implementation.

@interface MyCategoryIVars : NSObject 
@property (retain,nonatomic) NSUInteger someObject; 
+ (MyCategoryIVars*)fetch:(id)targetInstance; 
@end 

@implementation MyCategoryIVars 

@synthesize someObject; 

+ (MyCategoryIVars*)fetch:(id)targetInstance 
{ 
    static void *compactFetchIVarKey = &compactFetchIVarKey; 
    MyCategoryIVars *ivars = objc_getAssociatedObject(targetInstance, &compactFetchIVarKey); 
    if (ivars == nil) { 
    ivars = [[MyCategoryIVars alloc] init]; 
    objc_setAssociatedObject(targetInstance, &compactFetchIVarKey, ivars, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
    [ivars release]; 
    } 
    return ivars; 
} 

- (id)init 
{ 
    self = [super init]; 
    return self; 
} 

- (void)dealloc 
{ 
    self.someObject = nil; 
    [super dealloc]; 
} 

@end 

Il codice sopra dichiara e implementa la classe che contiene i nostri ivars (someObject). Dato che non possiamo davvero estendere UIViewController, questo dovremo fare.

+1

Nel tuo esempio implementazione, a cosa serve l'override '-init'? Sembra essere un no-op. Grazie! – Olie

+1

@Olie, hai ragione non è necessario. Tendo ad essere un piccolo OCD con la mia codifica. Dato che c'è un dealloc, mi piace fornire un init corrispondente per ricordarmi di toccare sia se/quando aggiungere risorse all'oggetto. Puoi eliminarlo in sicurezza. –

+0

Nessun problema. Siccome sono nuovo di 'objc_setAssociatedObject()', non ero sicuro se ci fosse qualche strano effetto collaterale desiderabile, lì. Grazie! – Olie

5

Questo è il modo migliore per utilizzare la funzione ObjC incorporata. Oggetti associati (noti anche come riferimenti associati), nell'esempio seguente è sufficiente passare alla categoria e sostituire associatoObject con il nome della variabile.

NSObject + AssociatedObject.h

@interface NSObject (AssociatedObject) 
@property (nonatomic, strong) id associatedObject; 
@end 

NSObject + AssociatedObject.m

#import <objc/runtime.h> 

@implementation NSObject (AssociatedObject) 
@dynamic associatedObject; 

- (void)setAssociatedObject:(id)object { 
    objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
} 

- (id)associatedObject { 
    return objc_getAssociatedObject(self, @selector(associatedObject)); 
} 

Vedi qui per il tutorial completo:

http://nshipster.com/associated-objects/

3

Ha menzionato in molti documenti online che non è possibile creare creare nuove variabili nella categoria, ma ho trovato un modo molto semplice per raggiungere questo obiettivo. Ecco come dichiarare la nuova variabile nella categoria.

In Your.file di

@interface UIButton (Default) 

    @property(nonatomic) UIColor *borderColor; 

@end 

h Nel file .m

#import <objc/runtime.h> 
static char borderColorKey; 

@implementation UIButton (Default) 

- (UIColor *)borderColor 
{ 
    return objc_getAssociatedObject(self, &borderColorKey); 
} 

- (void)setBorderColor:(UIColor *)borderColor 
{ 
    objc_setAssociatedObject(self, &borderColorKey, 
          borderColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
    self.layer.borderColor=borderColor.CGColor; 
} 

@end 

Questo è tutto ora avete la nuova variabile.

Problemi correlati