- Published on
Objective-C Runtime in Practice
Class and Object Operation Functions
Class Operation Functions
The methods that runtime provides for operating on classes are mostly targeted at the various fields of the objc_class struct. Below we introduce these functions one by one, and conclude with a practical example demonstrating their usage.
Class Name (name)
The main functions for class name operations:
// Get the class name
const char * class_getName ( Class cls );
Raw
For
class_getName: if the passed-inclsisNil, an empty string is returned.
Superclass (super_class) and Metaclass (meta-class)
The main functions for superclass and metaclass operations:
// Get the superclass of a class
Class class_getSuperclass ( Class cls );
// Determine whether the given Class is a metaclass
BOOL class_isMetaClass ( Class cls );
class_getSuperclass: returnsNilwhenclsisNilorclsis a root class. You can usually use NSObject'ssuperclassmethod to achieve the same result.
class_isMetaClass: returnsYESifclsis a metaclass; returnsNOif not, or ifclsisNil.
Instance Variable Size (instance_size)
// Get the instance size
size_t class_getInstanceSize ( Class cls );
Member Variables (ivars) and Properties
In objc_class, all member variable and property information is stored in the linked list ivars. ivars is an array where each element is a pointer to Ivar (variable info). The runtime provides a rich set of functions for operating on this field, which can be broadly categorized as:
- Member variable operation functions, mainly including:
// Get info about a named instance member variable of a class
Ivar class_getInstanceVariable ( Class cls, const char *name );
// Get info about a class variable of a class
Ivar class_getClassVariable ( Class cls, const char *name );
// Add a member variable
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
// Get the complete list of member variables
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
class_getInstanceVariable: returns a pointer (Ivar) to theobjc_ivarstruct containing info about the member variable namedname.
class_getClassVariable: no information on class variables in Objective-C has been found; it is generally accepted that Objective-C does not support class variables. Note: the returned list does not include member variables and properties inherited from parent classes.
Objective-C does not support adding instance variables to existing classes — neither system-provided classes nor custom classes support it dynamically. However, if you want to add a member variable to a class created at runtime, you can use
class_addIvar. Note that this can only be called betweenobjc_allocateClassPairandobjc_registerClassPair, and the class cannot be a metaclass. The minimum byte alignment for a member variable is1<<alignment, which depends on the ivar's type and the machine's architecture. If the variable type is a pointer, passlog2(sizeof(pointer_type)).
class_copyIvarList: returns an array of pointers to member variable info, where each element is a pointer to theobjc_ivarstruct. This array does not include variables declared in parent classes. TheoutCountpointer returns the size of the array. You must usefree()to release this array.
- Property operation functions, mainly including:
// Get a specified property
objc_property_t class_getProperty ( Class cls, const char *name );
// Get the property list
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
// Add a property to a class
BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
// Replace a class's property
void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
These methods also operate on ivars, but only on those that are properties. We'll encounter these functions again when we cover properties later.
- On Mac OS X, you can use a garbage collector. The runtime provides several functions to determine whether an object's memory regions can be scanned by the garbage collector for strong/weak references:
const uint8_t * class_getIvarLayout ( Class cls );
void class_setIvarLayout ( Class cls, const uint8_t *layout );
const uint8_t * class_getWeakIvarLayout ( Class cls );
void class_setWeakIvarLayout ( Class cls, const uint8_t *layout );
Raw
Normally, you don't need to call these methods directly; a sensible layout is generated when objc_registerClassPair is called. These functions won't be detailed further here.
Methods (methodLists)
The main method operation functions:
// Add a method
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
// Get an instance method
Method class_getInstanceMethod ( Class cls, SEL name );
// Get a class method
Method class_getClassMethod ( Class cls, SEL name );
// Get an array of all methods
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
// Replace the implementation of a method
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
// Return the implementation of a method
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
// Determine whether a class instance responds to a given selector
BOOL class_respondsToSelector ( Class cls, SEL sel );
class_addMethodwill override the parent class's method implementation but will not replace an existing implementation in the same class — if the class already contains an implementation with the same name, the function returnsNO. To modify an existing implementation, usemethod_setImplementation. An Objective-C method is a simple C function that takes at least two parameters —selfand_cmd— so our implementation function (the function pointed to by theIMPparameter) must also take at least two parameters:
void myMethodIMP(id self, SEL _cmd)
{
// implementation ....
}
Unlike member variables, you can dynamically add methods to a class regardless of whether it already exists.
Also, the types parameter is a character array describing the types of the arguments passed to the method. This involves type encoding, which we'll cover later.
class_getInstanceMethodandclass_getClassMethod: unlikeclass_copyMethodList, both of these functions search the parent class's implementations as well.
class_copyMethodList: returns an array of all instance methods. To get class methods, useclass_copyMethodList(object_getClass(cls), &count)(a class's instance methods are defined in the metaclass). The list does not include methods implemented in parent classes. TheoutCountparameter returns the number of methods. After obtaining the list, callfree()to release it.
class_replaceMethod: its behavior splits into two cases — if the class does not have a method namedname, it acts likeclass_addMethodand adds the method; if the method already exists, it acts likemethod_setImplementationand replaces the original implementation.
class_getMethodImplementation: this function is called when a message is sent to a class instance, and returns a pointer to the method implementation function. It is faster thanmethod_getImplementation(class_getInstanceMethod(cls, name)). The returned pointer may point to a runtime-internal function rather than the actual method implementation — for example, if the class instance cannot respond to theselector, the returned pointer will be part of the runtime's message-forwarding mechanism.
class_respondsToSelector: we typically use NSObject'srespondsToSelector:orinstancesRespondToSelector:methods to achieve the same purpose.
Protocols (objc_protocol_list)
Protocol-related operations include:
// Add a protocol
BOOL class_addProtocol ( Class cls, Protocol *protocol );
// Return whether a class implements a given protocol
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
// Return the list of protocols implemented by a class
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
class_conformsToProtocolcan be replaced by NSObject'sconformsToProtocol:method.
class_copyProtocolListreturns an array; callfree()manually after use.
Version and Other Functions
Version-related operations, plus two functions the runtime provides for CoreFoundation's toll-free bridging:
// Get the version number
int class_getVersion ( Class cls );
// Set the version number
void class_setVersion ( Class cls, int version );
// We generally do not use these two functions directly
Class objc_getFutureClass ( const char *name );
void objc_setFutureClass ( Class cls, const char *name );
Example
Let's look at the code directly:
@interface MyClass : NSObject <NSCopying, NSCoding>
@property (nonatomic, strong) NSArray *array;
@property (nonatomic, copy) NSString *string;
- (void)method1;
- (void)method2;
+ (void)classMethod;
@end
@interface MyClass () {
NSInteger _instance1;
NSString *_instance2;
}
@property (nonatomic, assign) NSUInteger integer;
- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2;
@end
@implementation MyClass
+ (void)classMethod {
}
- (void)method1 {
NSLog(@"call method method1");
}
- (void)method2 {
}
- (void)method3WithArg1:(NSInteger)arg1 arg2:(NSString *)arg2 {
NSLog(@"arg1 : %ld, arg2 : %@", arg1, arg2);
}
@end
- (void)runtimeTest1 {
MyClass *myClass = [[MyClass alloc] init];
unsigned int outCount = 0;
Class cls = [myClass class];
// Class name
NSLog(@"-----%@-----", @"Class Name");
NSLog(@"class name: %s \n", class_getName(cls));
// Superclass
NSLog(@" ");
NSLog(@"-----%@-----", @"Superclass");
NSLog(@"super class name: %s", class_getName(class_getSuperclass(cls)));
// Is it a metaclass?
NSLog(@" ");
NSLog(@"-----%@-----", @"Is Metaclass");
NSLog(@"MyClass is %@ a meta-class", (class_isMetaClass(cls) ? @"" : @"not"));
Class meta_class = objc_getMetaClass(class_getName(cls));
NSLog(@"%s's meta-class is %s", class_getName(cls), class_getName(meta_class));
// Instance variable size
NSLog(@" ");
NSLog(@"-----%@-----", @"Instance Variable Size");
NSLog(@"instance size: %zu", class_getInstanceSize(cls));
// Member variables
NSLog(@" ");
NSLog(@"-----%@-----", @"Member Variables");
Ivar *ivars = class_copyIvarList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSLog(@"instance variable's name: %s at index: %d", ivar_getName(ivar), i);
}
free(ivars);
Ivar string = class_getInstanceVariable(cls, "_string");
if (string != NULL) {
NSLog(@"instace variable %s", ivar_getName(string));
}
// Property operations
NSLog(@" ");
NSLog(@"-----%@-----", @"Property Operations");
objc_property_t * properties = class_copyPropertyList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
NSLog(@"property's name: %s", property_getName(property));
}
free(properties);
objc_property_t array = class_getProperty(cls, "array");
if (array != NULL) {
NSLog(@"property %s", property_getName(array));
}
// Method operations
NSLog(@" ");
NSLog(@"-----%@-----", @"Method Operations");
Method *methods = class_copyMethodList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
Method method = methods[i];
NSLog(@"method's signature: %s", sel_getName(method_getName(method)));
}
free(methods);
Method method1 = class_getInstanceMethod(cls, @selector(method1));
if (method1 != NULL) {
NSLog(@"method %s", sel_getName(method_getName(method1)));
}
Method classMethod = class_getClassMethod(cls, @selector(classMethod));
if (classMethod != NULL) {
NSLog(@"class method : %s", sel_getName(method_getName(classMethod)));
}
NSLog(@"MyClass is%@ responsd to selector: method3WithArg1:arg2:", class_respondsToSelector(cls, @selector(method3WithArg1:arg2:)) ? @"" : @" not");
IMP imp = class_getMethodImplementation(cls, @selector(method1));
imp();
// Protocols
NSLog(@" ");
NSLog(@"-----%@-----", @"Protocols");
Protocol * __unsafe_unretained * protocols = class_copyProtocolList(cls, &outCount);
Protocol * protocol;
for (int i = 0; i < outCount; i++) {
protocol = protocols[i];
NSLog(@"protocol name: %s", protocol_getName(protocol));
}
NSLog(@"MyClass is%@ responsed to protocol %s", class_conformsToProtocol(cls, protocol) ? @"" : @" not", protocol_getName(protocol));
}
Output:
2016-02-23 14:37:25.214 runtime_demo -----Class Name-----
2016-02-23 14:37:25.215 runtime_demo class name: MyClass
2016-02-23 14:37:25.215 runtime_demo
2016-02-23 14:37:25.215 runtime_demo -----Superclass-----
2016-02-23 14:37:25.215 runtime_demo super class name: NSObject
2016-02-23 14:37:25.215 runtime_demo
2016-02-23 14:37:25.215 runtime_demo -----Is Metaclass-----
2016-02-23 14:37:25.215 runtime_demo MyClass is not a meta-class
2016-02-23 14:37:25.216 runtime_demo MyClass's meta-class is MyClass
2016-02-23 14:37:25.216 runtime_demo
2016-02-23 14:37:25.216 runtime_demo -----Instance Variable Size-----
2016-02-23 14:37:25.216 runtime_demo instance size: 48
2016-02-23 14:37:25.216 runtime_demo
2016-02-23 14:37:25.216 runtime_demo -----Member Variables-----
2016-02-23 14:37:25.216 runtime_demo instance variable's name: _instance1 at index: 0
2016-02-23 14:37:25.217 runtime_demo instance variable's name: _instance2 at index: 1
2016-02-23 14:37:25.217 runtime_demo instance variable's name: _array at index: 2
2016-02-23 14:37:25.217 runtime_demo instance variable's name: _string at index: 3
2016-02-23 14:37:25.217 runtime_demo instance variable's name: _integer at index: 4
2016-02-23 14:37:25.218 runtime_demo instace variable _string
2016-02-23 14:37:25.218 runtime_demo
2016-02-23 14:37:25.218 runtime_demo -----Property Operations-----
2016-02-23 14:37:25.218 runtime_demo property's name: array
2016-02-23 14:37:25.218 runtime_demo property's name: string
2016-02-23 14:37:25.218 runtime_demo property's name: integer
2016-02-23 14:37:25.218 runtime_demo property array
2016-02-23 14:37:25.219 runtime_demo
2016-02-23 14:37:25.219 runtime_demo -----Method Operations-----
2016-02-23 14:37:25.219 runtime_demo method's signature: method1
2016-02-23 14:37:25.219 runtime_demo method's signature: method3WithArg1:arg2:
2016-02-23 14:37:25.219 runtime_demo method's signature: method2
2016-02-23 14:37:25.219 runtime_demo method's signature: integer
2016-02-23 14:37:25.220 runtime_demo method's signature: setInteger:
2016-02-23 14:37:25.220 runtime_demo method's signature: array
2016-02-23 14:37:25.220 runtime_demo method's signature: string
2016-02-23 14:37:25.220 runtime_demo method's signature: setArray:
2016-02-23 14:37:25.222 runtime_demo method's signature: setString:
2016-02-23 14:37:25.222 runtime_demo method's signature: .cxx_destruct
2016-02-23 14:37:25.222 runtime_demo method method1
2016-02-23 14:37:25.222 runtime_demo class method : classMethod
2016-02-23 14:37:25.223 runtime_demo MyClass is responsd to selector: method3WithArg1:arg2:
2016-02-23 14:37:25.223 runtime_demo call method method1
2016-02-23 14:37:25.223 runtime_demo
2016-02-23 14:37:25.223 runtime_demo -----Protocols-----
2016-02-23 14:37:25.223 runtime_demo protocol name: NSCopying
2016-02-23 14:37:25.223 runtime_demo protocol name: NSCoding
2016-02-23 14:37:25.223 runtime_demo MyClass is responsed to protocol NSCoding
Dynamically Creating Classes and Objects
Dynamically Creating Classes
Dynamically creating a class involves the following functions:
// Create a new class and metaclass
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );
// Destroy a class and its associated metaclass
void objc_disposeClassPair ( Class cls );
// Register a class created with objc_allocateClassPair
void objc_registerClassPair ( Class cls );
objc_allocateClassPair: to create a root class, specifysuperclassasNil.extraBytesis usually 0; it represents the number of bytes to allocate at the end of the class and metaclass objects for indexedivars.
To create a new class, call objc_allocateClassPair. Then use functions like class_addMethod and class_addIvar to add methods, instance variables, and properties to the new class. Once done, call objc_registerClassPair to register the class so it can be used in the program.
Instance methods and instance variables should be added to the class itself, while class methods should be added to the class's metaclass.
objc_disposeClassPairdestroys a class. Note: if instances of the class or its subclasses still exist in the running program, do not call this on the class.
We've already seen these functions when discussing metaclasses. Here's another example to illustrate their usage:
- (void)runtimeTest2 {
Class cls = objc_allocateClassPair(MyClass.class, "MySubClass", 0);
class_addMethod(cls, @selector(submethod1), (IMP)imp_submethod1, "v@:");
class_replaceMethod(cls, @selector(method1), (IMP)imp_submethod1, "v@:");
class_addIvar(cls, "_ivar1", sizeof(NSString *), log(sizeof(NSString *)), "i");
objc_property_attribute_t type = {"T", "@\"NSString\""};
objc_property_attribute_t ownership = { "C", "" };
objc_property_attribute_t backingivar = { "V", "_ivar1"};
objc_property_attribute_t attrs[] = {type, ownership, backingivar};
class_addProperty(cls, "property2", attrs, 3);
objc_registerClassPair(cls);
id instance = [[cls alloc] init];
[instance performSelector:@selector(submethod1)];
[instance performSelector:@selector(method1)];
}
void imp_submethod1(id self,SEL _cmd){
NSLog(@"run sub method1:%p",_cmd);
}
Program output:
2016-02-23 15:06:08.079 runtime_demo[33746:4896485] run sub method1:0x1000f2a88
2016-02-23 15:06:08.080 runtime_demo[33746:4896485] run sub method1:0x1000f2a5e
Dynamically Creating Objects
The functions for dynamically creating objects are:
// Create a class instance
id class_createInstance ( Class cls, size_t extraBytes );
// Create a class instance at a specified location
id objc_constructInstance ( Class cls, void *bytes );
// Destroy a class instance
void * objc_destructInstance ( id obj );
Raw
class_createInstance: when creating an instance, memory is allocated for the class in the default memory area. TheextraBytesparameter specifies the number of extra bytes to allocate. These extra bytes can be used for instance variables beyond those defined in the class definition. This function cannot be used in an ARC environment.
Calling class_createInstance is similar to +alloc. However, when using class_createInstance you need to know exactly what you're doing. The following example uses NSString to test the function's actual behavior:
- (void)runtimeTest3 {
id theObject = class_createInstance(NSString.class, sizeof(unsigned));
id str1 = [theObject init];
NSLog(@"%@", [str1 class]);
id str2 = [[NSString alloc] initWithString:@"test"];
NSLog(@"%@", [str2 class]);
}
Output:
2016-02-23 16:17:00.676 runtime_demo[33883:4912917] NSString
2016-02-23 16:17:00.676 runtime_demo[33883:4912917] __NSCFConstantString
You can see that using class_createInstance returns an NSString instance rather than the default placeholder class __NSCFConstantString from the class cluster.
objc_constructInstance: creates a class instance at the specified location (bytes).
objc_destructInstance: destroys a class instance but does not free or remove any associated references.
Instance Operation Functions
Instance operation functions primarily target a series of operations on the instance objects we create. We can use these functions to obtain information from instance objects, such as the values of instance variables. These functions fall into three subcategories:
- Functions that operate on the entire object:
// Return a copy of the specified object
id object_copy ( id obj, size_t size );
// Free the memory occupied by the specified object
id object_dispose ( id obj );
Consider this scenario: we have class A and class B, where B is a subclass of A. B extends A by adding some extra properties. We've created an instance of A and want to convert it to a B instance at runtime so we can store data in B's properties. We can't convert directly because a B instance is larger than an A instance — there isn't enough space. The functions above can handle this:
- (void)runtimeTest4 {
NSObject *a = [[NSObject alloc] init];
id newB = object_copy(a, class_getInstanceSize(MyClass.class));
object_setClass(newB, MyClass.class);
object_dispose(a);
}
- Functions that operate on object instance variables:
// Modify the value of an instance variable of a class instance
Ivar object_setInstanceVariable ( id obj, const char *name, void *value );
// Get the value of an instance variable of an object
Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );
// Return a pointer to any extra bytes allocated with an object
void * object_getIndexedIvars ( id obj );
// Return the value of an instance variable in an object
id object_getIvar ( id obj, Ivar ivar );
// Set the value of an instance variable in an object
void object_setIvar ( id obj, Ivar ivar, id value );
If the Ivar of an instance variable is already known, calling object_getIvar is faster than object_getInstanceVariable; similarly, object_setIvar is faster than object_setInstanceVariable.
- Functions that operate on an object's class:
// Return the class name of a given object
const char * object_getClassName ( id obj );
// Return the class of an object
Class object_getClass ( id obj );
// Set the class of an object
Class object_setClass ( id obj, Class cls );
Getting Class Definitions
The Objective-C dynamic runtime automatically registers all classes defined in your code. You can also create class definitions at runtime and register them using objc_addClass. The runtime provides a series of functions to get class definition information, mainly:
// Get the list of registered class definitions
int objc_getClassList ( Class *buffer, int bufferCount );
// Create and return a pointer list to all registered classes
Class * objc_copyClassList ( unsigned int *outCount );
// Return the class definition for a specified class
Class objc_lookUpClass ( const char *name );
Class objc_getClass ( const char *name );
Class objc_getRequiredClass ( const char *name );
// Return the metaclass for a specified class
Class objc_getMetaClass ( const char *name );
objc_getClassList: gets the list of registered class definitions. We cannot assume that class objects obtained from this function inherit from theNSObjecthierarchy, so before calling methods on these classes, always check whether the method is implemented on the class.
The following code demonstrates how to use this function:
- (void)runtimeTest5 {
int numClasses;
Class * classes = NULL;
numClasses = objc_getClassList(NULL, 0);
if (numClasses > 0) {
classes = malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
NSLog(@"number of classes: %d", numClasses);
for (int i = 0; i < numClasses; i++) {
Class cls = classes[i];
NSLog(@"class name: %s", class_getName(cls));
}
free(classes);
}
}
Output:
2016-02-23 16:27:58.659 runtime_demo[33907:4916057] number of classes: 5017
2016-02-23 16:27:58.660 runtime_demo[33907:4916057] class name: CTMessageCenter
2016-02-23 16:27:58.660 runtime_demo[33907:4916057] class name: ABPersonLinker
......
......
There are three ways to get a class definition: objc_lookUpClass, objc_getClass, and objc_getRequiredClass. If the class is not registered at runtime, objc_lookUpClass returns nil, while objc_getClass invokes a class handler callback and checks again; if still not found, it returns nil. objc_getRequiredClass behaves the same as objc_getClass except it kills the process if the class is not found.
objc_getMetaClass: if the specified class is not registered, the function invokes a class handler callback and checks again; if still not registered, it returnsnil. However, every class definition must have a valid metaclass definition, so this function always returns a metaclass definition, whether or not it is valid.
Code:
All code from this post can be found on my GitHub runtime_demo.