美文网首页
实例方法与类方法的归属

实例方法与类方法的归属

作者: 杨奇 | 来源:发表于2021-07-13 23:38 被阅读0次

我们知道实例方法存储在类中,类方法存储在元类中
今天要探索的是,类方法是属于类还是属于元类
1.创建一个类,定义一个实例方法和类方法做比较

@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;
@end
@implementation LGPerson
- (void)sayHello{
    NSLog(@"LGPerson say : Hello!!!");
}

+ (void)sayHappy{
    NSLog(@"LGPerson say : Happy!!!");
}
@end

main.m中这样定义

void lgObjc_copyMethodList(Class pClass){
    unsigned int count = 0;
    Method *methods = class_copyMethodList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Method const method = methods[i];
        //获取方法名
        NSString *key = NSStringFromSelector(method_getName(method));
        
        LGLog(@"Method, name: %@", key);
    }
    free(methods);
}

void lgInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

void lgClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHello));
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));

    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    // 元类 为什么有 sayHappy 类方法 0 1
    //
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

void lgIMP_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    // - (void)sayHello;
    // + (void)sayHappy;
    IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));

    IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));

    NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
    NSLog(@"%s",__func__);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        // 0x0000000100000000
        // LGTeacher *teacher = [LGTeacher alloc];

        LGPerson *person = [LGPerson alloc];
        Class pClass     = object_getClass(person);
        lgObjc_copyMethodList(pClass);

        lgInstanceMethod_classToMetaclass(pClass);
        lgClassMethod_classToMetaclass(pClass);
        NSLog(@"Hello, World!");
    }
    return 0;
}

2.lgObjc_copyMethodList用于获取类的方法列表

3.lgInstanceMethod_classToMetaclass用于获取类的实例方法

4.lgClassMethod_classToMetaclass用于获取类的类方法

  1. lgIMP_classToMetaclass用户获取方法的调用
打印后结果如下
输出
这么看未免太乱,依次单个分析
  • pClass为LGPerson类
  • metaClass为LGPerson元类
  • method1为从类中 sayhello相应的方法
  • method2为从元类中 sayhello相应的方法
  • method3为从类中 sayHappy相应的方法
  • method4为从元类中 sayHappy相应的方法
  • -(void)sayHello;
  • +(void)sayHappy;
  • -(void)sayHello{
    NSLog(@"LGPerson say : Hello!!!");
    }
  • +(void)sayHappy{
    NSLog(@"LGPerson say : Happy!!!");
    }
lgObjc_copyMethodList 类的方法列表
Method, name: sayHello

在这个函数中获取的是LGPerson类的方法列表,因为实例方法储存在类中,类方法存储在元类中,所以只打印了sayHello方法。

lgInstanceMethod_classToMetaclass 获取类的实例方法
    • 分析之前,需要先了解class_getInstanceMethod方法,从return Value 后半句得如果传入的类或其父类不包含具有指定的实例方法,则为NULL
class_getInstanceMethod
看打印
lgInstanceMethod_classToMetaclass - 0x1000081e8--0x0--0x0--0x100008180
  • method1:0x1000081e8

    • 中查找是否有sayHello实例方法,有返回查找的实例方法
  • method2:0x0

    • 元类中查找是否有sayHello实例方法,没有找到,则从元类的父类根元类中查找,没有找到,继续从根元类的父类NSObject中查找,没有找到,从NSObject父类nil中查找,也没找到。返回NULL。所以地址为0x0。
  • method3:0x0

    • 中查找是否有sayHappy实例方法,没有找到,则从类的父类根类(NSObject)中查找,没有找到。从NSObject父类nil中查找,也没找到。返回NULL。所以地址为0x0。
  • method4:0x100008180

    • 元类中查找是否有sayHappy实例方法,找到了sayHappy的实例方法,返回方法的地址。
lgClassMethod_classToMetaclass 获取类的类方法
    • 先了解class_getClassMethod方法,如果传入的类或者类的父类中没有找到指定的类方法,则返回NULL
      找到该方法的底层源码,可以看到class_getClassMethod方法, 调用了class_getInstanceMethod(cls->getMeta(), sel),可以得出,获取类的类方法,就是获取元类的实例方法。需要注意在getMeta()中,会先判断cls,如果是元类会直接返回this。其目的是为了防止元类的无限递归查找。
Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}
-------------------------
// NOT identical to this->ISA when this is a metaclass
// 当这是一个元类时,返回。否则继续查找。
Class getMeta() {
    if (isMetaClassMaybeUnrealized()) return (Class)this;
    else return this->ISA()
}

图示如下:


获取类方法流程.jpg
lgClassMethod_classToMetaclass - 0x0--0x0--0x100008180--0x100008180

了解底层逻辑后,分析起来就得心应手了

  • method1:0x0
    • 首先判断是否是元类,否,返回元类,然后在元类中查找sayHello实例方法。按照元类--->根源类 ---> 根类--->nil,都没有找到,返回NULL。
  • method2:0x0
    • 首先判断元类是否是元类,是,直接返回元类,然后在元类中查找sayHello实例方法。按照元类--->根源类 ---> 根类--->nil,都没有找到,返回NULL。
  • method3: 0x100008180
    • 首先判断是否是元类,否,返回元类,然后在元类中查找sayHappy实例方法。按照元类--->根源类 ---> 根类--->nil元类中找到了,返回实例方法的地址。
  • method4: 0x100008180
    • 首先判断是否是元类,是,直接返回元类,然后在元类中查找sayHappy实例方法。按照元类--->根源类 ---> 根类--->nil元类中找到了,返回实例方法的地址。
      现在出现疑问了,我们会发现method4,它既是元类的实例方法,又是元类的类方法。为什么元类中有sayHappy类方法?这个是因为我们上边说的防止元类的无限递归。在class_getClassMethod判断是直接返回元类,所以此时查找类方法时,然后按照class_getInstanceMethod就变成了查找元类中的实例方法。所以会查找到。
lgIMP_classToMetaclass 返回方法的具体实现
    • 先了解class_getMethodImplementation方法,得出class_getMethodImplementation()可能比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函数指针可能是运行时内部的函数,而不是实际的方法实现。例如,如果类的实例不响应选择器,则返回的函数指针将是运行时消息转发机制的一部分

底层代码

IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;

    if (!cls  ||  !sel) return nil;

    lockdebug_assert_no_locks_locked_except({ &loadMethodLock });

    imp = lookUpImpOrNilTryCache(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);

    // Translate forwarding function to C-callable external version
    if (!imp) {
        return _objc_msgForward;
    }

    return imp;
}

打印结果分析验证

gIMP_classToMetaclass - 0x100003d20--0x7fff2049c5c0--0x7fff2049c5c0--0x100003d50

  • imp1: 0x100003d20
    • 中可以查找到sayHello的具体实现,所以返回imp函数指针的地址
  • imp2: 0x7fff2049c5c0
    • 元类中没有sayHello的具体实现,因为实例方法存储在类中,所以进行了消息转发
  • imp3: 0x7fff2049c5c0
    • 中没有sayHappy的具体实现,因为类方法存储在元类中,所以进行了消息转发
  • imp4: 0x100003d50
    • 元类中有sayHappy的具体实现,因为类方法存储在元类中,所以返回imp函数指针的地址

总结

  • class_getInstanceMethod:获取实例方法,如果传入的类或其父类带有指定选择器的实例方法,返回方法地址,否则返回NULL。
  • class_getClassMethod:获取类方法,如果传入的类或其父类带有指定选择器的类方法,返回方法地址,否则返回NULL。
  • class_getMethodImplementation:获取方法的具体实现,如果传入的类有指定方法的实现,返回imp函数指针地址,否则进行消息转发。

相关文章

  • 关于isa走位的2道经典面试题

    第一道经典面试题:类方法的归属分析 关于类方法和实例方法的归属分析,我们首先得知道:实例方法是在类中,而类方法是在...

  • 实例方法与类方法的归属

    我们知道实例方法存储在类中,类方法存储在元类中今天要探索的是,类方法是属于类还是属于元类1.创建一个类,定义一个实...

  • 实例属性,类属性,实例方法,类方法,静态方法

    实例方法、静态方法和类方法 三种方法在内存中都归属于类,区别在于调用方法不同实例方法:由对象调用;至少一个self...

  • iOS中两道经典面试题解析

    1.方法归属 自定义类一个实例方法和一个类方法 main函数调用 lgObjc_copyMethodList 函数...

  • Objective-C底层面试题总结

    方法的归属问题探索 定一个Person类,定义一个实例方法,一个类方法,并完成实现 获取类的方法并打印出来 获取实...

  • Swift3.0-实例方法

    实例方法 实例方法是属于某个特定类、结构体或者枚举类型实例的方法。实例方法提供访问和修改实例属性的方法或提供与实例...

  • Python 实例方法、类方法和静态方法(转)

    原文链接概括来说,是否与类或者实例进行绑定,这就是实例方法、类方法、静态方法的区别。 在 Python 中,实例方...

  • Swift 中的方法声明

    实例方法 类方法 静态方法 实例方法由实例对象进行调用;类方法和静态方法由类对象进行调用

  • Swift--方法

    实例方法 静态方法 实例方法 实例方法与实例属性类似,都隶属于枚举、结构体或类的个体(即实例),我们通过实例化这些...

  • Swift基础-12(方法)

    1.实例方法 实例方法是属于某个特定类、结构体或者枚举类型实例的方法。实例方法提供访问和修改实例属性的方法或提供与...

网友评论

      本文标题:实例方法与类方法的归属

      本文链接:https://www.haomeiwen.com/subject/jfospltx.html