C++模拟实现Objective-C动态类型(附源码)
在OC(Objective-C)里面有动态类型分为以下几类:
-(BOOL)isKindOfClass:classObj 是否是classObj类或其子类 -(BOOL)isMemberOfClass:classObj 是否是classObj的实例 -(BOOL)respondsTosSelector:selector 类中是否有这个方法 NSClassFromString(NSString*); 由字符串得到类对象 NSStringFromClass([类名 Class]); 由类名得到字符串 Class rectClass= [Rectangle class]; 通过类名得到类对象 Class aClass =[anObject class]; 通过实例得到类对象 if([obj1 class]== [obj2 class]) 判断是不是相同类的实例
虽然C++本身不包含以上这些功能,但是相比之下C++是更为底层的语言,其实以上大部分也可以用C++模拟实现。虽然这些对实际开发意义或许不大,但是从中我们也可以了解很多高级语言底层知识,从而更深入了解语言本质或者是语言的灵活性。当然以上接口我在模拟实现时或许会和OC实际接口有差异,但是这并不影响,因为有些只需要做相应修改即可,而我更多的是介绍实现的思想。
首先我们来实现isKindOfClass这个函数,该函数在C++主要是判断一个对象是否从一个类的对象或者是某个类的子类对象。我们模拟实现这个功能,假如说A-B-C-D-E一连串的继承,如果满足以下两个条件:1.每个类都有自己的标识,用于通过标识判断对象是否是这个类的对象;2.对于A-B-C-D-E一连串的继承,作出一张链表,用于查询使用。那么如果我们需要判断C c对象是否是A的子类对象时,那么只需要调用一个查询接口,传入A对象标识,当判断是A类的对象时,再查询B对象,然后查询到C对象,此时判断通过于是返回为真。
1 //在这里我们用类名字作为唯一标识,也就是szClassName变量 2 //然后向前向后两个链表用于串起整个继承关系 3 struct ClassRunTimeType 4 { 5 char *szClassName; 6 static ClassRunTimeType* pFirstClass; 7 ClassRunTimeType *m_pPrevClassRunTimeType; 8 ClassRunTimeType *m_pNextClassRunTimeType; 9 }; 10 11 //这个类并没有其他用途,唯独借用定义对象时需要执行构造 12 //通过执行构造函数来初始化我们需要第一时间执行的代码 13 //每个类都会用他定义一个对应的对象 14 class CRunTimeType 15 { 16 public: 17 CRunTimeType(ClassRunTimeType *pType); 18 };
有了以上的基本数据结构接下来可以在实际类中动手脚了
1 class NSObject 2 { 3 public: 4 virtual void ShowHello(){} 5 6 BOOL isKindOfClass(ClassRunTimeType *pRunTimeType); 7 BOOL isMemberOfClass(void *pFun); 8 void *GetInstallFromName(char *szName); 9 10 virtual char* GetCurrentClassName() 11 { 12 return NSObject::GetClassName(); 13 } 14 15 static char* GetClassName() 16 { 17 return szClassNSObject; 18 } 19 }; 20 21 static char szClassNSObject[] = "NSObject"; 22 ClassRunTimeType NSObject::classNSObject = {szClassNSObject,NULL,NULL};
szClassName[] = "NSObject"就是这个类的唯一标识,如果要判断是否是出自于这个类,只需要对比这个字符串就可以了,相应的调用方法也很简单,也就是BOOL isKindOfClass(ClassRunTimeType *pRunTimeType)的内部实现。
1 BOOL isKindOfClass(ClassRunTimeType *pRunTimeType) 2 { 3 if (0 == strcmp(GetCurrentClassName(),pRunTimeType->szClassName)) 4 return TRUE; 5 return FALSE; 6 }
当然了,这个时候还仅仅只是实现了判断一个对象是不是某个类的对象,对于是不是子类还是不行,因为没有借助上面的链表查询功能。但是相信到了这个具体怎么实现已经不是难事了,如果有疑问可以调本文对应的源码,里面有详细的说明。
可惜的是respondsTosSelector并没有找到很好的方法,首先Object-C每个成员函数都是虚函数,相对很容易实现一些,而C++里面的非虚函数则完全无法判断。其次即使是虚函数,判断起来也存在一定的困难,以VS2010为例,在禁用优化的情况下,调用一个虚函数会执行以下几步:
1 ShowHelloEx.ShowHello(); 2 00191080 lea ecx,[ShowHelloEx] 3 00191083 call NSObject::ShowHello (191010h)
首先保存this指针的地址到ecx,这也是VS系列管用手法。其次调用的ShewHello函数内部,但是这里并没有调到虚表里面的对应的地址,而是调到这个地方
1 NSObject::`vcall'{0}': 2 00191140 mov eax,dword ptr [ecx] 3 00191142 jmp dword ptr [eax]
此时再取出虚表地址,然后计算偏移jmp,这时候才是真正虚表的地址,而从函数名获取到的地址只是这里的中转跳转位置也就是191140这个地方。所以说单纯的比较虚表地址来实现这个功能也并不靠谱,换成其他编译器也会存在兼容性问题,所以说这个接口我只能表示无能为力。至于OC为什么能做到,我在想毕竟接口是苹果实现的,而OC编译器也是苹果自己的,苹果可以设定成编译成指定的调用规定来适应接口,而C++就不一样。
NSClassFromString这个函数相对简单,其实很多地方都会用到类似的功能。比如说我们写个程序,在程序上画了一条线,然后保存到文件。如果我们以后要打开这个文件则必须要保存这条线的类名字以及其他特征,读取的时候首先根据类名创建对象,也就是这个函数类似的功能。事实上我们只需要在上面那个结构体中动动手脚就好了,具体实现:
1 typedef void* ( *pFn)(); 2 //在这里我们用类名字作为唯一标识,也就是szClassName变量 3 //然后向前向后两个链表用于串起整个继承关系 4 struct ClassRunTimeType 5 { 6 char *szClassName; 7 static ClassRunTimeType* pFirstClass; 8 ClassRunTimeType *m_pPrevClassRunTimeType; 9 ClassRunTimeType *m_pNextClassRunTimeType; 10 pFn pfn; 11 };
pfn这个函数主要用于创建对象,我们只需要在给这个结构体赋值的时候,传入一个函数指针,而函数的目的就是创建对象。首先我们需要遍历我们的链表,寻找到对应的类名,然后调用这个函数创建对象即可。相反,NSStringFromClass也可以用类似的方法实现,具体的可以参考示例代码。
至于判断是否同一个实例就很简单了,首先比较地址对不对,对的话就完全一样^_^其次比较唯一标识,符合的话就是同一个类的对象啦。
源码下载: DynamicType.zip