iOS的两道经典面试题(一)

1,067 阅读9分钟

前言

这篇文章我们看两道典型的题目来分析下对象,类的一致问题以及对象方法,类方法的归属问题。

对象方法以及类方法

做准备

我们在Person写如下代码:

@interface Person : NSObject
- (void)likeFood;
+ (void)homeAddress;
@end

@implementation Person
- (void)likeFood {
    NSLog(@"2222");
}
+ (void)homeAddress {
    NSLog(@"1111");
}
@end

我们在ViewController写如下代码

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    Person *person = [Person alloc];
    Class pClass = object_getClass(person);
    ljObjc_copyMethodList(pClass);
    ljInstanceMethod_classToMetaclass(pClass);
    ljClassMethod_classToMetaclass(pClass);
    ljIMP_classToMetaclass(pClass);
    NSLog(@"%@--->%p", person, &person);
}

void ljObjc_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));
        NSLog(@"1->Method, name: %@", key);
    }
    free(methods);
}

void ljInstanceMethod_classToMetaclass(Class pClass){
    // 获取类名
    const char *className = class_getName(pClass);
    // 获取元类
    Class metaClass = objc_getMetaClass(className);
    Method method1 = class_getInstanceMethod(pClass, @selector(likeFood));
    Method method2 = class_getInstanceMethod(metaClass, @selector(likeFood));
    Method method3 = class_getInstanceMethod(pClass, @selector(homeAddress));
    Method method4 = class_getInstanceMethod(metaClass, @selector(homeAddress));
    NSLog(@"2->%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

void ljClassMethod_classToMetaclass(Class pClass){
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    Method method1 = class_getClassMethod(pClass, @selector(likeFood));
    Method method2 = class_getClassMethod(metaClass, @selector(likeFood));
    Method method3 = class_getClassMethod(pClass, @selector(homeAddress));
    Method method4 = class_getClassMethod(metaClass, @selector(homeAddress));
    NSLog(@"3->%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

void ljIMP_classToMetaclass(Class pClass){
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    IMP imp1 = class_getMethodImplementation(pClass, @selector(likeFood));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(likeFood));
    IMP imp3 = class_getMethodImplementation(pClass, @selector(homeAddress));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(homeAddress));
    NSLog(@"4->%p-%p-%p-%p",imp1,imp2,imp3,imp4);
    NSLog(@"%s",__func__);
}
@end

按上面方法写好后,我们下面解释这几个方法

解释方法

  • NSStringFromSelector :获取方法名
  • class_getInstanceMethod :得到类的实例方法
  • class_getClassMethod : 得到类的类方法
  • class_getMethodImplementation :获取到一个方法的IMP

分析方法

我们想知道都打印的什么,就要对每个方法进行分析,理解方法的实现过程,同样我们使用的源码是:objc4-781

ljObjc_copyMethodList 方法分析

方法如下图所示,我们需要探究红框的方法 对class_copyMethodList进行分析,在源码找到方法实现,发现红框方法。很眼熟,这个方法就是获取方法列表。 由于我们传入的是Person类,故它返回Preson的对象方法,通过我们在Person类的声明,对象方法只有一个likeFood,所有打印的只有一个是likeFood。

ljInstanceMethod_classToMetaclass 方法分析

方法如下图所示,只需要对红框方法:class_getInstanceMethod 进行分析。class_getInstanceMethod:得到类的实例方法 我们在源码找到这个方法的实现,在找的过程中发现,源码的调用方法很多,我就拿一些关键的方法解释一下

getMethodNoSuper_nolock这个方法,我们加入传进的sel是likeFood,cls是Person,这个方法就是查找当前的Person中是否存在likeFood方法,如果存在就返回这个方法,如果不存在,我们就去父类查找

查看getMethodNoSuper_nolock方法内部

auto const methods = cls->data()->methods();就是获取cls的方法列表,search_method_list_inline通过传入的方法列表跟sle名去查找方法,找到了就返回,招不到就返回nil

方法分析完了,我们看打印什么

  • method1是在Person类中查找likeFood方法,因为是对象方法,所以在methods中是存在的,故有值
  • method2是在Person的元类中找likeFood方法,元类的方法列表里不存在对象方法,它的父类依然没有,故返回nil,不存在。
  • method3是在Person类中查找homeAddress方法,因为是类方法,所以不存在,它的父类同样不存在,故返回nil,不存在
  • method4是在Person的元类中找homeAddress方法,元类的方法列表里存在类方法,故返回有值,存在。

ljClassMethod_classToMetaclass 方法分析

方法如下,只需要对红框方法:class_getClassMethod进行分析 我们查看下class_getClassMethod方法内部 发现也是调用的class_getInstanceMethod只是参数不同,我们进去看下cls->getMeta()方法

isMetaClass意思是是否是元类,这个方法意思就是如果是cls的元类就返回,如果不是就返回cls的isa指针,根据之前文章知道cls的指针指向的是cls的元类。所以这个方法就是在元类中查找是否有该方法

方法分析完了,我们看打印什么

  • method1是传进Person类,会找到Person的元类是否存在likeFood方法,因为likeFood是对象方法,故不存在,返回nil
  • method2是传进Person元类类,直接找Person的元类是否存在likeFood方法,因为likeFood是对象方法,故不存在,返回nil
  • method3是传进Person类,会找到Person的元类是否存在homeAddress方法,因为homeAddress是类方法,故存在。
  • method4是传进Person元类类,直接找Person的元类是否存在homeAddress方法,因为homeAddress是类方法,故存在。

ljIMP_classToMetaclass 方法分析

方法如下,只需要对class_getMethodImplementation方法进行分析 我们进入class_getMethodImplementation方法看看

方法就是现在cls的方法列表中找sle方法,如果能找到就直接返回,如果找不到就走_objc_msgForward方法,开始进行消息转发

方法分析完了,我们看看打印什么

  • imp1是传进Person类,在这个类中找likeFood,能找到,所以返回imp
  • imp2是传进去的Person的元类,在元类中找likeFood,因为likeFood是对象方法,所以找不到,走消息转发,最后返回地址
  • imp3是传进Person类,在这个类中找homeAddress,因为homeAddress是类方法,所以找不到,走消息转发,最后返回地址
  • imp4是传进去的Person的元类,在元类中找homeAddress,因为homeAddress是类方法方法,所以能找到,所以返回imp。

验证

上面分析完了,我们运行验证一下 通过打印结果,验证我们上面说的是正确的,注意画横线的部分,上面说了这地方是找不到方法的,会走消息转发,但是他们地址确实一致的。我们单独看下

上面发现打印的都是libobjc.A.dylib`_objc_msgForward,上面我们看源码知道,找不到方法会走消息转发,那这个地址是不是某个方法呢?。这就牵扯到runTime的内容,后续我们补充

对象比较

我们在ViewController中写如下代码

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    // insert code here...
    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(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
    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(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}

上面写好后,我们思考一下会打印什么,再这之前我们解释上面出现的方法(这也是经常出现的面试题)

方法解释

  • isKindOfClass:这个方法用来判断一个对象是否是指定类或者某个从该类继承类的实例对象
  • isMemberOfClass:这个方法用来判断一个对象是否是指定类的实例对象

分析方法

为了查看内部实现,我们依然会看源码。我们用的源码依然是:objc4-781

对isKindOfClass方法分析

我们看下isKindOfClass内部实现 上面是类方法跟对象方法都会走objc_opt_isKindOfClass,不走isKindOfClass,运行验证的。 obj是调用类,Class cls = obj->getIsa();是拿调用者的isa,如果是类就是拿元类,如果是对象就是当前的对象类。for (Class tcls = cls; tcls; tcls = tcls->superclass)是for循环,让tcls等于上面获取的cls,if (tcls == otherClass) return YES;如果跟传进来的otherClass相等就返回YES,如果不是就循环,让tcls等于它的父类,一直循环跳出来前还不等就返回NO。

我们看完objc_opt_isKindOfClass,下面看下isMemberOfClass

对isMemberOfClass方法分析

我们看下isMemberOfClass的内部实现 上面是类方法,下面是对象方法,

我们先说类方法 self->ISA() == cls是指如果调用者的isa指针指向类(元类)如果等于cls就返回YES,否则就返回NO。

再说对象方法 [self class] == cls是指如果调用对象的类等于cls就返回YES,否则就返回NO。

方法说完了,我们就来分析上面的问题。

  • re1:调用的是isKindOfClass的类方法,调用者为NSObject类,cls为NSObject类,上面讲了先获取NSObject类的元类,然后会到一个for循环,此元类不等于NSObject类,循环到NSObject的元类的父类,NSObject的元类的父类是NSObject类,等于NSObject类,所以返回YES
  • re2:调用的是isMemberOfClass的类方法,调用者是NSObject类,cls为NSObject类,上面讲的这里判断NSObject的isa指向元类,元类跟NSObject类不相等,所以返回NO
  • re3:调用的是isKindOfClass的类方法,调用者为Person类,cls为Person类,上面讲了先获取Person类的元类就是Person的元类Person,然后会到一个for循环,,元类不等于Person类,循环到Person的元类的父类,就是NSObject元类,NSObject元类不等于等于Person类,继续循环,NSObject元类的父类就是NSObject类,不等于Person类,继续循环,NSObject类的父类就是nil,不等于Person类,跳出循环,所以返回NO
  • re4:调用的是isMemberOfClass的类方法,调用者是Person类,cls为Person类,上面讲的这里判断Person的isa指向元类,元类跟Person类不相等,所以返回NO
  • re5:调用的是isKindOfClass的对象方法,调用者为NSObject对象,cls为NSObject类,下面会到一个for循环,NSObject对象的类是NSObject,此类等于NSObject类,所以返回YES
  • re6:调用的是isMemberOfClass的对象方法,调用者是NSObject对象,cls为NSObject类,上面讲的这里判断NSObject对象的class就是NSObject类,此类跟NSObject类相等,所以返回YES
  • re7:调用的是isKindOfClass的对象方法,调用者为Person对象,cls为Person类,下面会到一个for循环,NSObject对象的Class是Person,此类等于Person类,所以返回YES
  • re8:调用的是isMemberOfClass的对象方法,调用者是Person对象,cls为Person类,上面讲的这里判断Person对象的class就是Person类,此类跟Person类相等,所以返回YES。 综合上面打印应该是:1000 1111,下面验证: 验证和我们答案一直。

最后

这两个问题都牵扯到isa指针的走位,在之前的文章OC底层原理之-类的结构有详细介绍,不理解的可以去看看。今天写到这,哪里有问题还希望指正出来。