runtime(一)

学习OC编程了一直没有怎么接触过runtime,可是这个又很重要,最近学习下,做个小结。

事情还得从OC独有消息发送说起。OC底层的执行机制跑的是C语言,其实整个iOS系统的底层大部分都是由C语言写的,因此,OC中方法调用即发送消息最终都要变为C语言代码执行(个人理解是这样)。C语言中的方法执行是通过函数指针执行的,runtime机制完成的任务就是把消息发送转为函数指针。事情的开始就是下面这条语句。

[self test];       //示例方法调用
[receiver message];//抽象形式

经过编译器会变成:

objc_msgSend(receiver, selector)

如果有参数则是:

objc_msgSend(receiver, selector, arg1, arg2, ...)

以下所有的内容(包括后面的博客)都是把上面代码转为函数指针然后执行代码的过程。

先准备预备知识。

在runtime中有三个名字,消息,方法,方法选择器。

①message(消息)message的具体定义很难说,因为并没有真正的代码描述,简单的讲message 是一种抽象,包括了函数名+参数列表,他并没有实际的实体存在。

②method(方法)method是真正的存在的代码。如:- (int)meaning { return 42; }

③selector(方法选择器)selector 通过SEL类型存在,描述一个特定的method 或者说 message。在实际编程中,可以通过selector进行检索方法等操作。

这三个名词了解下。真正重要的是以下三个:SEL,IMP和id。

1.SEL

又叫方法选择器,在OC中如下定义(这些定义的查看可以包含头文件objc/objc.h,记得没错应该是)。

struct objc_selector *SEL;

关于objc_selector这个结构体我也不清楚,但是据网上查阅SEL它是个char*指针,代表方法的名字。可以再代码中用如下方法验证。

SEL selector = @selector(message);  //@selector不是函数调用,只是给编译器的一个提示   
NSLog (@"%s", (char *)selector);    //print message 

还记得在初学OC时就教导我们,OC的方法的命名只与冒号前面的名称有关,与参数类型无关,与参数名无关。

//下面的两种命名会报错
-(void)setWidth:(int)width;
-(void)setWidth:(double)width

//要改为的命名
-(void)setWidthIntValue:(int)width;
-(void)setWidthDoubleValue:(int)width

每次给方法想名字都很麻烦!每次名字都很长!都是为了这个SEL,它就是用字符串来区分方法的。编译器会根据每个方法的方法名为方法生成唯一的SEL,这些SEL组成了一个Set集合,这个Set简单的说就是一个经过了优化过的hash表。SEL实际上就是根据方法名hash化了的一个字符串(注意,下面方法选择Cache中选择方法时也用到了hash表)。而Set的特点就是唯一,也就是SEL是唯一的,因此,如果我们想到这个方法集合中查找某个方法时,只需要去找到这个方法对应的SEL就行了,但是,有一个问题,就是数量增多会增大hash冲突而导致的性能下降(或是没有冲突,因为也可能用的是perfect hash)。但是不管使用什么样的方法加速,如果能够将总量减少(多个方法可能对应同一个SEL),那将是最犀利的方法。

到这里就清楚了SEL就是编译器根据方法名hash化的一个字符串,程序中所有的SEL都放在一个set集合中,而且相同的名称的方法对应同一个SEL。而方法由那个对象执行或者说是接收这个方法由isa指针和一下的IMP来决定。

2,IMP

IMP在objc.h中是如下定义的。

typedef id (*IMP)(id, SEL, ...); 

这个比SEL要好理解多了,熟悉C语言的同学都知道,这其实是一个函数指针。这里插播一下方法的调用。方法调用或者说是函数执行就是执行一段代码,关键是确定这段函数的代码地址。C语言中(runtime就是C写的)所有的函数调用都是转换为函数指针后才能确定代码位置再执行代码。so,函数指针的作用即指向目标函数代码地址。而runtime中如何确定代码地址呢,就是用IMP。

IMP如何获得正确的函数代码地址呢?用SEL和id。方法名虽然在整个程序空间中可以重复,但是在单个类中却必须唯一,即在每个类中SEL能确定一个唯一的方法。id就是来确定类的。这两个参数都确定了就可以确定函数指针,也就可以确定代码地址了,runtime就可以像我们平时调用C语言函数一样执行OC的方法了。至于SEL后面的……是用来表示执行方法的参数的。

举个例子:

//定义一个IMP(函数指针),是不是有点像block
void (* performMessage)(id,SEL);
//通过methodForSelector方法根据SEL获取对应的函数指针 
performMessage = (void (*)(id,SEL))[self methodForSelector:@selector(message)];
//通过取到的IMP(函数指针)跳过runtime消息传递机制,直接执行message方法
performMessage(self,@selector(message));

3,id

objc_msgSend第一个参数类型为id,对它都不陌生,它是一个指向类实例的指针:

typedef struct objc_object *i
objc_object又是什么鬼呢:

struct objc_object { Class isa; };
objc_object结构体包含一个isa指针,根据isa指针就可以顺藤摸瓜找到对象所属的类,即找到相应的class。


另外再补充一些知识。

class:

之所以说isa是指针是因为Class其实是一个指向objc_class结构体的指针:

1
typedef struct objc_class *Class;

objc_class就是我们摸到的那个瓜,里面的东西多着呢:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#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;

可以看到运行时一个类还关联了它的超类指针,类名,成员变量,方法,缓存,还有附属的协议。 
其中objc_ivar_listobjc_method_list分别是成员变量列表和方法列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct objc_ivar_list {
    int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;

    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}

如果你C语言不是特别好,可以直接理解为objc_ivar_list结构体存储着objc_ivar数组列表,而objc_ivar结构体存储了类的单个成员变量的信息;同理objc_method_list结构体存储着objc_method数组列表,而objc_method结构体存储了类的某个方法的信息。

最后要提到的还有一个objc_cache,顾名思义它是缓存,它在objc_class的作用很重要,在后面会讲到。

不知道你是否注意到了objc_class中也有一个isa对象,这是因为一个 ObjC 类本身同时也是一个对象,为了处理类和对象的关系,runtime 库创建了一种叫做元类 (Meta Class) 的东西,类对象所属类型就叫做元类,它用来表述类对象本身所具备的元数据。类方法就定义于此处,因为这些方法可以理解成类对象的实例方法每个类仅有一个类对象,而每个类对象仅有一个与之相关的元类。当你发出一个类似[NSObject alloc]的消息时,你事实上是把这个消息发给了一个类对象 (Class Object) ,这个类对象必须是一个元类的实例,而这个元类同时也是一个根元类 (root meta class) 的实例。你会说 NSObject 的子类时,你的类就会指向 NSObject 做为其超类。但是所有的元类最终都指向根元类为其超类。所有的元类的方法列表都有能够响应消息的类方法。所以当 [NSObject alloc] 这条消息发给类对象的时候,objc_msgSend()会去它的元类里面去查找能够响应消息的方法,如果找到了,然后对这个类对象执行方法调用。

上图实线是 super_class 指针,虚线是isa指针。 有趣的是根元类的超类是NSObject,而isa指向了自己,而NSObject的超类为nil,也就是它没有超类。

Method

Method是一种代表类中的某个方法的类型。

1
typedef struct objc_method *Method;

objc_method在上面的方法列表中提到过,它存储了方法名,方法类型和方法实现:

1
2
3
4
5
struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;
  • 方法名类型为SEL,前面提到过相同名字的方法即使在不同类中定义,它们的方法选择器也相同。
  • 方法类型method_types是个char指针,其实存储着方法的参数类型和返回值类型。
  • method_imp指向了方法的实现,本质上是一个函数指针,后面会详细讲到。
Ivar

Ivar是一种代表类中实例变量的类型。

1
typedef struct objc_ivar *Ivar;

objc_ivar在上面的成员变量列表中也提到过:

1
2
3
4
5
6
7
8
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;

PS:OBJC2_UNAVAILABLE之类的宏定义是苹果在 Objc 中对系统运行版本进行约束的黑魔法,有兴趣的可以查看源代码。



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值