理解 id, Class, isa, SEL, IMP, Met
id 类型
首先介绍两个概念:静态类型和动态类型。
静态类型:将一个指针变量定义为特定类的对象时,使用的是静态类型,在编译的时候就知道这个指针变量所属的类,比如:(下面的p
指针就是静态类型)
NSString *p = [[NSString alloc] init];
动态类型:程序运行时才确定变量的类,比如:(下面的p
指针在编译的时候不知道自己的类型,直到运行时才直到)
id p = [[NSString alloc] init];
id
就是一种动态类型,它可以指向属于任何继承与NSObject
类的对象,这样id
就可以调用项目中所有继承与NSObject
类的方法(但可能调用的方法不在对象中,要小心使用),比如:
@interface Person : NSObject
- (void)speak;
@end
@implementation Person
- (void)speak {
NSLog(@"hello");
}
@end
id p = [[NSString alloc] init];
[p speak]; // 编译可以通过,运行时报错:unrecognized selector sent to instance 0x7fff8b02f288"
id
在内部是如何实现的呢?其实id
是一个结构体指针类型:
typedef struct objc_object *id {
Class isa;
};
可以看出id
内部有一个Class
类型的指针isa
,这里引入了两个新概念:Class
和isa
。
Class 类型和 isa 指针
Class 也是一个结构体指针类型:
typedef struct objc_class *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
};
id
是对象的类型,Class
就是类的类型了,id
通过isa
指针,指向了他的类,他的类的类型就是Class
。
从Class
结构体的定义中,我们可以看到,内部有一个isa
指针,指向类的元类(meta class),有一个super_class指针,指向Class
的父类,还有objc_ivar_list
,objc_method_list
和objc_protocol_list
结构体指针,分别指向类的实例的成员变量列表、方法列表和协议列表。
因为元类和父类也都是 Class
类型,我们可以推测出元类和父类也有和类相同的数据结构(有元类和父类、成员变量列表、方法列表和协议列表等),而且类的方法和类的变量都储存在元类中,子类通过继承的方式可以访问父类的方法列表和父类的元类的方法类别。但元类和父类不可能无限循环的指向更高一级的元类和父类,OC中类的关系到底是怎样的呢?下面这张经典的关系图说明了一切:
其中Root class就是NSObject
,所有类的元类的isa
指针都指向了NSObject
的元类,NSObject
的元类的isa
指针指向了自己,结束了循环。NSObject
的元类的父类指向了NSObject
,NSObject
的父类为空。
SEL 类型
SEL
是一个结构体指针类型,也就是selector:
typedef struct objc_selector *SEL;
然而官方并没有给出objc_selector
这个结构体的定义,下面总结已知的所有信息:
selector是指向objc_selector
结构体的指针, selector 是一个已被Objective-C运行时注册过或映射过的C语言字符串, 如果想创建 selector ,必须使用 sel_registername
的返回值或者编译器指令@selector()
(官方解释)。
这里说selector是C语言字符串,我们打印试试:
SEL testSelector = @selector(init);
NSLog(@"%s", testSelector); // init
结果还真是,可以推测,objc_selector结构体内部的第一个成员很有可能就是一个 char *
类型的字符串,保存着 selector 的名字(因为结构体指针指向的地址其实就是第一个成员的地址,这与数组指针指向的地址和首元素地址相同是一样的道理)。其他信息就无从得知了,至少目前苹果没有公布相关的代码,可能是出于安全的考虑吧。
sel_registername
的返回值是创建selector的唯一方法,虽然编译器指令 @selector()
找不到具体实现,其底层底层仍然是使用的函数 sel_registername
,验证如下:
编译main.m
中的如下代码(clang -rewrite-objc main.m
):
SEL testSelector = @selector("init");
结果如下:
SEL testSelector = sel_registerName("init");
那sel_registerName
是怎么实现的呢?简单的总结就是:
- 如果方法名为空,返回0。
- 如果方法名内建函数同名,返回内建函数的 selector。
- 如果方法名如方法名不与内建函数同名,则以传入的方法名为键,在
NXMapTable
(一个哈希表,键为方法名,值为方法名对应的selector)中匹配,如果NXMapTable
中有这个方法名 ,返回这个方法名的selector。 - 如果方法名未在
NXMapTable
中找到,那么会新创建一个selector,并与方法名互相映射成键值对,保存在NXMapTable
中。
调用SEL
:
SEL testSelector1 = @selector(alloc);
SEL testSelector2 = @selector(init);
NSString *testString = [[NSString performSelector:testSelector1] performSelector:testSelector2];
获取SEL
的方法名:
SEL testSelector1 = @selector(alloc);
NSString *testString = NSStringFromSelector(testSelector1);
NSLog(@"%@", testString); // alloc
IMP
IMP
(implement)实际上是一个函数指针,指向方法实现的首地址。代表了方法的最终实现。其定义如下:
typedef id (*IMP)(id, SEL, ...)
对一个参数是id
类型,是一个指向消息接收对象的指针,如果是实例方法,则是类实例的内存地址;如果是类方法,则是类对象的指针。第二个参数是方法选择器(selector),省略号是方法的参数。返回的是id
,我们可以推测:方法的返回类型可以是任何对象类型(也可以是nil
),具体在运行的时候动态决定。
这样,通过对象、方法选择器和方法参数,就可以确定要调用的方法,调用IMP
的就如同调用一个函数:
@interface Person : NSObject
- (NSString *)speak;
@end
@implementation Person
- (NSString *)speak {
return @"hello";
}
@end
Person *person = [[Person alloc] init];
SEL testSelector = @selector(speak);
IMP imp = [person methodForSelector:testSelector];
// 调用 imp
NSLog(@"%@", imp(person, testSelector)); // hello
// 等价于:
NSLog(@"%@", [person performSelector:testSelector]); // hello
Method
Method
的定义如下:
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
可知,Method
类型是一个结构体指针,包含了SEL
和IMP
,可知一个Method
类中包含了一个方法选择器SEL
,这个方法选择器的调用函数IMP
,也就是说,给我们一个类和一个方法选择器,那我们就可以确定一个Method
。那具体有什么用呢?可以用在方法交换上(Method Swizzling)
比如交换实例方法:
@interface Person : NSObject
- (void)speak;
- (void)run;
@end
@implementation Person
- (void)speak {
NSLog(@"hello");
}
- (void)run {
NSLog(@"run");
}
@end
Person *person = [[Person alloc] init];
Method m1 = class_getInstanceMethod([Person self], @selector(speak));
Method m2 = class_getInstanceMethod([Person self], @selector(run));
method_exchangeImplementations(m1, m2);
[person speak]; // run
[person run]; // speak
交换类方法:
@interface Person : NSObject
- (void)speak;
- (void)run;
@end
@implementation Person
+ (void)speak {
NSLog(@"hello");
}
+ (void)run {
NSLog(@"run");
}
@end
Method m1 = class_getClassMethod([Person self], @selector(speak));
Method m2 = class_getClassMethod([Person self], @selector(run));
method_exchangeImplementations(m1, m2);
[Person speak];
[Person run];
Method Swizzling的实现机理,就是交换了Method
中IMP
的指针的指向,来交换方法的具体实现。