在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
Class Objective-C是支持反射的,先来了解一下其如何表达一个类。在Objective-C的Runtime中有个类型是Class(只在Runtime环境中使用),用来表示Objective-C中的类,其定义为: typedef struct objc_class *Class;
可以看出,其实Class类型是一个指针,指向struct objc_class,而struct objc_class才是保存真正数据的地方,再看struct objc_class的声明(from http://www.opensource.apple.com/source/objc4/objc4-493.9/runtime/runtime.h): struct objc_class { Class isa; #if !__OBJC2__ Class super_class OBJC2_UNAVAILABLE; const char *name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; struct objc_method_list **methodLists OBJC2_UNAVAILABLE; struct objc_cache *cache OBJC2_UNAVAILABLE; struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE; 其中包含了方法列表、父类等信息,详细的可以稍后再看。
Method 是Runtime内部定义的方法,用来代表一个方法,其声明如下: typedef struct objc_method *Method;
而struct objc_method的声明如下: struct objc_method { SEL method_name OBJC2_UNAVAILABLE; char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; } SEL和IMP代表什么需要看下面的内容。如果你已经了解了SEL和IMP的含义,可以看看下面这段:根据Class和Method的定义来理解Objective C中的消息机制: 先看看objc_class中method list的在新runtime(http://opensource.apple.com/source/objc4/objc4-437/runtime/objc-runtime-new.h)里的定义: typedef struct method_list_t { uint32_t entsize_NEVER_USE; // low 2 bits used for fixup markers uint32_t count; struct method_t first; } method_list_t; SEL相当于char*,可以认为objc_class中method list保存了一个SEL<->IMP的映射,看下面的代码: Bird * aBird = [[Bird alloc] init];
[aBird fly];
其中对fly的调用,其实是由编译器插入了一些代码,根据SEL([aBird fly] 中的fly就是SEL)找到了IMP,从而进行调用的。下面看编译器插入了什么样的代码。我们来看Objective C runtime中跟msg相关的函数: id objc_msgSend(id theReceiver, SEL theSelector, ...) 这个函数发送消息给theReceiver,并将返回值返回。编译器其实就是将[aBird fly]转化成了对objc_msgSend的调用,从而实现消息机制的。objec_msgSend()函数将会使用theReceiver的isa指针来找到theReceiver的类空间结构并在类空间结构中查找theSelector所对应的方法。如果没有找到,那么将使用指向父类的指针找到父类空间结构进行theSelector的查找。如果仍然没有找到,就继续往父类的父类一直找,直到找到为止。如果找不到怎么办呢?关于消息机制,有一篇引用文章,介绍的更加详细,这里就不赘述。
Ivar Runtime中用来表示instance variable(实例变量,跟某个对象关联,不能被静态方法使用,与之想对应的是class variable),其声明如下: typedef struct objc_ivar *Ivar;
而struct objc_ivar的声明如下: struct objc_ivar { char *ivar_name OBJC2_UNAVAILABLE; char *ivar_type OBJC2_UNAVAILABLE; int ivar_offset OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE; Category Runtime中用来表示Category( link ?),其声明为: typedef struct objc_category *Category;
struct objc_category 的定义也在runtime.h文件中。 []Catagory可以动态地为已经存在的类添加新的行为。这样可以保证类的原始设计规模较小,功能增加时再逐步扩展。使用Category对类进行扩展时,不需要访问其源代码,也不需要创建子类。Category使用简单的方式,实现了类的相关方法的模块化,把不同的类方法分配到不同的分类文件中。下面看一个例子: SomeClass.h @interface SomeClass : NSObject{ } -(void) print; @end 这是类SomeClass的声明文件,其中包含一个实例方法print。如果我们想在不修改原始类、不增加子类的情况下,为该类增加一个hello的方法,只需要简单的定义两个文件SomeClass+Hello.h和SomeClass+Hello.m,在声明文件和实现文件中用“()”把Category的名称括起来即可。声明文件代码如下: #import "SomeClass.h" @interface SomeClass (Hello) -(void)hello; @end 实现文件代码如下: #import "SomeClass+Hello.h" @implementationSomeClass (Hello) -(void)hello{ NSLog (@"name:%@ ", @"Jacky"); } @end 其中Hello是Category的名称,如果你用XCode创建Category,那么需要填写的内容包括名称和要扩展的类的名称。这里还有一个约定成俗的习惯,将声明文件和实现文件名称统一采用“原类名+Category”的方式命名。 #import "SomeClass+Hello.h" SomeClass * sc =[[SomeClass alloc] init]; [sc hello] 执行结果是:
SEL Runtime中用来表示一个method selector,其声明为: typedef struct objc_selector *SEL;
没有找到struct objc_selector的定义,有人说是编译器定义的,GCC 和MacOSX的实现方式还不一样,不想花时间找GCC的代码,而且也没那么重要,所以就先姑且相信这个说法吧。 IMP IMP是一个函数指针,指向方法的实现,其定义为: id (*IMP)(id, SEL, ...) 其所指向的方法,返回一个id(Cocoa 对象),需要传入的第一个参数是self(指向某个对象,或者一个类),第二个参数是方法的SEL。 objc_property_t objc_method_list objc_cache objc_protocol_list id在 Objective-C中id类型的对象可以转换为任何一种对象,有点类似与void *指针类型的作用。下面简要介绍一下id类型。 id标志符:通用对象类型。id类型是一个独特的数据类型,可以转换为任何数据类型,即id类型的变量可以存放任何数据类型的对象。id在objc.h中的定义为: typedef struct objc_object { Class isa; } *id; 从上面的介绍,我们已经知道Class是struct objc_class的指针别名,所以id可以指向一个第一个元素是Class的struct;那么它为什么可以指向NSObject对象呢?下面看NSObject的定义: @interface NSObject <NSObject> { Class isa; } 可以看出NSObject的第一个对象是Class类型的isa。因为第一个元素相同,也就意味着可以互相cast而不损失信息,下面是用C语言来演示的其实现原理: #include <stdio.h> #include <stdlib.h> struct objc_class { int count; char * name; }; typedef struct objc_class * Class; typedef struct objc_obj0 { Class isa; }*id; typedef struct objc_obj1 { Class isa; int a; }*id1; typedef struct objc_obj2 { Class isa; char *b; }*id2; int main(int argc, char **argv) { // id 的第一个元素与id1是一样的,所以可以用id指向id1的元素,而不损失任何信息,不过后续使用的时候应该使用其实际类型 id a = (struct objc_obj1 *)malloc(sizeof(struct objc_obj1)); id b = (struct objc_obj1 *)malloc(sizeof(struct objc_obj1)); } 实施上,通常而言,这样使用时编译器是要report warning的,我们可以在.m文件中加入下面的代码来验证: typedef struct objc_object *id2; id2 = [[NSNumber alloc] initWithInt:(i*3)]; 这时是会报incompatible pointer types initializing 'id2' (aka 'struct objc_object *') with an expression of type 'NSNumber *' 的,但id2和id的定义相同,为什么使用id时不会有这个warning呢?因为编译器对id做了特殊处理,不报warning。这下对id有了更多了解了吧。后续而来的问题就是,为什么可以在id类型上调用一些NSNumber上才有的方法呢?这一部分留到Dynamic Typing and Dynamic binding时再说吧。
http://unixjunkie.blogspot.com/2008/03/id-vs-nsobject-vs-id.html http://www.cppblog.com/kesalin/archive/2011/08/15/objc_message.html http://www.cnblogs.com/chijianqiang/archive/2012/06/22/objc-category-protocol.html |
请发表评论