No Magic
The key difference between Objective-C and Java is that Objective-C follows a philosophy of no magic. There is no virtual machine. The compiler turns Objective-C behavior into calls to the runtime library, which is written in C. You can call all of the various functions yourself. You can create new classes, add or replace methods in existing classes, get the types of methods and instance variables, and so on. You can also look up and call methods by name.
Some of these things require calling the runtime functions directly, others are exposed via NSObject. A simple example of this is calling a method by name. The first thing that you need to do is look up the selector. This is basically a unique string pointer. There are runtime functions for looking these up from C strings, or a Foundation function for getting them from Objective-C strings:
SEL sel = NSSelectorFromString(@"doSomething");
Note the at symbol before the string. This is used to create literal Objective-C string objects. You can also use this function with a string created at runtime. Once you have the selector, the next step is to call the method. There are three ways of doing this. On the Apple runtime, you can call the message send function directly:
objc_msgSend(object, sel);
You can also look up the function that implements the method and call that:
IMP method = [object methodForSelector: sel]; method(object, sel);
IMP stands for Instance Method Pointer, and is a standard Objective-C type for methods, with the signature described earlier. The highest-level way of doing this is to tell NSObject to send the message itself, like this:
[object performSelector: sel];
The important thing to remember is that all of the code in NSObject for doing this is implemented in terms of the runtime library functions. You can implement this behavior yourself.