zoukankan      html  css  js  c++  java
  • ObjectiveC Runtime的数据类型

    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;
    typedef
    struct method_t { SEL name; const char *types; IMP imp; } method_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”的方式命名。
    调用也非常简单,毫无压力,首先引入Category的声明文件,然后正常调用即可:

    #import "SomeClass+Hello.h"
     
    SomeClass * sc =[[SomeClass alloc] init];
    [sc hello] 

    执行结果是:
    name:Jacky

    Category的使用场景:
    1、当你在定义类的时候,在某些情况下(例如需求变更),你可能想要为其中的某个或几个类中添加方法。
    2、一个类中包含了许多不同的方法需要实现,而这些方法需要不同团队的成员实现
    3、当你在使用基础类库中的类时,你可能希望这些类实现一些你需要的方法。

    遇到以上这些需求,Category可以帮助你解决问题。当然,使用Category也有些问题需要注意,
    1、Category可以访问原始类的实例变量,但不能添加变量,如果想添加变量,可以考虑通过继承创建子类。
    2、Category可以重载原始类的方法,但不推荐这么做,这么做的后果是你再也不能访问原来的方法。如果确实要重载,正确的选择是创建子类。
    3、和普通接口有所区别的是,在分类的实现文件中可以不必实现所有声明的方法,只要你不去调用它。

    用好Category可以充分利用Objective-C的动态特性,编写出灵活简洁的代码。

    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

  • 相关阅读:
    easy ui 表单ajax和from两种提交数据方法
    easy ui 下拉级联效果 ,下拉框绑定数据select控件
    easy ui 下拉框绑定数据select控件
    easy ui 异步上传文件,跨域
    easy ui 菜单和按钮(Menu and Button)
    HTTP 错误 404.3
    EXTJS4.2 后台管理菜单栏
    HTML 背景图片自适应
    easy ui 表单元素input控件后面加说明(红色)
    EXTJS 4.2 添加滚动条
  • 原文地址:https://www.cnblogs.com/whyandinside/p/2933552.html
Copyright © 2011-2022 走看看