EffectiveOC的学习-2

这里是EffectiveOC的 第 9,11,12 三条

2016-06-04 | 阅读

9. 以类族模式隐藏实现细节

文中所说class cluster类族,即在基类中通过工厂方法来创建子类对象的一种模式。如UIButton的:

+ (UIButton *)buttonWithType:(UIButtonType)type;

11. 理解objc_msgSend的作用

OC中的方法调用,称之为传递消息。C语言中一般使用静态绑定来调用函数,即编译期就能决定运行时所应调用的函数,而在C语言使用函数指针来调用函数时,这种情况就可以称之为dynamic binding动态绑定了,要调用的函数直到运行期才能确定。事实上,OC的消息传递,就是基于函数指针的调用而实现的,消息传递是动态绑定的。

id returnValue = [someObject messageName:parameter];

消息传递中,someObject称为接受者,消息名称称为selectorselector和参数在一起合称为message.编译器在遇到消息时,将其转换为函数调用,调用函数void objc_msgSend(id self,SEL cmd,...). 上面的例子,被编译器转换为调用函数:

id returnValue = objc_msgSend(someObject,@selector(messageName:),parameter);

这个函数执行时,会去接受者的类中的函数列表中查找,如果找到了与selector名称相符的方法,就跳到其实现代码,如果没有找到,则按照继承体系继续向上查找。最终没有找到的时候,会执行消息转发message forwarding操作,之后会详细介绍。

在查找消息列表时,为了提高速度,有一个快速映射表fast map缓存了查找结果。但即使加了缓存,速度显然还是会比 静态绑定的函数调用要慢。

普通的消息调用是这样的,但是还有一些特殊情况:

  • objc_msgSend_stret : 如果消息的返回值是结构体,则要交给这个函数来处理。结构体较小时,能够储存在CPU的寄存器时,这个函数才会处理消息。否则又会交给其它函数来执行派发,那个函数会将返回值放在栈上而不是寄存器上。
  • objc_msgSend_fpret : 如果消息的返回值时浮点数,则由此函数处理。在某些架构的CPU中调用函数时,需要对浮点数寄存器floating-point register做特殊处理。
  • objc_msgSendSuper : 发消息给父类时,即调用[super message:parameter]时,实际上时由这个函数来处理,而不是objc_msgSend处理的。对于objc_msgSend_stretobjc_msgSend_fpret,也有两个等效的super函数来发送消息。

要理解,OC是在C的基础上的,OC中的函数调用,称之为消息传递的原因,是真的没有调用函数,而只是传递了要调用的函数的名称和参数给objc_msgSend函数来处理。OC的函数实际上真正的形式是:

<return_type>Class_selector(id self,SEL _cmd,...)

都是C的函数,OC类会有一张映射表,来存储selector与真实的函数指针的关系。而这个原型函数的样子与objc_msgSend很像,原因就是利用了尾调用优化tail-call optimization.

12.理解消息转发机制

在编译期向类发送无法解读的消息并不会报错,因为OC是动态的,可以在运行时为类添加方法。当对象收到无法解读的消息后,就会启动消息转发机制message forwarding

消息转发,分为两个阶段。动态方法解析和完整的消息转发机制:

dynamic method resolution

在对象收到无法解读的消息时,首先会调用这个类方法:

+ (BOOL)resolveInstanceMethod:(SEL);

该方法返回BOOL类型,表示这个类是否能为此selector增加一个新的实例方法。如果selector是一个类方法,则会调用相应的resolveClassMethod:方法。要添加方法时,在该函数中,通过class_addMethod等runtime方法来实现。

如果前面无法处理,则还会询问对象是否可以将消息转发给其它接受者:

- (id)forwardingTargetForSelector:(SEL)selector;

如果能找到备援对象,则返回该对象,如果不能,则返回nil。这里我们无法修改消息的内容,要修改消息的内容,只能通过完整的消息转发机制。

full forwarding mechanism

首先创建一个NSInvocation对象,把尚未处理的消息的selector,目标和参数封装起来 :

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector

这个函数是返回一个NSMethodSignature对象,这个对象中包含了函数的描述信息,即selector的函数名称,参数类型等。这个函数只在经过了动态方法解析依旧无法处理消息时,才会调用,而对于这种消息,这个函数的默认处理结果,肯定是返回nil,而如果返回nil,就会出现no selector的错误。 而如果这个函数能够返回一个NSMethodSignature对象,则会继续进行消息转发机制。举一个在这个函数中处理selector的例子:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
	// Look for the selector as an optional instance method.
	struct objc_method_description methodDescription = protocol_getMethodDescription(_protocol, selector, NO, YES);

	if (methodDescription.name == NULL) {
		// Then fall back to looking for a required instance
		// method.
		methodDescription = protocol_getMethodDescription(_protocol, selector, YES, YES);
		if (methodDescription.name == NULL) return [super methodSignatureForSelector:selector];
	}

	return [NSMethodSignature signatureWithObjCTypes:methodDescription.types];
}

如果methodSignatureForSelector返回了函数描述信息,接下来就会进行消息转发:

- (void)forwardInvocation:(NSInvocation *)invocation;

这里的invocation中封装好了消息的全部消息,selectortarget,methodSignature ,具体参数,返回值。在这个函数里可以选择是否进行消息转发,也可以对invocation进行修改后再进行转发。到了这里,不进行消息转发,是不会报错的。

以上就是整个消息转发的流程。