OC方法本质

2024-09-25  本文已影响0人  星星杨

一、方法探索

先看一段代码,Person类中定义了sayHello及sayHappy方法;

{
    NSObject *objc; 
    NSString *nickName;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSObject *obj;


- (void)sayHello;
+ (void)sayHappy;

@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));
    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);

    IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));// 0
    // sel -> imp 方法的查找流程 imp_farw
    IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy)); // 0
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));

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

}

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        // LGTeacher *teacher = [LGTeacher alloc];
        LGPerson *person = [LGPerson alloc];
        Class pClass     = object_getClass(person);
        lgObjc_copyMethodList(pClass);
        const char *className = class_getName(pClass);
        Class metaClass = objc_getMetaClass(className);
        NSLog(@"*************");
        lgObjc_copyMethodList(metaClass);
//
        lgInstanceMethod_classToMetaclass(pClass);
        lgClassMethod_classToMetaclass(pClass);
        lgIMP_classToMetaclass(pClass);

        NSLog(@"Hello, World!");
    }
    return 0;
}
打印结果

Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));

从打印结果,我们会发现method3、method4都有值,而且都是一样的值0x100003d61;

引出一个问题:类方法跟元类方法一样?元类里面存在类方法?

为了论证这一问题,需要进一步研究class_getClassMethod在底层的实现;

可以看到当我们获取类的类方法的时候,等于获取类的元类(cls->getMeta)的对象方法;即类方法在元类里面以一种对象方法形式存在;

直观结论:类.类方法 = 类的元类.对象方法

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    // 类.类方法 = 类的元类.对象方法 - + -> 方法 - 消息 OC 唯一
    // cls->getMeta() 元类.对象
    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(); 
}

从这一点可以反向论证一点,在所有的OC中,底层根本就没有-或+方法,方法在底层本质都是消息,只是-方法存储在类里面,而+存储在元类里面,这也可以说明Apple为什么要设计元类,为了把对象方法跟类方法区分开存储;

回到之前的问题:类的元类会有类方法吗?即method4是否存在?
答:在cls->getMeta中我们可以发现有一个判断,如果当前的类是元类的时候,返回的是本身,否则通过ISA去查找它的元类;即pClass、metaClass返回的都是metaClass,method3和method4是一个的意思,都是打印的元类的对象方法;

而之所以要设计类方法,主要是为了节省内存,对象方法每次调用都需要生成对象,开辟内存;

类方法跟静态方法有什么不同?
个人认为有以下几点:
一是为了保证命名冲突问题,static静态方法可以在不同文件中声明一样的方法,会导致冲突,而类方法则不会;
二是静态方法在启动的时候会被提前加载,而类方法则不一样,依靠iOS的懒加载机制,在使用的时候才会被加载进内存,节约启动运行时间;

二、方法实现探索

打印方法实现,会发现比较让人困惑的事情,一是元类里面有对象方法sayHello的实现,二是对象同样也有类方法sayHappy的实现,而且这两个值地址都是0x1a5a01d20;


方法实现IMP

方法调用流程

上面有说到未被定义的方法,当我们获取它的IMP指针的时候,也会返回有值,这主要跟消息的查找有关系;


image.png

方法 -> 消息查找 -> 找不到 -> 消息转发

之前有探索过写过一篇关于深入探究cache_t的,里面有提到cache_t方法的插入流程,但是没有说在哪里调用cache_t::insert,接下来我们就探究一下,方法是何时插入我们的缓存的;

  • Cache readers (PC-checked by collecting_in_critical())
    objc_msgSend*
    cache_getImp
    // Cache readers/writers (hold cacheUpdateLock during access; not PC-checked)
    cache_t::copyCacheNolock (caller must hold the lock)
    cache_t::eraseNolock (caller must hold the lock)
    cache_t::collectNolock (caller must hold the lock)
    cache_t::insert (acquires lock)
    cache_t::destroy (acquires lock)

在objc-cache.mm里面找到一些注释,里面有提到一个方法的调用顺序,在cache_t结构体方法调用之前有提到两个方法objc_msgSend、cache_getImp,接下来就看看这俩方法是干嘛的;

三、方法本质

首先看一个简单的方法调用

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *p = [[LGPerson alloc] init];
        [p sayHello];
        NSLog(@"%@",p);
    }
    return 0;
}

通过clang -rewrite-objc main.m得到编译后的问题,main函数如下:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        LGPerson *p = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("sayHello"));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_1j_qwn3218x4x7037_0c5kty2cr0000gn_T_main_3eef07_mi_0,p);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_1j_qwn3218x4x7037_0c5kty2cr0000gn_T_main_3eef07_mi_1);
    }
    return 0;
}
上一篇 下一篇

猜你喜欢

热点阅读