乐趣区

iOS笔记-1SEL的原理与使用

概念

SEL:方法名(编号)
IMP:一个函数指针, 保存了方法的地址
@selector(方法名) 获取方法的编号,结果是 SEL 类型。他的行为基本可以等同于 C 语言中的函数指针
区别

 C 语言中,可以直接把函数名赋值给一个函数指针,而且函数指针直接保存了函数地址
Objc 中的类不能直接应用函数指针,只能使用 @selector 来获取,获取的是方法的编号

方法以 @selector 作为索引,@selector 的数据类型是 SEL,对应每个方法的编号,当我们寻找方法的时候使用的是这个方法编号。类中存在一个 methodLists 专门用来存放方法实现 IMP 和 SEL 的映射。方法编号 SEL 通过 Dispatch table 表寻找到对应的 IMP,IMP 就是一个函数指针,然后执行这个方法。

struct objc_class {

    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;   /* 协议链表 */

};
typedef struct objc_method *Method;

typedef struct objc_ method {

    SEL method_name;        // 方法名

    char *method_types;        // 参数类型

    IMP method_imp;            // 方法实现

};

相关

class   返回对象的类;isKindOfClass 和 isMemberOfClass 检查对象是否在指定的类继承体系中;respondsToSelector 检查对象能否相应指定的消息;conformsToProtocol 检查对象是否实现了指定协议类的方法;methodForSelector  返回指定方法实现的地址。performSelector:withObject 执行 SEL 所指代的方法。

具体实现

在寻找 IMP 的地址时,runtime 提供了两种方法:

IMP class_getMethodImplementation(Class cls, SEL name);
IMP method_getImplementation(Method m)

而根据官方描述,第一种方法可能会更快一些

class_getMethodImplementation may be faster than method_getImplementation(class_getInstanceMethod(cls, name)).

(一)、对于第一种方法而言,类方法和实例方法实际上都是通过调用 class_getMethodImplementation() 来寻找 IMP 地址的,不同之处在于传入的第一个参数不同

/// 类方法(假设有一个类 ClassA)class_getMethodImplementation(objc_getMetaClass("ClassA"),@selector(methodName));

/// 实例方法
class_getMethodImplementation([ClassA class],@selector(methodName));

通过该传入的参数不同,找到不同的方法列表,方法列表中保存着下面方法的结构体,结构体中包含这方法的实现,selector 本质就是方法的名称,通过该方法名称,即可在结构体中找到相应的实现。

(二)、对于第二种方法而言,传入的参数只有 method,区分类方法和实例方法在于封装 method 的函数

/// 类方法
Method class_getClassMethod(Class cls, SEL name)

/// 实例方法
Method class_getInstanceMethod(Class cls, SEL name)

/// 获取 IMP 地址
IMP method_getImplementation(Method m) 

(三)、测试代码

@implementation TestSelAndImp

- (instancetype)init {self = [super init];
    if (self) {[self getIMPFromSelector:@selector(aaa)];
        [self getIMPFromSelector:@selector(test1)];
        [self getIMPFromSelector:@selector(test2)];
    }
    return self;
}

- (void)test1 {NSLog(@"test1");
}

- (void)test2 {NSLog(@"test2");
}

- (void)getIMPFromSelector:(SEL)aSelector {
    // First method, use 'class_getMethodImplementation'
    IMP classInstanceIMP_1 = class_getMethodImplementation(objc_getClass("TestSelAndImp"), aSelector);
    IMP metaClaseIMP_1 = class_getMethodImplementation(objc_getMetaClass("TestSelAndImp"), aSelector);
    
    // Second method, use 'method_getImplementation'
    Method classInstanceMethod_2 = class_getInstanceMethod(objc_getClass("TestSelAndImp"), aSelector);
    IMP classInstnceIMP_2 = method_getImplementation(classInstanceMethod_2);
    
    Method classMethod_2 = class_getClassMethod(objc_getClass("TestSelAndImp"), aSelector);
    IMP classIMP_2 = method_getImplementation(classMethod_2);
    
    Method metaClassMethod_2 = class_getClassMethod(objc_getMetaClass("TestSelAndImp"), aSelector);
    IMP metaClassIMP_2 = method_getImplementation(metaClassMethod_2);
    
    NSLog(@"selectorName: %@, classInstanceIMP_1: %p",NSStringFromSelector(aSelector), classInstanceIMP_1);
    NSLog(@"selectorName: %@, metaClaseIMP_1: %p",NSStringFromSelector(aSelector),metaClaseIMP_1);
    NSLog(@"selectorName: %@, classInstnceIMP_2: %p",NSStringFromSelector(aSelector),classInstnceIMP_2);
    NSLog(@"selectorName: %@, classIMP_2: %p,",NSStringFromSelector(aSelector), classIMP_2);
    NSLog(@"selectorName: %@, metaClassIMP_2: %p",NSStringFromSelector(aSelector),metaClassIMP_2);
    NSLog(@"-------");
}

@end

打印结果:

调用 class_getMethodImplementation() 方法时,无法找到对应实现时返回的相同的一个地址,无论该方法是在实例方法或类方法,无论是否对一个实例调用该方法,返回的地址都是相同的,但是每次运行该程序时返回的地址并不相同,而对于另一种方法method_getImplementation(),如果找不到对应的实现,则返回 0。

还有 class_getClassMethod() 的第一个参数无论传入 objc_getClass() 还是 objc_getMetaClass(),最终调用method_getImplementation() 都可以成功的找到类方法的实现。

class_getInstanceMethod() 的第一个参数如果传入 objc_getMetaClass(),再调用method_getImplementation() 时无法找到实例方法的实现却可以找到类方法的实现。

退出移动版