It is not possible to transform an object into an instance of a subclass in Objective-C. However, with the class below you can supply an instance of both the object and the subclass and have the values of all properties copied to the subclass instance. This implementation works with both Objective-C object types and C primitives. You do not have to specify (or indeed even determine) the properties that need to be copied, providing you know that the important variables are visible and can be set (i.e., there are no properties that are exposed as "read only" or not exposed at all, whose values cannot be recalculated by the class). This method is thus relatively robust for known classes and will not require updating to support future changes you make in your object class that fit these parameters. It is iOS 8 compatible.
This class provides four class methods:
+ (id) copyObject:(id)object toSubclassObject:(id)subclassObject
Copies all properties of object to subclassObject. If the subclassObject is not a subclass of object, nil is returned.
+ (NSDictionary *) propertiesOfObject:(id)object;
Returns a dictionary of all visible properties of an object, including those from all its superclasses (other than NSObject).
+ (NSDictionary *) propertiesOfClass:(Class)class;
Returns a dictionary of all visible properties of a class, including those from all its superclasses (other than NSObject).
+ (NSDictionary *) propertiesOfSubclass:(Class)class;
Returns a dictionary of all visible properties that are specific to a subclass. Properties for its superclasses are not included.
Header:
// 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
Implementation:
#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 (NSString * property in properties) {
SEL selector = NSSelectorFromString(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);
NSString *propertyName = [NSString stringWithUTF8String:propName];
NSString *propertyType = [NSString stringWithUTF8String:propType];
[properties setObject:propertyType forKey:propertyName];
}
}
free(objcProperties);
return properties;
}
static const char *getPropertyType(objc_property_t property) {
const char *attributes = property_getAttributes(property);
char buffer[1 + strlen(attributes)];
strcpy(buffer, attributes);
char *state = buffer, *attribute;
while ((attribute = strsep(&state, ",")) != NULL) {
if (attribute[0] == 'T' && attribute[1] != '@') {
// A C 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 Attribute Description Examples"
*/
NSString *name = [[NSString alloc] initWithBytes:attribute + 1 length:strlen(attribute) - 1 encoding:NSASCIIStringEncoding];
return (const char *)[name cStringUsingEncoding:NSASCIIStringEncoding];
}
else if (attribute[0] == 'T' && attribute[1] == '@' && strlen(attribute) == 2) {
// An Objective C id type:
return "id";
}
else if (attribute[0] == 'T' && attribute[1] == '@') {
// Another Objective C id type:
NSString *name = [[NSString alloc] initWithBytes:attribute + 3 length:strlen(attribute) - 4 encoding:NSASCIIStringEncoding];
return (const char *)[name cStringUsingEncoding:NSASCIIStringEncoding];
}
}
return "";
}
@end