美文网首页iOS面试资料搜集iOS基础篇
深入浅出 Runtime(六):相关面试题

深入浅出 Runtime(六):相关面试题

作者: 师大小海腾 | 来源:发表于2020-02-26 01:19 被阅读0次

    Runtime 系列文章

    深入浅出 Runtime(一):初识
    深入浅出 Runtime(二):数据结构
    深入浅出 Runtime(三):消息机制
    深入浅出 Runtime(四):super 的本质
    深入浅出 Runtime(五):具体应用
    深入浅出 Runtime(六):相关面试题

    网络配图

    Q:你了解 isa 指针吗?

    • isa指针用来维护对象和类之间的关系,并确保对象和类能够通过isa指针找到对应的方法、实例变量、属性、协议等;
    • 在 arm64 架构之前,isa就是一个普通的指针,直接指向objc_class,存储着ClassMeta-Class对象的内存地址。instance对象的isa指向class对象,class对象的isa指向meta-class对象;
    • 从 arm64 架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息。将 64 位的内存数据分开来存储着很多的东西,其中的 33 位才是拿来存储classmeta-class对象的内存地址信息。要通过位运算将isa的值& ISA_MASK掩码,才能得到classmeta-class对象的内存地址;
    • isa指针存储的信息;
    • isa指针的指向。
      传送门:深入浅出 Runtime(二):数据结构

    Q:类对象与元类对象的区别和联系。

    • classmeta-class底层结构都是objc_class结构体,objc_class继承自objc_object,所以它也有isa指针,它也是对象;
    • class中存储着实例方法、成员变量、属性、协议等信息,
      meta-class中存储着类方法等信息;
    • isa指针和superclass指针的指向;
    • 基类的meta-classsuperclass指向基类的class
      决定了一个性质:当我们调用一个类方法,会通过classisa指针找到meta-class,在meta-class中查找有无该类方法,如果没有,再通过meta-classsuperclass指针逐级查找父meta-class,一直找到基类的meta-class如果还没找到该类方法的话,就会去找基类的class中同名的实例方法的实现。
      isa 与 superclass 指针指向

    Q:为什么要设计 meta-class ?

    目的是将实例和类的相关方法列表以及构建信息区分开来,方便各司其职,符合单一职责设计原则。

    Q:Runtime 的消息机制,objc_msgSend 方法调用流程。

    传送门:深入浅出 Runtime(三):消息机制
    OC中的方法调用,其实都是转换为objc_msgSend()函数的调用(不包括[super message])。objc_msgSend()的执行流程可以分为 3 大阶段:消息发送、动态方法解析、消息转发。

    Q:调用以下 init 方法的打印结果是什么?(super)

    @interface HTPerson : NSObject
    @end
    
    @interface HTStudent : HTPerson
    @end
    
    @implementation HTStudent
    - (instancetype)init
    {
        if (self = [super init]) {
            
            NSLog(@"[self class] = %@",[self class]);
            NSLog(@"[super class] = %@",[super class]);
            NSLog(@"[self superclass] = %@",[self superclass]);
            NSLog(@"[super superclass] = %@",[super superclass]);
            
        }
        return self;
    }
    @end
    

    [self class] = HTStudent
    [super class] = HTStudent
    [self superclass] = HTPerson
    [super superclass] = HTPerson

    classsuperclass方法的实现在 NSObject 类中,可以看到它们的返回值取决于receiver

    + (Class)class {
        return self;
    }
    - (Class)class {
        return object_getClass(self);
    }
    + (Class)superclass {
        return self->superclass;
    }
    - (Class)superclass {
        return [self class]->superclass;
    }
    

    [self class]是从receiverClass开始查找方法的实现,如果没有重写的情况,则会一直找到基类 NSObject,然后调用。
    [super class]是从receiverClass->superclass开始查找方法的实现,如果没有重写的情况,则会一直找到基类 NSObject,然后调用。
    由于receiver相同,所以它们的返回值是一样的。

    Q:如何防止“调用无法识别的方法导致应用程序崩溃”?

    实现doseNotRecognizeSelector方法。

    Q:@synthesize 和 @dynamic

    • @synthesize :为属性生成下划线成员变量,并且自动生成settergetter方法的实现。以前 Xcode 还没这么智能的时候就要这么做。而现在默认我们写的属性,会自动进行@synthesize
      有时候我们不希望它自动生成,而是在程序运行过程中再去决定该方法的实现,就可以使用@dynamic
    • @dynamic :是告诉编译器不用自动生成settergetter的实现,不用自动生成成员变量,等到运行时再添加方法实现,但是它不会影响settergetter方法的声明。
    • 动态运行时语言与编译时语言的区别:动态运行时语言将函数决议推迟到运行时,编译时语言在编译器进行函数决议。OC 是动态运行时语言。

    Q:能否向编译后的类增加实例变量?能否向运行时动态创建的类增加实例变量?

    • 不能向编译后的类增加实例变量。类的内存布局在编译时就已经确定,类的实例变量列表存储在class_ro_t结构体里,编译时就确定了内存大小无法修改,所以不能向编译后的类增加实例变量。
    • 能向运行时动态创建的类增加实例变量。运行时动态创建的类只是通过alloc分配了类的内存空间,没有对类进行内存布局,内存布局是在类初始化过程中完成的,所以能向运行时动态创建的类增加实例变量。
      需要注意的是,要在调用注册类的方法之前去完成实例变量的添加,因为注册类的时候,类的结构就生成了。说白了就是class_addIvar()函数不能给已经存在的类动态添加成员变量。
        // 动态创建一对类和元类(参数:父类,类名,额外的内存空间)
        Class newClass = objc_allocateClassPair([NSObject class], "Person", 0);
        // 动态添加成员变量
        class_addIvar(newClass, "_age", 4, 1, @encode(int));
        class_addIvar(newClass, "_name", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
        // 注册一对类和元类(要在类注册之前添加成员变量)
        objc_registerClassPair(newClass);
        // 创建实例
        id person = [[newClass alloc] init];
        [person setValue:@"Lucy" forKey:@"name"];
        [person setValue:@"20" forKey:@"age"];  
        NSLog(@"name:%@, age:%@", [person valueForKey:@"name"], [person valueForKey:@"age"]);    
        // 当类和它的子类的实例存在时,不能调用 objc_disposeClassPair(),否则会 Crash:Attempt to use unknown class 0x1005af5c0.
        person = nil;    
        // 销毁一对类和元类
        objc_disposeClassPair(newClass);
    
        // name:Lucy, age:20
    

    Q:你是否有使用过 performSelector: 方法?

    使用场景:一个类在编译时没有这个方法,在运行的时候才产生了这个方法,这个时候要调用这个方法就要用到performSelector:方法。
    关于动态添加方法的实现可以查看:传送门:深入浅出 Runtime(三):消息机制

    Q:以下打印结果是什么?(isKindOfClass & isMemberOfClass)

    @interface Person : NSObject
    @end
    ......
        BOOL res1 = [[NSObject class] isKindOfClass:[NSObject class]];
        BOOL res2 = [[NSObject class] isMemberOfClass:[NSObject class]];
        BOOL res3 = [[Person class] isKindOfClass:[Person class]];
        BOOL res4 = [[Person class] isMemberOfClass:[Person class]];
    
        NSLog(@"%d,%d,%d,%d", res1, res2, res3, res4);
    ......
    

    打印结果:1,0,0,0
    以下是isMemberOfClassisKindOfClass方法以及object_getClass()函数的实现。

    + (BOOL)isMemberOfClass:(Class)cls {
        return object_getClass((id)self) == cls;
    }
    
    - (BOOL)isMemberOfClass:(Class)cls {
        return [self class] == cls;
    }
    
    + (BOOL)isKindOfClass:(Class)cls {
        for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    
    - (BOOL)isKindOfClass:(Class)cls {
        for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    
    Class object_getClass(id obj)
    {
        if (obj) return obj->getIsa();
        else return Nil;
    }
    
    • isMemberOfClass方法是判断当前instance/class对象的isa指向是不是class/meta-class对象类型;
    • isKindOfClass方法是判断当前instance/class对象的isa指向是不是class/meta-class对象或者它的子类类型。

    显然isKindOfClass的范围更大。如果方法调用着是instance对象,传参就应该是class对象。如果方法调用着是class对象,传参就应该是meta-class对象。所以res2-res4都为 0。那为什么res1为 1呢?
    因为 NSObject 的class的对象的isa指向它的meta-class对象,而它的meta-classsuperclass指向它的class对象,所以它满足isKindOfClass方法的判断条件。
    总之,[instance/class isKindOfClass:[NSObject class]];都返回 1。

    相关文章

      网友评论

        本文标题:深入浅出 Runtime(六):相关面试题

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