Transformer (ou copyr) un object dans une instance de sous-class en Objective-C

Je veux transformer une instance d'un object en une instance d'une sous-class de cette class d'objects, de sorte que je puisse utiliser les methods et propriétés supplémentaires de cette sous-class, en Objective-C.

Comment puis-je le faire d'une manière qui ne nécessite pas de coder en dur les propriétés de cette class d'object dans une méthode de copy?

Il n'est pas possible de transformer un object en instance d'une sous-class en Objective-C. Cependant, avec la class ci-dessous, vous pouvez fournir une instance de l'object et de la sous-class et avoir les valeurs de toutes les propriétés copiées dans l'instance de la sous-class. Cette implémentation fonctionne avec les types d'objects Objective-C et les primitives C. Vous n'avez pas besoin de spécifier (ou même de déterminer) les propriétés qui doivent être copiées, sachant que les variables importantes sont visibles et peuvent être définies (par exemple, il n'y a pas de propriétés qui sont exposées en "lecture seule" ou non exposée du tout, dont les valeurs ne peuvent pas être recalculées par la class). Cette méthode est donc relativement robuste pour les classs connues et ne nécessitera pas de mise à jour pour prendre en charge les futures modifications que vous apportez à votre class d'objects qui correspondent à ces parameters. C'est iOS 8 compatible.

Cette class fournit quatre methods de class:

+ (id) copyObject:(id)object toSubclassObject:(id)subclassObject 

Copie toutes les propriétés de l'object dans subclassObject. Si subclassObject n'est pas une sous-class d'object, nil est renvoyé.

 + (NSDictionary *) propertiesOfObject:(id)object; 

Renvoie un dictionary de toutes les propriétés visibles d'un object, y compris celles de toutes ses superclasss (autres que NSObject).

 + (NSDictionary *) propertiesOfClass:(Class)class; 

Renvoie un dictionary de toutes les propriétés visibles d'une class, y compris celles de toutes ses superclasss (autres que NSObject).

 + (NSDictionary *) propertiesOfSubclass:(Class)class; 

Renvoie un dictionary de toutes les propriétés visibles spécifiques à une sous-class. Les propriétés de ses superclasss ne sont pas incluses.

Entête:

 // SYNUtilities.h #import <Foundation/Foundation.h> @interface SYNUtilities : NSObject + (id) copyObject:(id)object toSubclassObject:(id)subclassObject; + (NSDictionary *) propertiesOfObject:(id)object; + (NSDictionary *) propertiesOfClass:(Class)class; + (NSDictionary *) propertiesOfSubclass:(Class)class; @end 

La mise en oeuvre:

 #import "SYNUtilities.h" #import <objc/runtime.h> #import <objc/message.h> @implementation SYNUtilities + (id) copyObject:(id)object toSubclassObject:(id)subclassObject { if (![[subclassObject class] isSubclassOfClass:[object class]]) { return nil; } NSDictionary * properties = [self propertiesOfObject:object]; NSLog(@"Properties of %@:\n%@", [object class], properties); for (NSSsortingng * property in properties) { SEL selector = NSSelectorFromSsortingng(property); if (selector) { id value = [object valueForKey:property]; [subclassObject setValue:value forKey:property]; } } return subclassObject; } + (NSDictionary *) propertiesOfObject:(id)object { Class class = [object class]; return [self propertiesOfClass:class]; } + (NSDictionary *) propertiesOfClass:(Class)class { if (class == NULL) { return nil; } NSMutableDictionary * properties = [NSMutableDictionary dictionary]; [self propertiesForHierarchyOfClass:class onDictionary:properties]; return [NSDictionary dictionaryWithDictionary:properties]; } + (NSDictionary *) propertiesOfSubclass:(Class)class { if (class == NULL) { return nil; } NSMutableDictionary *properties = [NSMutableDictionary dictionary]; return [self propertiesForSubclass:class onDictionary:properties]; } + (NSMutableDictionary *)propertiesForHierarchyOfClass:(Class)class onDictionary:(NSMutableDictionary *)properties { if (class == NULL) { return nil; } if (class == [NSObject class]) { // On reaching the NSObject base class, return all properties collected. return properties; } // Collect properties from the current class. [self propertiesForSubclass:class onDictionary:properties]; // Collect properties from the superclass. return [self propertiesForHierarchyOfClass:[class superclass] onDictionary:properties]; } + (NSMutableDictionary *) propertiesForSubclass:(Class)class onDictionary:(NSMutableDictionary *)properties { unsigned int outCount, i; objc_property_t *objcProperties = class_copyPropertyList(class, &outCount); for (i = 0; i < outCount; i++) { objc_property_t property = objcProperties[i]; const char *propName = property_getName(property); if(propName) { const char *propType = getPropertyType(property); NSSsortingng *propertyName = [NSSsortingng ssortingngWithUTF8Ssortingng:propName]; NSSsortingng *propertyType = [NSSsortingng ssortingngWithUTF8Ssortingng:propType]; [properties setObject:propertyType forKey:propertyName]; } } free(objcProperties); return properties; } static const char *getPropertyType(objc_property_t property) { const char *atsortingbutes = property_getAtsortingbutes(property); char buffer[1 + strlen(atsortingbutes)]; strcpy(buffer, atsortingbutes); char *state = buffer, *atsortingbute; while ((atsortingbute = strsep(&state, ",")) != NULL) { if (atsortingbute[0] == 'T' && atsortingbute[1] != '@') { // AC primitive type: /* For example, int "i", long "l", unsigned "I", struct. Apple docs list plenty of examples of values returned. For a list of what will be returned for these primitives, search online for "Objective-c" "Property Atsortingbute Description Examples" */ NSSsortingng *name = [[NSSsortingng alloc] initWithBytes:atsortingbute + 1 length:strlen(atsortingbute) - 1 encoding:NSASCIISsortingngEncoding]; return (const char *)[name cSsortingngUsingEncoding:NSASCIISsortingngEncoding]; } else if (atsortingbute[0] == 'T' && atsortingbute[1] == '@' && strlen(atsortingbute) == 2) { // An Objective C id type: return "id"; } else if (atsortingbute[0] == 'T' && atsortingbute[1] == '@') { // Another Objective C id type: NSSsortingng *name = [[NSSsortingng alloc] initWithBytes:atsortingbute + 3 length:strlen(atsortingbute) - 4 encoding:NSASCIISsortingngEncoding]; return (const char *)[name cSsortingngUsingEncoding:NSASCIISsortingngEncoding]; } } return ""; } @end 

J'avais besoin de créer une sous-class de NSTextFieldCell , utilisée dans un NSTableView , et je voulais garder les propriétés intactes qui étaient définies pour la cellule dans Interface Builder.

J'ai résolu la tâche en utilisant NSKeyedArchiver , qui est fait pour stocker et restaurer les propriétés d'un object.

Puisque NSTextFieldCell implémente initWithCoder, il supporte les fonctions d'archiveur, et donc je pourrais utiliser ce code pour initer ma sous-class à partir des propriétés de l'autre:

 - (id)initWithCell:(NSCell *)cell { // Use NSArchiver to copy the NSCell's properties into our subclass NSMutableData *data = [NSMutableData data]; NSKeyedArchiver *arch = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; [cell encodeWithCoder:arch]; [arch finishEncoding]; NSKeyedUnarchiver *ua = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; self = [self initWithCoder:ua]; // Here I'd set up additional properties of my own class return self; }