Haben Sie jemals Adapter für Keychain oder NSUserDefaults geschrieben? Sie bestehen vollständig aus den gleichen Setzern und Gettern. Ich schlage vor, die Logik einmal zu schreiben und den Rest für die Laufzeit bereitzustellen. Zur Umsetzung bitte ich um Katze.

Hallo. Mit dir wieder vdugnist von FunCorp. Beim Hinzufügen eines neuen Felds zum Schlüsselbundadapter ist kürzlich ein Fehler beim Kopieren von Code von einer benachbarten Methode aufgetreten.
Wie die Implementierung zuvor ausgesehen hat:
- (Credentials *)credentials { // implementation details } - (void)setCredentials:(Credentials *)credentials { // implementation details } - (NSDate *)firstLaunchDate { // implementation details } - (void)setFirstLaunchDate:(NSDate *)date { // implementation details }
Der Code wurde einfach von der nächsten Methode kopiert und die Konstanten darin geändert. Es ist klar, dass Sie mit diesem Ansatz leicht einen Fehler machen können. Nach einigem Refactoring stellte sich heraus, dass die gesamte Implementierung der Klassenlogik in zwei Methoden ausgeführt wurde:
- (Credentials *)credentials { return [self objectFromKeychainForKey:@"credentials"]; } - (void)setCredentials:(Credentials *)credentials { [self setObject:credentials toKeychainForKey:@"credentials"]; } - (NSDate *)firstLaunchDate { return [self objectFromKeychainForKey:@"firstLaunchDate"]; } - (void)setFirstLaunchDate:(NSDate *)firstLaunchDate { [self setObject:firstLaunchDate toKeychainForKey:@"firstLaunchDate"]; } - (void)setObject:(id)obj toKeychainForKey:(NSString *)key {
Schon besser. Es bleiben jedoch noch zwei Probleme:
- Sie können weiterhin in der Zeichenfolgenkonstante versiegelt werden, die wir an die Methode übergeben.
- Die gesamte Klasse besteht aus Methoden mit derselben Implementierung, die sich nur im Argument der aufgerufenen Methode unterscheiden.
Und hier kommt Hilfe zu uns. Wenn in Objective-C @property zur Klassenschnittstelle hinzugefügt wird, werden Setter, Getter und Ivar automatisch generiert. In der Standard-Setter-Implementierung wird der Wert in ivar geschrieben und für den Getter aus ivar gelesen. Damit diese Methoden nicht generiert werden, müssen Sie in der Klassenimplementierung dynamisches <Feldname> schreiben. Wenn wir dann auf das Feld zugreifen, wird ein nicht erkannter Selektor an die Instanzausnahme gesendet.
Vor dem Senden einer Ausnahme +(BOOL)resolveInstanceMethod:(SEL)sel
die Klasse im Fall der Instanzeigenschaft die Methode +(BOOL)resolveClassMethod:(SEL)sel
im Fall der Eigenschaft class +(BOOL)resolveClassMethod:(SEL)sel
auf.
In ihnen können Sie die Methodenimplementierung per Selektor mit class_addMethod
und YES
wenn alles reibungslos class_addMethod
. Danach wird die Implementierung der neu hinzugefügten Methode für die aktuellen und nachfolgenden Aufrufe aufgerufen.
Um eine Methode zur Laufzeit hinzuzufügen, benötigen Sie einen Zeiger auf die Klasse, zu der die Methode hinzugefügt wird, einen Selektor, einen Implementierungsblock und eine Methodensignatur. Die Dokumentation zum letzten Argument finden Sie hier .
Ich habe mich sofort entschlossen, eine Lösung für mein Problem in der Bibliothek zu finden, sodass sowohl die Klasseneigenschaft als auch die Instanzeigenschaft im Beispiel verarbeitet wurden. Das Beispiel verwendet Hilfsfunktionen, die Implementierung finden Sie hier .
+ (BOOL)resolveClassMethod:(SEL)sel { return [self resolveMethodFor:object_getClass(self) selector:sel]; } + (BOOL)resolveInstanceMethod:(SEL)sel { return [self resolveMethodFor:self selector:sel]; } + (BOOL)resolveMethodFor:(id)target selector:(SEL)sel { if (!sel_isGetterOrSetter(target, sel)) { return NO; } objc_property_t property = propertyForSelector(target, sel); if (sel_isSetter(target, sel)) { SEL getterSel = sel_getterFromSetter(sel); dvPropertySetterBlock setterBlock = [self setterBlockForTarget:target getterSelector:getterSel]; IMP blockImplementation = imp_implementationWithBlock(setterBlock); char *methodTypes = copySetterMethodTypesForProperty(property); assert(class_addMethod(target, sel, blockImplementation, methodTypes)); free(methodTypes); } else { dvPropertyGetterBlock getterBlock = [self getterBlockForTarget:target getterSelector:sel]; IMP blockImplementation = imp_implementationWithBlock(getterBlock); char *methodTypes = copyGetterMethodTypesForProperty(property); assert(class_addMethod(target, sel, blockImplementation, methodTypes)); free(methodTypes); } return YES; } + (dvPropertySetterBlock)setterBlockForTarget:(id)target getterSelector:(SEL)getterSelector { @throw @"Override this method in subclass"; } + (dvPropertyGetterBlock)getterBlockForTarget:(id)target getterSelector:(SEL)getterSelector { @throw @"Override this method in subclass"; }
Bei den Erben reicht es aus, zwei Methoden neu zu definieren (Getterblock und Setterblock), der Schnittstelle @property hinzuzufügen und der Implementierung dynamisch zu sein. Hier ist zum Beispiel eine Implementierung eines Adapters für NSUserDefaults:
+ (dvPropertySetterBlock)setterBlockForTarget:(id)target getterSelector:(SEL)getterSelector { return ^(id blockSelf, id value) { [[NSUserDefaults standardUserDefaults] setObject:value forKey:NSStringFromSelector(getterSelector)]; }; } + (dvPropertyGetterBlock)getterBlockForTarget:(id)target getterSelector:(SEL)getterSelector { return ^id(id blockSelf) { return [[NSUserDefaults standardUserDefaults] objectForKey:NSStringFromSelector(getterSelector)]; }; }
Die Bibliothek selbst befindet sich auf dem Github , und ich bin bereit, Ihre Fragen in den Kommentaren zu beantworten.