h2.Overview

Objective-C language defers as many decisions as it can from compile time and link time to runtime.as a principle, it does things dynamically.This means that the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system for the Objective-C language; it’s what makes the language work.

we'll discuss about the NSObject class and how Objective-C programs interact with the runtime system, dynamically loading new classes at runtime, and forwarding messages to other objects, also provides information about how you can find information about objects while your program is running.

from this document we can gain an understanding of how the Objective-C runtime system works and how we can take advantage of it.

This doc contains the following chapter:
1,Runtime Versions and Platforms
2,Interacting with the Runtime
3,Messaging
4,Dynamic Method Resolution
5,Message Forwarding

so that's start.

h3.1,runtime的版本和platforms

Objective-C的runtime有两个版本—“modern” and “legacy”. iPhone applications and 64-bit programs on OS X v10.5 and later use the modern version of the runtime.Other programs (32-bit programs on OS X desktop) use the legacy version of the runtime.(iPhone开发用的都是64-bit)

h3.2,Interacting with the Runtime

Objective-C programs interact with the runtime system at three distinct levels: through Objective-C source code; through methods defined in the NSObject class of the Foundation framework; and through direct calls to runtime functions.

h3.3,Messaging

This chapter describes how the message expressions are converted into objc_msgSend function calls, and how you can refer to methods by name,and how we can take advantage of objc_msgSend method.

==
//parameter:self,A pointer that points to the instance of the class that is to receive the message.
//parameter:op,The selector of the method that handles the message.
//parameter:...,A variable argument list containing the arguments to the method.
//return:The return value of the method.
id objc_msgSend(id self, SEL op, ...);

warning:
When it encounters a method call, the compiler generates a call to one of the functions objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, or objc_msgSendSuper_stret.
Messages sent to an object’s superclass (using the super keyword) are sent using objc_msgSendSuper; other messages are sent using objc_msgSend.
Methods that have data structures as return values are sent using objc_msgSendSuper_stret and objc_msgSend_stret.
==

In Objective-C, messages aren’t bound to method implementations until runtime.
The compiler converts a message expression 【receiver message】, into a call on a messaging function, objc_msgSend(receiver, selector, arg1, arg2, ...).The messaging function does everything necessary for dynamic binding:

1,It first finds the procedure (method implementation) that the selector refers to. Since the same method can be implemented differently by separate classes, the precise procedure that it finds depends on the class of the receiver.
2,It then calls the procedure, passing it the receiving object (a pointer to its data), along with any arguments that were specified for the method.
3,return value.

The key to messaging lies in the +structures+ that the compiler builds for each +class and object+.

Every +class structure+ includes these two essential elements:
1, A pointer to the superclass.
2, A class dispatch table.(简单说,就是方法选择器和+procedure的地址+的+映射表+,做关联)

When a new object is created, its instance variables are initialized. First among the object’s variables is a pointer to its class structure. This pointer, called isa, gives the object access to its class and to all the classes it inherits from.(创建一个object会初始化一个isa,可以访问+它的类结构+,并更进一步访问各种父类。isa是指向自身类结构的。)

When a message is sent to an object,
1,the messaging function follows the object’s isa pointer to the class structure where it looks up the method selector in the +dispatch table+.
2,If it can’t find the selector there, objc_msgSend follows the pointer to the superclass and tries to find the selector in its dispatch table.
3,Successive failures cause objc_msgSend to climb the class hierarchy until it reaches the NSObject class(what if it cannot find the selector in NSObject Class?see _message forwarding_ below). Once it locates the selector, the function calls the method entered in the table and passes it the receiving object’s data structure.

so we say that,in iOS,methods are dynamically bound to messages.

To speed the messaging process, the runtime system caches the selectors and addresses of methods as they are used(RS实现了dispatch的部分方法cache,可以包含自身和继承的方法). There’s a separate cache for each class, and it can contain selectors for inherited methods as well as for methods defined in the class. Before searching the dispatch tables, the messaging routine first checks the cache of the receiving object’s class (on the theory that a method that was used once may likely be used again). If the method selector is in the cache, messaging is only slightly slower than a function call. Once a program has been running long enough to “warm up” its caches, almost all the messages it sends find a cached method. Caches grow dynamically to accommodate new messages as the program runs.(刚开始+消息分发+会比+消息调用+慢,足够热身后,几乎所有用到的方法都可以在cache中找到就会变快。cache大小会动态增加。)

Using Hidden Arguments。objc_msgSend查找并调用函数,传参中传了2个隐藏参数,接收对象The receiving object和方法选择器The selector for the method。这两个参数,方法定义时未声明,而是在方法实现时插入到实现中。尽管没有显式的声明,我们依旧可以使用RS的api直接使用它们。

Getting a Method Address。可以直接获取方法选择器的地址,并合理使用,避免过于频繁的消息转发的开销。比如在for循环里的消息转发,可以直接获取方法地址并直接调用。

h3.3,Dynamic Method Resolution。动态方法解析
describes how you can provide an implementation of a method dynamically,也就是动态的提供一个方法的实现。

可以使用@dynamic动态的为一个属性绑定setter和getter方法。也可以使用resolveInstanceMethod: 和resolveClassMethod: 方法来动态的给一个实例或者类的方法选择器selector提供implement。

==
@dynamic的作用:
1,告诉编译器不要创建实现属性所用的实例变量;
2,告诉编译器不要创建该属性的get和setter方法。

其实作用如名字,就是告诉编译器会动态的生成实例变量和settergetter。相比下,@synthesize是合成,编译器自动合成实例变量以及settergetter方法。
1,如果都不加@dynamic和@synthesize,默认就是@synthesize。
2,如果加了@dynamic但是实际上没添加属性的settergetter,如果真调用到了setter和getter,会出异常,导致crash。
==
关于resolveInstanceMethod:
动态方法解析和消息转发实际上很大部分是耦合的。
一个正常的消息传递是不会走动态方法解析的。这个方法,仅在+被调用的方法实现部分没有找到,而消息转发机制启动之前的这个中间时刻+会完成。同时,同一个未找到implement的selector不会每次都走动态绑定,只是第一次找不到才会。

一个消息在开发转发前会先完成动态方法解析。举一个例子:

我们@dynamic修饰了一个属性,但没提供相应的setter和getter,但又在运行时使用到了。理论上这个时候就会进入消息转发流程。在此之前,就是一个动态方法解析过程。

void dynamicMethodIMP(id self, SEL _cmd)
{
// implementation ....
}

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"sel is %@", NSStringFromSelector(sel));
if(sel == @selector(setName:)){
class_addMethod([self class],sel,(IMP)dynamicMethodIMP,"v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}

这是NSObject类中提供的类方法。在消息转发前会进入动态方法绑定流程,可以给预期的选择器selector提供implement。如果希望动态绑定后selector走fowarding流程,则应该返回no。
===

h3.4,message forwarding。消息转发

给一个对象发送一个自身无法处理的消息,会引起错误。但在抛出错误之前,RS会做一些行为,来补救这种错误。就是这一节说的forwarding。

只有在消息传递阶段无法完成的消息,才会进入转发流程。也就是只有自身以及基类中没实现的类,才会尝试转发。

转发一共3个步骤。
1,上一节说的,动态方法绑定。resolveInstanceMethod和resolveClassMethod。因为找不到implement,所以尝试动态寻找。找到则绑定方法,返回yes,完成。找不到,则下一步。
2,尝试找一个可以处理消息的instance或者class。调用forwardingTargetForSelector让别的对象去执行这个函数。返回了非self和非nil,说明找到了,转发消息。否则下一步。
比如,
- (id)forwardingTargetForSelector:(SEL)aSelector {
Task *task = [Task new];
if ([task respondsToSelector:aSelector]) {
return task;
}
return [super forwardingTargetForSelector:aSelector];
}
3,调用forwardInvocation(函数执行器)灵活的将目标函数以其他形式执行。
比如,
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// 分发消息
Task *task = [Task new];
if ([task respondsToSelector:[anInvocation selector]]) {
[anInvocation invokeWithTarget:task];
}
// 或者更灵活的处理消息
//拿到函数名
NSString *key = NSStringFromSelector([invocation selector]);
if ([key rangeOfString:@"set"].location == 0) {
//setter函数形如 setXXX: 拆掉 set和冒号
key = [[key substringWithRange:NSMakeRange(3, [key length]-4)] lowercaseString];
NSString *obj;
//从参数列表中找到值
[invocation getArgument:&obj atIndex:2];
[data setObject:obj forKey:key];
} else {
//getter函数就相对简单了,直接把函数名做 key就好了。
NSString *obj = [data objectForKey:key];
[invocation setReturnValue:&obj];
}
}

第2步与第3步的区别:第2步只能把消息转发给一个对象。第3步可以转发给多个对象,或者可以直接更灵活的处理消息。简单说就是第3步更灵活。缺点在于每一步的代价都逐渐变大。

上面三个步骤都没处理消息,则会调用NSObject的-(void)doesNotRecognizeSelector:(SEL)aSelector方法。这里会抛出异常,而且不建议hook,理由如下:
1,官方文档直接明确的建议,“一定不能让这个函数就这么结束掉,必须抛出异常”。所以hook方法,不抛出异常的做法不推荐。
2,在这里做防护,加异常处理代码。函数名叫doesNotRecognizeSelector,却在这里做业务处理代码,不合适,而且业务复杂后会显得臃肿。

==
消息转发与多重继承:

可以理解成RS借鉴模仿或者用另一种形式实现了多重继承。一个对象通过转发消息来响应消息。该对象似乎是借或者“继承”另一个类中实现的方法。

转发提供了大部分多重继承的功能。然而,两者之间有个重要的区别:
多重继承在单一对象上结合了不同的功能。更强大更直观,但也更复杂。
转发分配单独的职责给不同的对象。它将问题分解为更小的对象,但是以一种方式将对象联合。

尽管消息转发模仿了多继承,但是NSObject类并不会混淆继承和消息转发。对于一些方法,比如iskindofClass和respondsToSelector,只会查看类继承结构,而不会出现在转发链上。
也可以重写这些方法,来使一个类“假装”真的继承了另一个类。

一个方法在被分发的时候,forwardInvocation之前,必须拿到一个签名,这个签名可以正确的描述这个方法。比如,一个instance想把一个方法转发给它的surrogate,则必须实现methodSignatureForSelector方法:

-(NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{

NSMethodSignature* signature = [super methodSignatureForSelector:selector];
//如果自己没有selector的签名,尝试获取surrogate中selector的方法签名。如果有,则forwarding。否则下一步会crash。
//methodSignatureForSelector的一般重载法
if (!signature) {

signature = [surrogate methodSignatureForSelector:selector];

}

return signature;
}

===
关于方法签名:Objective-C中方法签名(Method Signature)机制

方法签名是ObjC中对一个方法的参数类型和返回值类型的一条记录。+每个方法都对应一个方法签名+。
只要知道一个对象,和一个SEL(selector),那么就可以动态的获取这个方法签名了。方法签名中会包含方法的参数个数,每个参数的类型,返回值类型,以及返回值占用的空间大小。

所有的消息传递和转发都是会伴随着函数签名。签名的异常会导致诡异的问题,可以参考这篇博文:
http://blog.xcodev.com/posts/method-signature-in-objc/

==

04-29 23:21