2010-08-24 12 views
16

Supponiamo (per ragioni di discussione) di avere una classe vista che contiene un NSDictionary. Voglio un sacco di proprietà, tutte che accedono ai membri di quel dizionario.Scrivere le proprie proprietà @dynamic in Cocoa

Ad esempio, voglio @property NSString* title e @property NSString* author.

Per ognuna di queste proprietà, l'implementazione è la stessa: per il getter, chiamare [dictionary objectForKey:propertyName]; e per il setter fare lo stesso con setObject: forKey :.

Ci vorrebbe un sacco di tempo e usare un sacco di codice copia-e-incolla per scrivere tutti quei metodi. C'è un modo per generarli tutti automaticamente, come Core Data fa con le proprietà @dynamic per le sottoclassi NSManagedObject? Per essere chiari, voglio solo questo mezzo di accesso per le proprietà che definisco nell'intestazione, non solo qualsiasi chiave arbitraria.

Ho trovato valueForUndefinedKey: come parte della codifica del valore chiave, che potrebbe gestire i getter, ma non sono del tutto sicuro se questo è il modo migliore per andare.

Ho bisogno che queste siano proprietà esplicite, quindi posso associarle a Interface Builder: alla fine ho intenzione di scrivere una tavolozza IB per questa vista.

(BTW, so che il mio esempio di utilizzo di un NSDictionary per archiviarli è un po 'forzato. In realtà sto scrivendo una sottoclasse di WebView e le proprietà faranno riferimento agli ID degli elementi HTML, ma ciò non è importante per logica della mia domanda!)

risposta

17

sono riuscito a risolvere questo me stesso dopo avere versato sopra la documentazione Objective-C runtime.

ho attuato questo metodo classe:

+ (BOOL) resolveInstanceMethod:(SEL)aSEL 
{ 
    NSString *method = NSStringFromSelector(aSEL); 

    if ([method hasPrefix:@"set"]) 
    { 
     class_addMethod([self class], aSEL, (IMP) accessorSetter, "[email protected]:@"); 
     return YES; 
    } 
    else 
    { 
     class_addMethod([self class], aSEL, (IMP) accessorGetter, "@@:"); 
     return YES; 
    } 
    return [super resolveInstanceMethod:aSEL]; 
} 

Seguito da una coppia di funzioni C:

NSString* accessorGetter(id self, SEL _cmd) 
{ 
    NSString *method = NSStringFromSelector(_cmd); 
    // Return the value of whatever key based on the method name 
} 

void accessorSetter(id self, SEL _cmd, NSString* newValue) 
{ 
    NSString *method = NSStringFromSelector(_cmd); 

    // remove set 
    NSString *anID = [[[method stringByReplacingCharactersInRange:NSMakeRange(0, 3) withString:@""] lowercaseString] stringByReplacingOccurrencesOfString:@":" withString:@""]; 

    // Set value of the key anID to newValue 
} 

Poichè si tenta di realizzare qualsiasi metodo chiamato sulla classe e non già attuato , causerà problemi se qualcuno prova a chiamare qualcosa che stai aspettando. Ho in programma di aggiungere alcuni controlli di integrità, per assicurarmi che i nomi corrispondano a quello che mi aspetto.

+2

Solo il tweak che farei è solo la minuscola della prima lettera (e assicurarsi che la stringa _cmd abbia almeno 5 caratteri): NSString * property = NSStringFromSelector (_cmd); NSString * firstLetter = [[proprietà substringWithRange: NSMakeRange (3, 1)] lowercaseString]; proprietà = [firstLetter stringByAppendingString: [proprietà substringWithRange: NSMakeRange (4, [proprietà length] - 5)]]; – charles

+0

Ottimo esempio. Ero arrivato a una specie di IMP (per esempio: setName: "il mio IMP era' setValueForName') ma il tuo uso di 'NSStringFromSelector (_cmd)' mi dava esattamente il calcio nei pantaloni mentali che stavo cercando generalizza a 'setWhatever:'. Grazie per averlo scritto. – matt

0

sembra che dovresti usare una classe invece di un dizionario. ti stai avvicinando a implementare a mano ciò che la lingua sta cercando di darti.

+1

Sì, la cosa del dizionario era un esempio forzato. In realtà sto usando questo per creare una vista per un documento HTML a cui è possibile connettersi con i collegamenti. Hai ragione, se questo è tutto ciò che stavo facendo, sarebbe un brutto piano :) –

2

È possibile utilizzare una combinazione di opzioni suggerite:

  • utilizzare la parola chiave @dynamic
  • sovrascrittura valueForKey: e setValue: Forkey: per accedere al dizionario
  • utilizzare i Objective-C riflessione API metodo class_getProperty e controllalo per nil. Se non è zero, la tua classe ha una tale proprietà. Non lo è se lo è.
  • quindi chiamare il metodo super nei casi in cui non esiste alcuna proprietà di questo tipo.

Spero che questo aiuti. Potrebbe sembrare un po 'hacky (usando la riflessione) ma in realtà questa è una soluzione molto flessibile e anche assolutamente "legale" per il problema ...

PS: il modo coredata è possibile ma sarebbe totale overkill nel tuo caso ..

+0

Aggiungerò il bit class_getProperty alla mia soluzione, penso. Grazie. –

2

Fare amicizia con una macro? Questo potrebbe non essere corretto al 100%.

#define propertyForKey(key, type) \ 
    - (void) set##key: (type) key; \ 
    - (type) key; 

#define synthesizeForKey(key, type) \ 
    - (void) set##key: (type) key \ 
    { \ 
     [dictionary setObject];// or whatever \ 
    } \ 
    - (type) key { return [dictionary objectForKey: key]; } 
Problemi correlati