Link to the original article: a large number of objective-c developers have emerged in the past few years at http://pilky.me/view/21. Some are transferred from dynamic languages, such as Ruby or Python, and some are transferred from strong languages, such as Java or C #, of course, Objective-C is also directly used as the entry language. That is to say, a large number of developers have not used Objective-C for a long time. When you come into contact with a new language, you will focus more on basic knowledge, such as syntax and features. However, there are usually some more advanced, less-known, and powerful features that you need to develop. This article focuses on understanding the runtime of Objective-C, explaining what makes Objective-C so dynamic, and then feeling these dynamic technical details. I hope this will give you a better understanding of how Objective-C and Cocoa run.
The RuntimeObjective-C is a simple language, and 95% is C. Only some keywords and syntaxes are added at the language level. What makes Objective-C so powerful is its runtime. It is small but powerful. Its core is message distribution.
MessagesIf you switch from a dynamic language such as Ruby or Python, you may know what a message is. You can skip to the next section. Those transferred from other languages can be further viewed. Execute a method. In some languages, the compiler will execute some extra optimizations and error checks, because the call relationship is very direct and obvious. However, for message distribution, it is not so obvious. Before sending a message, you do not need to know whether an object can process the message. If you send a message to it, it may be processed or transferred to another Object for processing. A message does not have to correspond to a method. An object may implement a method to process multiple messages. In Objective-C, messages are implemented through the runtime method objc_msgSend () and similar methods. This method requires a target, selector, and some parameters. Theoretically, the compiler only converts message distribution to objc_msgSend for execution. For example, the following two lines of code are equivalent.
[array insertObject:foo atIndex:5];objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);
Objects, Classes, MetaClassesMost object-oriented languages have the concepts of classes and objects. Objects is generated by classes. However, in objective-C, classes itself is also objects (Note: This is similar to Python) and can also process messages, which is why class methods and instance methods exist. Specifically, the object in objective-C is a struct, and the first member is ISA, pointing to its own class. This is defined in objc/objc. h.
typedef struct objc_object { Class isa;
} * ID; the object class saves the method list and pointer to the parent class. But classes is also objects, and there will also be an ISA variable, so where does it point again? The third type is metaclasses. A metaclass is directed to a class, and a class is directed to an object. It saves the list of all implementation methods and metaclass of the parent class. To learn more about how objects, classes, and metaclasses work together, read this article.
Methods, Selectors and IMPsWe know that the message will be sent to the object during running. We also know that the class of an object saves the method list. How are these messages mapped to methods, and how are these methods executed? The answer to the first question is simple. The method list of the class is actually a dictionary. The key is selectors and the IMPS is value. An imp is a method implementation in the memory. It is important that the relationship between selector and IMP is determined at runtime, rather than during compilation. In this way, we can make some tricks. IMP is usually a pointer to a method. The first parameter is self, the type is ID, the second parameter is _ cmd, the type is Sel, and the rest is the method parameter. This is also the definition of self and _ cmd. The method and IMP
-(Id) doSomethingWithInt :( int) aInt {} id doSomethingWithInt (id self, SEL _ cmd, int aInt ){}Other running methods
Now that we know objects, classes, selectors, IMPs, and message distribution, what can we do at runtime? There are two main roles: creating, modifying, introspective classes, and objects message distribution. message distribution has been mentioned before, but this is only a small part of the function. All runtime methods have specific prefixes. The following are some interesting methods:
ClassClass is used to modify and introspect classes. Methods such as class_addIvar, class_addMethod, class_addProperty, and class_addProtocol allow the reconstruction of classes. Class_copyIvarList, class_copyMethodList, class_copyProtocolList, and class_copyPropertyList can get all the content of a class. While class_getClassMethod, class_getClassVariable,
Class_getInstanceMethod, class_getInstanceVariable, class_getMethodImplementation, and class_getProperty return a single content. There are also some common introspection methods, such as class_conformsToProtocol, class_respondsToSelector, class_getSuperclass. Finally, you can use class_createInstance to create an object.
IvarThese methods give you names, memory addresses, and Objective-C type encoding.
MethodThese methods are mainly used for introspection, such as method_getName, method_getImplementation, and method_getReturnType. There are also some modification methods, including method_setImplementation and method_exchangeImplementations, which will be discussed later.
ObjcOnce you get the object, you can make some introspection and modifications to it. You can get/set ivar and use object_copy and object_dispose to copy and free object memory. In addition to getting a class, you can use object_setClass to change the class of an object. You can see the application scenarios in the future.
PropertyAttributes save a large part of information. In addition to getting the name, you can also use property_getAttributes to find more information about the property, such as the return value, whether it is atomic, getter/setter name, whether it is dynamic, the ivar name used behind it, and whether it is a weak reference.
ProtocolProtocols is a bit like classes, but in lite version, the runtime methods are the same. You can obtain the method, property, and protocol list and check whether other protocols are implemented.
SelFinally, we have some methods to process selectors, such as getting a name and registering a selector. Now we have a rough understanding of how to run Objective-C to see what interesting things they can do.
Classes And Selectors From StringsA basic dynamic feature of comparison is to generate Classes and Selectors through String. Cocoa provides NSClassFromString and NSSelectorFromString methods, which are easy to use:
Class stringclass = NSClassFromString(@"NSString");
So we get a string class. Next:
NSString *myString = [stringclass stringWithString:@"Hello World"];
Why? Isn't it more convenient to use Class directly? Generally, this method is useful in some scenarios. First, we can know whether a class exists. NSClassFromString will return nil, if this class does not exist at runtime. For example, you can check whether NSClassFromString (@ "NSRegularExpression") is nil to determine whether it is iOS4.0 +. Another scenario is to return different classes or methods based on different inputs. For example, if you are parsing some data, each data item has a String to be parsed and its own type (String, Number, Array ). You can do this in one method or multiple methods. One method is to obtain the type, and then use if to call the matching method. The other is to generate a selector based on the type and then call it. There are two implementation methods:
- (void)parseObject:(id)object { for (id data in object) { if ([[data type] isEqualToString:@"String"]) { [self parseString:[data value]]; } else if ([[data type] isEqualToString:@"Number"]) { [self parseNumber:[data value]]; } else if ([[data type] isEqualToString:@"Array"]) { [self parseArray:[data value]]; } }}- (void)parseObjectDynamic:(id)object { for (id data in object) { [self performSelector:NSSelectorFromString([NSString stringWithFormat:@"parse%@:", [data type]]) withObject:[data value]]; }}- (void)parseString:(NSString *)aString {}- (void)parseNumber:(NSString *)aNumber {}- (void)parseArray:(NSString *)aArray {}
As you can see, you can change 7 lines of code with if to 1 line. If there are new types in the future, you only need to add implementation methods, instead of adding new else if.
Method SwizzlingAs we have said before, a method consists of two parts. Selector is equivalent to the ID of a method; imp is the implementation of the method. In this way, the correspondence between selector and IMP can be changed. For example, an imp can have multiple selectors pointing to it. Method swizzling can exchange two methods for implementation. You may ask, "under what circumstances do you need this ?". Let's take a look at the two methods of extending the class in objective-C. First, subclassing. You can override a method and call the implementation of the parent class. This also means that you must use this subclass instance, But if you inherit a cocoa class, cocoa returns the original class (such as nsarray ). In this case, you will want to add a method to nsarray, that is, use category. In 99%, this is OK, but if you rewrite a method, there is no chance to call the original implementation. Method swizzling can solve this problem. You can override a method without inheritance, and call the original implementation. The common practice is to add a method (or a brand new class) to the category ). You can use the running method method_exchangeimplementations to exchange implementations. Let's take a look at a demo. This demo demonstrates how to override addobject: The method to record each newly added object.
#import <objc/runtime.h> @interface NSMutableArray (LoggingAddObject)- (void)logAddObject:(id)aObject;@end @implementation NSMutableArray (LoggingAddObject) + (void)load { Method addobject = class_getInstanceMethod(self, @selector(addObject:)); Method logAddobject = class_getInstanceMethod(self, @selector(logAddObject:)); method_exchangeImplementations(addObject, logAddObject);} - (void)logAddObject:(id)aobject { [self logAddObject:aObject]; NSLog(@"Added object %@ to array %@", aObject, self);} @end
We put the method exchange in load. This method is called only once and loaded at runtime. If you point to temporary use, you can place it elsewhere. It is obvious that logaddobject is called recursively :. This is also the place where method swizzling is easy to confuse us. Because we have exchanged the implementation of methods, we actually call addobject:
Dynamic inheritance and exchangeWe can create a new class at runtime. This feature is not used much, but it is still very powerful. You can use it to create a new subclass and add a new method. But what is the use of such a subclass? Don't forget one of the key points of objective-C: the object has a class named Isa pointing to it. This variable can be changed without re-creation. Then you can add a new Ivar and method. You can run the following command to modify the class of an object.
object_setClass(myObject, [MySubclass class]);
This can be used in Key Value Observing. When you start observing an object, Cocoa will create the subclass of the class of this object, and then point the isa of this object to the newly created subclass. Click here for more detailed explanation.
Dynamic Method ProcessingSo far, we have discussed method exchange and the processing of existing methods. So what happens when you send a message that the object cannot process? Obviously, "it breaks ". This is true in most cases, but Cocoa and runtime also provide some countermeasures. The first is dynamic method processing. In general, to process a method, find a matched selector at runtime and then execute it. Sometimes, you only want to create a method at runtime. For example, some information can be obtained only at runtime. To achieve this effect, you need to rewrite + resolveInstanceMethod: and/or + resolveClassMethod :. If a method is added, remember to return YES.
+ (BOOL)resolveInstanceMethod:(SEL)aSelector { if (aSelector == @selector(myDynamicMethod)) { class_addMethod(self, aSelector, (IMP)myDynamicIMP, "v@:"); return YES; } return [super resolveInstanceMethod:aSelector];}
In what scenarios will Cocoa use these methods? Core Data is used a lot. NSManagedObjects has many attributes added at runtime to process get/set attributes and relationships. What if the Model is changed at runtime?
Message forwardingIf the resolve method returns NO, the next step is to forward the message. There are two common use cases. 1) Forward the message to another object that can process the message. 2) forward multiple messages to the same method. The message is forwarded in two steps. First, call-forwardingTargetForSelector at runtime: to send messages to another object, use this method because it is more efficient. If you want to modify a message, use-forwardInvocation: to package the message into NSInvocation at runtime, and then return it to you for processing. After processing, call invokeWithTarget :. Cocoa uses message forwarding in several places, including Proxies and Responder Chain ). NSProxy is a lightweight class that forwards messages to another object. It is useful if you want to asynchronously load an object's attributes. NSUndoManager is also useful, but it intercepts messages and then executes them, instead of forwarding them to other places. The response chain is about how Cocoa handles and sends events and actions to corresponding objects. For example, if you run the copy command in Cmd + C,-copy is sent to the response chain. First, First Responder, usually the current UI. If the message is not processed, it is forwarded to the next-nextResponder. Until the object that can process the message is found or not found, an error is returned.
Use Block as Method IMPIOS 4.3 introduces many new runtime methods. In addition to the enhancement of properties and protocols, it also brings a new set of methods starting with imp. Generally, an IMP is a pointer to the method implementation. The first two parameters are object (self) and selector (_ cmd ). IOS 4.0 and Mac OS X 10.6 bring block. imp_implementationWithBlock () allows us to use block as IMP. The following code snippet shows how to use block to add new methods.
IMP myIMP = imp_implementationWithBlock(^(id _self, NSString *string) { NSLog(@"Hello %@", string);});class_addMethod([MYclass class], @selector(sayHello:), myIMP, "v@:@");
If you want to know how this is implemented, you can see in this article that Objective-C looks simple, but it is still flexible and may bring many possibilities. The advantage of Dynamic Language is that it does a lot of clever things without extending the language itself. For example, Key Value Observing provides an elegant API that can be seamlessly integrated with existing code without adding language-level features. I hope this article will allow you to gain a deeper understanding of Objective-C, broaden your thinking and consider more possibilities when developing apps.