web-dev-qa-db-fra.com

Comment puis-je ajouter des propriétés à un objet lors de l'exécution?

Est-il possible d'ajouter des propriétés à un objet Objective C lors de l'exécution?

41
cfischer

Il est possible d'ajouter des propriétés formelles à une classe via class_addProperty():

BOOL class_addProperty(Class cls,
    const char *name,
    const objc_property_attribute_t *attributes,
    unsigned int attributeCount)

Les deux premiers paramètres sont explicites. Le troisième paramètre est un tableau d'attributs de propriété, et chaque attribut de propriété est une paire nom-valeur qui suit Objective-C codages de type pour propriétés déclarées . Notez que la documentation mentionne toujours la chaîne séparée par des virgules pour le codage des attributs de propriété. Chaque segment de la chaîne séparée par des virgules est représenté par un objc_property_attribute_t exemple. En outre, objc_property_attribute_t accepte les noms de classe en plus du générique @ codage de type de id.

Voici un premier brouillon d'un programme qui ajoute dynamiquement une propriété appelée name à une classe qui a déjà une variable d'instance appelée _privateName:

#include <objc/runtime.h>
#import <Foundation/Foundation.h>

@interface SomeClass : NSObject {
    NSString *_privateName;
}
@end

@implementation SomeClass
- (id)init {
    self = [super init];
    if (self) _privateName = @"Steve";
    return self;
}
@end

NSString *nameGetter(id self, SEL _cmd) {
    Ivar ivar = class_getInstanceVariable([SomeClass class], "_privateName");
    return object_getIvar(self, ivar);
}

void nameSetter(id self, SEL _cmd, NSString *newName) {
    Ivar ivar = class_getInstanceVariable([SomeClass class], "_privateName");
    id oldName = object_getIvar(self, ivar);
    if (oldName != newName) object_setIvar(self, ivar, [newName copy]);
}

int main(void) {
    @autoreleasepool {
        objc_property_attribute_t type = { "T", "@\"NSString\"" };
        objc_property_attribute_t ownership = { "C", "" }; // C = copy
        objc_property_attribute_t backingivar  = { "V", "_privateName" };
        objc_property_attribute_t attrs[] = { type, ownership, backingivar };
        class_addProperty([SomeClass class], "name", attrs, 3);
        class_addMethod([SomeClass class], @selector(name), (IMP)nameGetter, "@@:");
        class_addMethod([SomeClass class], @selector(setName:), (IMP)nameSetter, "v@:@");

        id o = [SomeClass new];
        NSLog(@"%@", [o name]);
        [o setName:@"Jobs"];
        NSLog(@"%@", [o name]);
    }
}

Sa sortie (rognée):

Steve
Jobs

Les méthodes getter et setter doivent être écrites plus attentivement, mais cela devrait suffire comme exemple de la façon d'ajouter dynamiquement une propriété formelle à l'exécution.

62
user557219

Si vous regardez le protocole NSKeyValueCoding, documenté ici , vous pouvez voir qu'il y a un message appelé:

- (id)valueForUndefinedKey:(NSString *)key

Vous devez remplacer cette méthode pour fournir votre résultat personnalisé pour la propriété non définie spécifiée. Bien sûr, cela suppose que votre classe utilise le protocole correspondant.

Ce type d'approche est couramment utilisé pour fournir un comportement inconnu aux classes (par exemple, un sélecteur qui n'existe pas).

9
Jack

@properties - non (c'est-à-dire en utilisant la syntaxe à points, etc.). Mais vous pouvez ajouter du stockage en utilisant des objets associés: Comment utiliser objc_setAssociatedObject/objc_getAssociatedObject dans un objet? .

4
hypercrypt