iOS中的Method Overloading


Objective-C的[...]语法来源于smallTalk,其设计初衷是希望它是一种动态的,在运行时决定函数的入口地址。由于objective-C本质上也还是C语言,因此[…]语法在编译后实际上是一条标准C函数:

id objc_msgSend(id self, SEl OP, ...)

而C语言是一种静态语言,也就是说,函数的入口在编译的时候就已经确定了(static binding)。例如这样一段代码:

__attribute__((noinline))void searchProduct()
{
    printf("searching product");
}

__attribute__((noinline))void searchShop()
{
    printf("searching shop");
}

void search_static(int type)
{
    if (type == 1) {
        searchProduct();
    }
    else
        searchShop();
}

我们不让编译器去自动inline代码,searchProduct和searchShop在编译的时候地址已经确定了:

.globl _search_static
_search_static: ## @search_static
.cfi_startproc
Lfunc_begin2:
.loc 1 48 0
pushq %rbp

...................

Ltmp20:</span>
 ## BB#1:
.loc 1 50 0
popq %rbp
jmp _searchProduct ## TAILCALL
Ltmp21:</span>
LBB2_2:</span>
.loc 1 53 0
popq %rbp
jmp _searchShop ## TAILCALL
Ltmp22:</span>
Lfunc_end2:
.cfi_endproc

如果把上面代码改写成下面这样:

__attribute__((noinline))void searchProduct()
{
    printf("searching product");
}

__attribute__((noinline))void searchShop()
{
    printf("searching shop");
}

void search_dynamic(int type)
{
    void(*func)();
    
    if (type == 1) {
        func = searchProduct;
    }
    else
        func = searchShop;
    
    func();
}

这样就变成了运行时决定函数地址了。这种方式好处是很灵活,你甚至可以在运行时修改程序执行的结果。但是坏处也很明显,就是需要计算,慢。回退到1986年,OC和C++都刚刚问世,同是面向对象,C++因为其在编译器做大量的事而速度更快,OC由于受当时硬件条件,这种动态绑定的做法效率往往很低。

基本上OC的函数执行都是遵循上面这种模式,判断执行那个函数的条件是objc_msgSend的SEL参数,它是一个opaque string:

SEL someSelector = NSSelectorFromString(@"dealloc")

而函数的入口地址保存在了:

struct objc_method_list **methodLists 

为了效率考虑,这些地址被调用过后会cache住

struct objc_cache *cache  

例如:

2   CoreFoundation                      0x01a0c903 -[NSObject(NSObject) doesNotRecognizeSelector:] + 275
3   CoreFoundation                      0x0195f90b ___forwarding___ + 1019
4   CoreFoundation                      0x0195f4ee _CF_forwarding_prep_0 + 14
5   libobjc.A.dylib                     0x014c6275 _class_initialize + 599
6   libobjc.A.dylib                     0x014cd0f1 lookUpImpOrForward + 158
7   libobjc.A.dylib                     0x014cd04e _class_lookupMethodAndLoadCache3 + 55
8   libobjc.A.dylib                     0x014d512f objc_msgSend + 139
9   UIKit                               0x0034b9d4 -[UIViewController loadViewIfReq

虽然有cache或有一些优化在,但效率毕竟还是低于static binding,但对于现在的硬件条件来讲,这个也不算是瓶颈。

Overloading的问题

使用Java或者C++,overloading是种很常见的策略,或者很多人习惯通过overloading的参数来区别方法的意思。但是这种技术在OC中却行不通,道理也很简单,overloading也是一种static binding。

以Java代码为例:

class testObj
{
	public void searchProduct(String name)
	{
		System.out.println(name);
	}
	
	 public void searchProduct(int type)
	 {
			System.out.format("%d\n",type);
	 }
}

public class main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		testObj obj = new testObj();
		
		obj.searchProduct(0);
		obj.searchProduct("iphone");
	}
}

可以看到main.class中,方法的入口地址也确定了。

public static void main(java.lang.String[]);
  Code:
  Stack=2, Locals=2, Args_size=1
  0:	new	#15; //class testObj
  3:	dup
  4:	invokespecial	#17; //Method testObj."<init>":()V
  7:	astore_1
  8:	aload_1
  9:	iconst_0
  10:	invokevirtual	#18; //Method testObj.searchProduct:(I)V
  13:	aload_1
  14:	ldc	#22; //String iphone
  16:	invokevirtual	#24; //Method testObj.searchProduct:(Ljava/lang/String;)

为什么OC不行呢?

对同名的方法,参数类型不同,仅靠SEL是无法区别的:

@selector(searchProduct:)

如果有同名方法,谁知道该调用哪一个呢?