IOS SEL(@selector)原理
其中@selector()是取类方法的编号,取出的结果是SEL类型。
SEL:类成员方法的指针,与C的函数指针不一样,函数指针直接保存了方法的地址,而SEL只是方法的编号。
说一下C的函数指针:
int addOne(int val)
{
return val + 1;
}
int main(int argc, char *argv[]) {
int (*p)(int val);//定义一个函数指针
p = addOne;//p 指向addOne的地址
cout<<"result :"<<(*p)(5)<<endl;//直接通过函数指针调用函数
}
objective-c 中时:
SEL method = @selector(func);//定义一个类方法的指针,selector查找是当前类(包含子类)的方法
objective demo:
父类 SelectorDemo.h
#import <Foundation/Foundation.h>
@interface SelectorDemo : NSObject
@property (nonatomic, assign) SEL methodTest;
-(void)TestParentMethod;
-(void)TestChildMethod;
@end
selectorDemo.m
#import "SelectorDemo.h"
@implementation SelectorDemo
-(void)parentMethod{
NSLog(@"parent method call success");
}
-(void)TestParentMethod{
if (_methodTest) {
[self performSelector:_methodTest withObject:nil];
}
}
-(void)TestChildMethod{
if (_methodTest) {
[self performSelector:_methodTest withObject:nil];
}
}
@end
子类SelectorSub.h
#import "SelectorDemo.h"
@interface SelectorSub : SelectorDemo
@end
子类SelectorSub.m
#import "SelectorSub.h"
@implementation SelectorSub
-(void)SubMethod{
NSLog(@"sub class method call success");
}
@end
调用:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
SelectorSub *SClass = [[SelectorSub alloc] init];
SClass.methodTest = @selector(parentMethod);
[SClass TestChildMethod];
SClass.methodTest = @selector(SubMethod);
[SClass TestParentMethod];
}
运行的结果如下:
2017-11-16 10:52:16.089 SEL方法[50141:9692483] parent method call success
2017-11-16 10:52:16.091 SEL方法[50141:9692483] sub class method call success
我们看到SClass.methodTest = @selector(parentMethod);
时候子类的对象去通过@selector()
方法去寻找方法的ID,这时寻找的范围包括本身和父类,我们找到方法之后将ID赋值给自身的成员变量_methodTest
,之后的调用就是直接调用父类的TestChildMethod
方法。
SClass.methodTest = @selector(SubMethod);
通过@selector()
去父类寻找方法,没有找到继续找子类的方法,找到之后继续调用,没有找到之后会报地址寻找出错。
注意:
我们在调用方法的时候,调用不属于自己的方法和调用没有定义的方法是一样的,所以需要我们验证方法是否返回消息(即是否实现这个方法),所以在不确定的时候需要respondsToSelector: 来确定方法是否定义。
SEL消息机制的工作原理:
在作为所有类的根类的NSObject 中.isa的成员变量,所以所有的对象都有一个isa的变量,而isa变量指向该对象的类。
查看NSObject的类的时候可以发现
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
在runtime.h的查看类objc_class的时候,我们可以看到一个包含isa指针的结构体。所以类也是一个对象,同时它也必须是另一个类的实例,这份类就是元类。
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
元类同时也是一个对象,它指向的是根元类,根元类本身的isa 指针指向自己,就形成了闭环。
接着上面继续,所有的对象都有一个isa的变量,而isa变量指向该对象的类。类其实也是实体的存在, 程序运行时每个类都有自己的存储空间,而isa 便指向这样一个类的空间,便建立了类和对象的对应关系,类空间包含了该类的成员变量以及方法实现,还包含指向父类空间的指针。
方法以selector作为索引,selector的数据类型是SEL,对应每个方法的位置的ID,当我们寻找方法的时候寻找的是方法的ID,存在一个方法和ID对应的methodList表来存储这种对应关系。
selector-funName关系图:
编译时,编译器会通过selector来查找
[myobject funMethod1:para];
编译之后的方法应是:
objc_msgSend(myObject, 8, para);
这里的objc_msgSend()函数会使用myObject的isa指针来找到myObject放入类空间结构并在类空间结构中查找selector 8所对应的方法,如果没有找到,那么将使用指向父类的指针找到父类空间结构进行 selector 8方法的查找,如果还没有找到,就继续沿着父类网上找,直到找到,若果一直到NSObject还没有找到,就会抛异常。