分享

iOS - Objective-C - Advanced: Creating a Class at Runtime

 最初九月雪 2017-03-22

Yes, this may be an edge case, but it is a problem that you may run into one day. For example, say you're writing an App that requires you to accept any sort of random json or xml. While you could just consume them in dictionary format, it maybe not actually feasible for you to work with data in that format. And thus i present you with a way to create a class dynamically at runtime. 

So the way this will work, is we'll have one method you call into that will generate a class based of a list of properties that you give it. 

First lets just identify some helper methods i used to create all the strings we need from those properties that you give it. i.e. the SetterName, iVar Name, and the Property name:

+(NSString*)propName:(NSString*)name
{
   name = [name stringByReplacingOccurrencesOfString:@":" withString:@""];

   NSRange r;
   r.length = name.length -1 ;
   r.location = 1;

   NSString* firstChar = [name stringByReplacingCharactersInRange:r withString:@""];

   if([firstChar isEqualToString:[firstChar lowercaseString]])
   { 
      return name;
   }

   r.length = 1;
   r.location = 0;

   NSString* theRest = [name stringByReplacingCharactersInRange:r withString:@""];

   return [NSString stringWithFormat:@"%@%@", [firstChar lowercaseString] , theRest];

}

+(NSString*)setterName:(NSString*)name
{
   name = [self propName:name];

   NSRange r;
   r.length = name.length -1 ;
   r.location = 1;

   NSString* firstChar = [name stringByReplacingCharactersInRange:r withString:@""];

   r.length = 1;
   r.location = 0;

   NSString* theRest = [name stringByReplacingCharactersInRange:r withString:@""];

   return [NSString stringWithFormat:@"set%@%@", [firstChar uppercaseString] , theRest];
}


+(NSString*)propNameFromSetterName:(NSString*)name
{
   NSRange r;
   r.length = 3 ;
   r.location = 0;

   NSString* propName = [name stringByReplacingCharactersInRange:r withString:@""];

   return [self propName:propName];
}

+(NSString*)ivarName:(NSString*)name
{
   NSRange r;
   r.length = name.length -1 ;
   r.location = 1;

   NSString* firstChar = [name stringByReplacingCharactersInRange:r withString:@""].lowercaseString;

   if([firstChar isEqualToString:@"_"])
      return name;

   r.length = 1;
   r.location = 0;

   NSString* theRest = [name stringByReplacingCharactersInRange:r withString:@""];

   return [NSString stringWithFormat:@"_%@%@",firstChar, theRest];
}

The next thing we need to do is construct the Getter and Setter methods that our class' properties will use:

Getter:

NSObject *getter(id self, SEL _cmd)
{
   NSString* name = NSStringFromSelector(_cmd);
   NSString* ivarName = [self ivarName:name];
  Ivar ivar = class_getInstanceVariable([self class], [ivarName UTF8String]);
   return object_getIvar(self, ivar);
}

Setter:

void setter(id self, SEL _cmd, NSObject *newObj)
{
   NSString* name = [self propNameFromSetterName:NSStringFromSelector(_cmd)];
   NSString* ivarName = [self ivarName:name];
   Ivar ivar = class_getInstanceVariable([self class], [ivarName UTF8String]);
   id oldObj = object_getIvar(self, ivar);
   if (![oldObj isEqual: newObj])
   {
      if(oldObj != nil)
        [oldObj release];

      object_setIvar(self, ivar, newObj);
      [newObj retain];
   }
}

And finally, we'll define our method for creating the class. It will take 2 parameters, an array of property names and a name for your dynamic class:

+(NSDictionary*)buildClassFromDictionary:(NSArray*)propNames withName:(NSString*)className

{

    NSMutableDictionary* keys = [[NSMutableDictionary alloc]init];

    

    Class newClass = NSClassFromString(className);

    

    if(newClass == nil)

    {

        newClass = objc_allocateClassPair([NSObject class], [className UTF8String], 0);

        

        for(NSString* key in propNames)

        {

            NSString* propName = [self propName: key];

            NSString* iVarName = [self ivarName:propName];

            

            class_addIvar(newClass, [iVarName UTF8String] , sizeof(NSObject*), log2(sizeof(NSObject*)), @encode(NSObject));

            

            objc_property_attribute_t a1 = { "T", "@\"NSObject\"" };

            objc_property_attribute_t a2 = { "&", "" };

            objc_property_attribute_t a3 = { "N", "" };

            objc_property_attribute_t a4 = { "V", [iVarName UTF8String] };

            objc_property_attribute_t attrs[] = { a1, a2, a3, a4};

            

            class_addProperty(newClass, [propName UTF8String], attrs, 4);

            class_addMethod(newClass, NSSelectorFromString(propName), (IMP)getter, "@@:");

            class_addMethod(newClass, NSSelectorFromString([self setterName:propName]), (IMP)setter, "v@:@");

            

            [keys setValue:key forKey:propName];

        }

        

        objc_registerClassPair(newClass);

    }

    

    return keys;

}

Note, you must include the following import statement at the top of your file:

#import <objc/runtime.h>

You may notice that our method returns a dictionary. The dictionary contains the original property names passed in to the method as keys with a value of the actual property name that was defined. 

If we wanted to invoke our class it could look something like:

[self buildClassFromDictionary:@[@"FirstName", @"LastName", @"Age", @"Gender"] withName:@"Person"]

This in turn would create a Person class, that i can now use to create my objects dynamically. 

Hope you found this useful!

By Stephen Zaharuk (SteveZ)


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多