objective-c 消息是什么

参考资料

1、罗朝辉 深入浅出 Cocoa 之消息
2、Objective-C 中的元类(meta class)是什么?

基本概念与原理

Class

Class 在objc/objc.h中的定义:

typedef struct objc_class *Class;

objc_class的结构

Class 被定义为一个指向 objc_class的结构体指针,这个结构体表示每一个类的类结构。而 objc_class 在objc/objc_class.h中定义如下:

struct objc_class {
    struct objc_class * isa; //类指针(类是元类的对象)
    struct objc_class * super_class;  /*父类*/
    const char *name;                 /*类名字*/
    long version;                   /*版本信息*/
    long info;                        /*类信息*/
    long instance_size;               /*实例大小*/
    struct objc_ivar_list *ivars;     /*实例参数链表*/
    struct objc_method_list **methodLists;  /*方法链表*/
    struct objc_cache *cache;               /*方法缓存*/
    struct objc_protocol_list *protocols;   /*协议链表*/
};

每个对象都有类。这是面向对象的基本概念,但是在Objective-C中,它对数据结构也一样。含有一个指针且该指针可以正确指向类的数据结构,都可以被视作为对象。
在Objective-C中,对象的类是isa指针决定的。isa指针指向对象所属的类。

objc_msgSend对这个Class结构的应用:

objc_msgSend方法含有两个必要的参数:receiver、方法名(即:selector),如:

        [receiver message];将被转换为:objc_msgSend(receiver,selector);

objc_msgSend方法也能传message的参数,如:

    objc_msgSend(receiver, selector, arg1, arg2, …);

objc_msgSend调用类方法:

    BOOL isSuccess = ((BOOL(*)(id,SEL,int))objc_msgSend)(NSClassFromString(@"MyPointsManager"),NSSelectorFromString(@"addPointsIn:"),50);

objc_msgSend调用实例方法:

MyPointsManager *pointsManager = [MyPointsManager sharedInstance];
    BOOL isEnabled = ((BOOL(*)(id,SEL))objc_msgSend)(pointsManager,NSSelectorFromString(@"isEnable"));
    NSLog(@"isEnabled %d",isEnabled);
objc_msgSend调用类方法跟实例方法的调用实现区别:
在oc里,+号函数和-号函数的区别:

-(void)是实例方法,只有定义了这个类的实例,才能用实例调用这个方法。
+(void)是类方法,用类名可以直接调用这个方法。
通过分析,+号函数是基类调用的,-号函数是实例类调用的。其函数结构体数组的位置也不一样。

1、实例方法调用:

每个对象都有一个指向所属类的指针isa。通过该指针,对象可以找到它所属的类,也就找到了其全部父类。当向一个对象发送消息时,objc_msgSend方法根据对象的isa指针找到对象的类,然后在类的调度表(dispatch table)中查找selector。如果无法找到selector,objc_msgSend通过指向父类的指针找到父类,并在父类的调度表(dispatch table)中查找selector,以此类推直到NSObject类。一旦查找到selector,objc_msgSend方法根据调度表的内存地址调用该实现。 通过这种方式,message与方法的真正实现在执行阶段才绑定。
为了保证消息发送与执行的效率,系统会将全部selector和使用过的方法的内存地址缓存起来。每个类都有一个独立的缓存,缓存包含有当前类自己的 selector以及继承自父类的selector。查找调度表(dispatch table)前,消息发送系统首先检查receiver对象的缓存。

2、类方法的调用:

从Class的结构可以看出,Class中也有一个isa指针,指向实现它的类,即元类(metaClass)。Objective-C中的每个类都有和自己相关的元类,类方法就是放在元类的函数指针数组中。元类也是一个对象,那么元类又指向哪里呢?为了设计上的完整,所有的元类的isa指针都会指向一个根元类。元类的继承关系跟类的继承关系是一样的。

简单说就是:
当你给对象发送消息时,消息是在寻找这个对象的类的方法列表。
当你给类发消息时,消息是在寻找这个类的元类的方法列表。

objc_class结构里面的方法链表的Method结构

Method 在头文件 objc_class.h中定义如下:

typedef struct objc_method *Method;
typedef struct objc_ method {
    SEL method_name;
    char *method_types;
    IMP method_imp;
};

一个方法 Method,其包含一个方法选标 SEL – 表示该方法的名称,一个types – 表示该方法参数的类型,一个 IMP - 指向该方法的具体实现的函数指针

Method结构中SEL的含义

SEl在objc/objc.h中的定义为:

typedef struct objc_selector   *SEL;   

它是一个指向 objc_selector 指针,表示方法的名字/签名。
不同的类可以拥有相同的 selector,这个没有问题,因为不同类的实例对象performSelector相同的 selector 时,会在各自的消息选标(selector)/实现地址(address) 方法链表中根据 selector 去查找具体的方法实现IMP, 然后用这个方法实现去执行具体的实现代码。这是一个动态绑定的过程,在编译的时候,我们不知道最终会执行哪一些代码,只有在执行的时候,通过selector去查询,我们才能确定具体的执行代码。

Method结构中的IMP 的含义

IMP在objc/objc.h中的定义为:
typedef id (*IMP)(id, SEL, …);

IMP 是一个函数指针,这个被指向的函数包含一个接收消息的对象id(self 指针), 调用方法的选标 SEL (方法名),以及不定个数的方法参数,并返回一个id。也就是说 IMP 是消息最终调用的执行代码,是方法真正的实现代码 。我们可以像在C语言里面一样使用这个函数指针。

NSObject 类中的methodForSelector:方法就是这样一个获取指向方法实现IMP 的指针,methodForSelector:返回的指针和赋值的变量类型必须完全一致,包括方法的参数类型和返回值类型。

下面的例子展示了怎么使用指针来调用setFilled:的方法实现:

void (*setter)(id, SEL, BOOL);
int i;
setter = (void(*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];
for (i = 0; i < 1000; i++)
    setter(targetList[i], @selector(setFilled:), YES);

查找IMP的过程

说objc_class结构的时候说过,objc_msgSend 会根据方法选标 SEL 在类结构的方法列表中查找方法实现IMP。这里头有一些文章,我们在前面的类结构中也看到有一个叫objc_cache *cache 的成员,这个缓存为提高效率而存在的。每个类都有一个独立的缓存,同时包括继承的方法和在该类中定义的方法。。

下面这段是苹果官方的运行时源码

static Method look_up_method(Class cls, SEL sel,   
                             BOOL withCache, BOOL withResolver)  
{  
    Method meth = NULL; 
    if (withCache) {  
        meth = _cache_getMethod(cls, sel, &_objc_msgForward_internal);  
        if (meth == (Method)1) {  
            // Cache contains forward:: . Stop searching.  
            return NULL;  
        }  
    }  
    if (!meth) meth = _class_getMethod(cls, sel);  
    if (!meth  &&  withResolver) meth = _class_resolveMethod(cls, sel); 
    return meth;  
}  

看上面的源代码可以看出:
1、首先去该类的方法 cache中查找,如果找到了就返回它;
2、如果没有找到,就去该类的方法列表中查找。如果在该类的方法列表中找到了,则将 IMP返回,并将它加入cache中缓存起来。根据最近使用原则,这个方法再次调用的可能性很大,缓存起来可以节省下次调用再次查找的开销。3,3,如果在该类的方法列表中没找到对应的 IMP,在通过该类结构中的 super_class指针在其父类结构的方法列表中去查找,直到在某个父类的方法列表中找到对应的IMP,返回它,并加入cache中;
4、如果在自身以及所有父类的方法列表中都没有找到对应的 IMP,则看是不是可以进行动态方法决议;
5、如果动态方法决议没能解决问题,进入下面要讲的消息转发流程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值