OC类相关的经典面题分析

2020-09-15  本文已影响0人  闭家锁
1、类存在几份?

由于类的信息在内存中永远只存在一份,所以 类对象只有一份,同样,元类对象也只有一份,在lldbpo类对象和元类对象的地址会输出相同的类名,因为po会调用类的description方法。

2、objc_object 与 对象的关系

objc_object是OC类的c/c++实现,没有直接的联系,编译器会在编译阶段将OC语法的类转译为c/c++objc_object结构体实现,objc_object结构体是OC类的底层模板。通过typedef struct objc_object *id;定义可以看到id这个可以指向任意OC类关键字实际是指向objc_object结构体的指针。

3、什么是 属性 & 成员变量 & 实例变量 ?
4、如何证明元类中存放着类方法
//定义Person类
@interface Person : NSObject
- (void)sayHi;
+ (void)sayHaHa;
@end
//添加类方法和实例方法
#import "Person.h"
@implementation Person
- (void)sayHi{
    NSLog(@"Person say : Hi!!!");
}
+ (void)sayHaHa{
    NSLog(@"Person say : HaHa!!!");
}
@end

通过运行时方法class_copyMethodList可以获取一个类的实例的方法列表:

//打印方法名
void objc_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));
        
        CHLog(@"Method, name: %@", key);
    }
    free(methods);
}

main中运行以上代码:

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

        Person *person = [Person alloc];
        Class pClass     = object_getClass(person);
        objc_copyMethodList(pClass);

    }
    return 0;
}

输出结果:

打印类实例方法.png
objc_copyMethodList法中我们传入了Person类对象,所以只会输出类的实例方法sayHi,符合预期,再看下面的方法输出:
void instanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHi));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHi));

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

这个方法分别针对Person类和元类调用class_getInstanceMethod方法,看看打印结果:

类元类获取方法地址.png
可以看到共打印了4个方法的地址,只有method1method4打印出了内容,其余都是0x0说明没找到对应的方法,这就很好的证明了实例方法sayHi可以在类对象pClass中找到,而类方法sayHaHa,只能在metaClass元类中找到,这4个方法都调用了class_getInstanceMethod,从方法名中可以判断是获取实例方法,进而说明对元类调用获取实例方法,就相当于获取一个类的类方法,因为类在内存中也是一个特殊的实例,元类中存储的就是这个特殊实例的实例方法。
void classMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHi));
    Method method2 = class_getClassMethod(metaClass, @selector(sayHi));

    Method method3 = class_getClassMethod(pClass, @selector(sayHaHa));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHaHa));
    
    CHLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

将上面方法中调用的class_getInstanceMethod换成class_getClassMethod,分别对类和元类进行测试,看看打印结果:

对类和元类调用获取类方法.png
打印结果只有method3method4打印出了地址,分析之前看看class_getClassMethod的源码实现:
//获取类方法
Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}
//获取一个类的元类,如果本身是元类,就直接返回自己,否则返回类的ISA()找到元类
Class getMeta() {
    if (isMetaClass()) return (Class)this;
    else return this->ISA();
}

为什么元类的getMeta()会返回自己,这个是为了防止无限递归,因为对一个类调用class_getClassMethod会顺着类的isa找到类的元类,而对元类调用class_getClassMethod的话,如果不返回元类自身,还是沿着元类的isa查找,最终会找到根元类,所有元类的isa都指向根元类,而根元类的isa指向了自己,就会在这里形成死循环,为了打破这个循环,就在元类的getMeta()方法中直接返回自己终止isa的循环。

再来看看下面的方法:

void imp_ClassToMetaclass(Class pClass){
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    // - (void)sayHi;
    // + (void)sayHaHa;
    IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHi));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHi));

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

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

结果打印:0x100001d10-0x7fff6a7d6580-0x7fff6a7d6580-0x100001d40,四个imp都会打印出来,不太符合直觉,从元类里面获取实例方法的实现也拿到了地址,从类里面获取类方法的实现也能拿到地址,看看class_getMethodImplementation的源码实现:

IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;
    if (!cls  ||  !sel) return nil;
    //查找方法实现
    imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
    //没有找到,则进行消息转发
    if (!imp) {
        return _objc_msgForward;
    }
    return imp;
}

从源码中可以知道由于消息转发机制的处理,即使类或者元类中找不到对应的类方法或者实例方法,最终通过消息转发,也能获取到对应方法的实现!

5、分析下面代码中iskindOfClass 、 isMemberOfClass 的打印结果:
void isKindAndisMember() {
    //-----调用类的 iskindOfClass & isMemberOfClass
    BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
    BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
    BOOL re3 = [(id)[Person class] isKindOfClass:[Person class]];       //
    BOOL re4 = [(id)[Person class] isMemberOfClass:[Person class]];     //
    NSLog(@"\nClass isKindAndisMember\n re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

    //------调用实例的iskindOfClass & isMemberOfClass
    BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       //
    BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     //
    BOOL re7 = [(id)[Person alloc] isKindOfClass:[Person class]];       //
    BOOL re8 = [(id)[Person alloc] isMemberOfClass:[Person class]];     //
    NSLog(@"\nInstance isKindAndisMember\n re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}

看下打印结果:

isKindisMember结果.png
分析前,先看看iskindOfClassisMemberOfClass的源码:
//isKindOfClass类方法
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
//isKindOfClass实例方法
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
//isMemberOfClass类方法
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}
//isMemberOfClass实例方法
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

下面开始分析:

坑点:

上面的分析似乎顺利成章,看不出破绽,都是对着isKindOfClassisMemberOfClass的代码实现一步一步分析的,而且分析的结果也是正确的,但是如果我们用断点调试的话就会发现这里面是有坑的:

断点.png
断点2.png
分别在以上位置下断点,单步运行就会发现不论是实例方法isKindOfClass还是类方法isKindOfClass都没进到对应的断电中,而isMemberOfClass都可以进入对应的方法,这个结果是不是很意外,难道我们上面分析的isKindOfClass流程都是错的吗,系统没有进入对应的isKindOfClass方法,那最终调用的又是那个方法呢?用汇编方式来看看最终调用的方法是什么:
汇编视图debug.png
如上图在Debug菜单选择Always Show Disassembly显示整个方法的汇编代码:
汇编分析.png
从图中标记1的红框可以看到,实际的isKindOfClass方法调用已经变成了objc_opt_isKindOfClass,而标记2的红框显示isMemberOfClass依然是消息转发objc_msgSend方式去调用,所以isMemberOfClass可以进入对应的方法断点,而isKindOfClass已经直接调用objc_opt_isKindOfClass的实现了,这里应该是llvm做的编译优化。在runtime源码中搜索一下objc_opt_isKindOfClass方法看看是否能找到这个方法:
objc_opt_isKindOfClass搜索.png
很容易就搜索到了,声明在objc-internal.h中,实现在NSObject.mm中,点击查看方法实现:
// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
    if (slowpath(!obj)) return NO;
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        for (Class tcls = cls; tcls; tcls = tcls->superclass) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}

可以看到最上面的注释中已经说明了这个就是调用[obj isKindOfClass]方法最终所执行的实现,在这个方法中打断点可以看到,不论是调用实例的isKindOfClass还是类的isKindOfClass,都会进入这个方法,这个方法和之前我们认为要走的+ (BOOL)isKindOfClass:(Class)cls- (BOOL)isKindOfClass:(Class)cls的实现基本是一致的,略有不同,分析一下执行过程:

上一篇 下一篇

猜你喜欢

热点阅读