关于类的经典面试题

通过之前几篇文章的学习,我们已经对做了很多的探索了,现在我们看两道面试题,巩固一下。

【面试题】类方法归属分析

我们知道实例方法 存储在中,类方法存储在元类中,我们下面通过一些方法来验证这个结果.

准备工作
创建ZGPerson对象,分别有一个类方法实例方法

@interface ZGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;
@end

@implementation ZGPerson

- (void)sayHello{
    NSLog(@"ZGPerson say : Hello!!!");
}




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

@end

下面我们通过几个方法分析一下方法的归属问题

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

void instanceMethod_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));
    
    NSLog(@"方法名__func__:%s\n method1地址:%p\n method2地址:%p\n method3地址:%p\n method4地址:%p",__func__,method1,method2,method3,method4);
}

void classMethod_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));
    
    NSLog(@"方法名__func__:%s\n method1地址:%p\n method2地址:%p\n method3地址:%p\n method4地址:%p",__func__,method1,method2,method3,method4);
}

void IMP_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(@"方法名__func__:%s\n imp1:%p\n imp2:%p\n imp3:%p\n imp4:%p",__func__,imp1,imp2,imp3,imp4);
}

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

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

        instanceMethod_classToMetaclass(pClass);
        classMethod_classToMetaclass(pClass);
        iMP_classToMetaclass(pClass);
        NSLog(@"Hello, World!");
    }
    return 0;
}

思考一下打印结果分别是什么?


结果如下:


方法归属打印

我们挨个分析一下其中的打印:

objc_copyMethodList分析

我们点进去看一下方法的解释

 * @param cls The class you want to inspect.
 * @param outCount On return, contains the length of the returned array. 
 *  If outCount is NULL, the length is not returned.
 * 
 * @return An array of pointers of type Method describing the instance methods 
 *  implemented by the class—any instance methods implemented by superclasses are not included. 
 *  The array contains *outCount pointers followed by a NULL terminator. You must free the array with free().
 * 
 *  If cls implements no instance methods, or cls is Nil, returns NULL and *outCount is 0.
 * 
 * @note To get the class methods of a class, use \c class_copyMethodList(object_getClass(cls), &count).
 * @note To get the implementations of methods that may be implemented by superclasses, 
 *  use \c class_getInstanceMethod or \c class_getClassMethod.

大意是说:获取实现的实例方法,如果cls没有实现实例方法,或cls为Nil,则返回NULLoutCount为0。,而且告诉我们如果要获得一个类的类方法,可以使用class_copyMethodList(object_getClass(cls), &count)

let me try...

class_copyMethodList(pClass, &count)改为class_copyMethodList(object_getClass(pClass), &count)

打印结果如下:


打印结果

果然打印出了我们的类方法pClassZGPerson类,而object_getClass(pClass)ZGPerson的元类,这也正验证了我们的结论:实例方法 存储在中,类方法存储在元类

instanceMethod_classToMetaclass分析

 * @param cls The class you want to inspect.
 * @param name The selector of the method you want to retrieve.
 * 
 * @return The method that corresponds to the implementation of the selector specified by 
 *  \e name for the class specified by \e cls, or \c NULL if the specified class or its 
 *  superclasses do not contain an instance method with the specified selector.
 *
 * @note This function searches superclasses for implementations, whereas \c class_copyMethodList does not.

大意是这个方法主要是获取类的实例方法,如果指定的类或其父类不包含带有指定选择器的实例方法,则为NULL

instanceMethod_classToMetaclass打印

打印结果也证明了:实例方法 存储在中不在元类中,类方法存储在元类,不在本类

0x0表示无地址,即不存在此方法
有地址存在则存在此方法

classMethod_classToMetaclass分析

 * @param cls A pointer to a class definition. Pass the class that contains the method you want to retrieve.
 * @param name A pointer of type \c SEL. Pass the selector of the method you want to retrieve.
 * 
 * @return A pointer to the \c Method data structure that corresponds to the implementation of the 
 *  selector specified by aSelector for the class specified by aClass, or NULL if the specified 
 *  class or its superclasses do not contain an instance method with the specified selector.
 *
 * @note Note that this function searches superclasses for implementations, 
 *  whereas \c class_copyMethodList does not.

大意是该方法主要是用于获取类方法,如果在传入的类或者类的父类中没有找到指定的类方法,则返回NULL

打印结果中类和元类中都没有发现的sayHellod实例方法,而却都发现了sayHappy类方法,这是为什么呢?

我们进入的class_getClassMethod源码看一下

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 (isMetaClass()) return (Class)this;
     else return this->ISA();
 }

class_getClassMethod的实现是获取类的类方法,其本质就是获取元类的实例方法,最终还是会走到class_getInstanceMethod,但是在这里需要注意的一点是:在getMeta源码中,如果判断出cls元类,那么就不会再继续往下递归查找,会直接返回this,其目的是为了防止元类的无限递归查找

method3method4都打印了地址,是因为如果找不到都会递归到元类,而元类中有sayHappy的实现地址。

IMP_classToMetaclass分析

 * @param cls The class you want to inspect.
 * @param name A selector.
 * 
 * @return The function pointer that would be called if \c [object name] were called
 *  with an instance of the class, or \c NULL if \e cls is \c Nil.
 *
 * @note \c class_getMethodImplementation may be faster than \c method_getImplementation(class_getInstanceMethod(cls, name)).
 * @note The function pointer returned may be a function internal to the runtime instead of
 *  an actual method implementation. For example, if instances of the class do not respond to
 *  the selector, the function pointer returned will be part of the runtime's message forwarding machinery.

大意是说:该函数在向类实例发送消息时会被调用,并返回一个指向方法实现函数的指针。这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函数指针可能是一个指向runtime内部的函数,而不一定是方法的实际实现。如果类实例无法响应selector,则返回的函数指针将是运行时消息转发机制的一部分,即进行转发。

源码验证:

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;
}

打印结果分析:method1method4分别是类中找到了sayHello实例方法的实现和元类中找到了sayHappy类方法的实现,故打印的是imp函数指针的地址,而method2method3则是没有找到方法的实现,所以进行了消息转发.

总结

  • class_getInstanceMethod:获取实例方法,如果指定的类或其父类不包含带有指定选择器的实例方法,则为NULL

  • class_getClassMethod:获取类方法,如果指定的类或其父类不包含具有指定选择器的类方法,则为NULL。

  • class_getMethodImplementation:获取方法的具体实现,如果未查找到,则进行消息转发

【面试题】iskindOfClass & isMemberOfClass 的理解

请思考以下内容的打印结果:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
        BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
        BOOL re3 = [(id)[ZGPerson class] isKindOfClass:[ZGPerson class]];       //
        BOOL re4 = [(id)[ZGPerson class] isMemberOfClass:[ZGPerson class]];     //
        NSLog(@" \n 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)[ZGPerson alloc] isKindOfClass:[ZGPerson class]];       //
        BOOL re8 = [(id)[ZGPerson alloc] isMemberOfClass:[ZGPerson class]];     //
        NSLog(@" \n re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
    }
    
    return 0;
}

打印结果如下:


打印结果

那么,为什么会出现这样的结果呢?我们只能进入isKindOfClassisMemberOfClass实例方法类方法源码中寻找答案了。

(+/-)isKindOfClass源码:

//第一次比较是 获取类的元类 与 传入类对比,再次之后的对比是获取上次结果的父类 与 传入 类进行对比
+ (BOOL)isKindOfClass:(Class)cls {
    // 获取类的元类 vs 传入类
    // 根元类 vs 传入类
    // 根类 vs 传入类
    // 举例:ZGPerson vs 元类 (根元类) (NSObject)
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
//第一次是获取对象本类 与 传入类对比,如果不相等,后续对比是继续获取上次 类的父类 与传入类进行对比
- (BOOL)isKindOfClass:(Class)cls {

    // 获取本类 vs 传入的类 
    // 父类 vs 传入的类
    // 根类 vs 传入的类
    // nil vs 传入的类
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

注意:在这里发现了一个坑点

设置

如果你的源码配置选的是10.15的话是不会执行上面的代码的(10.14会执行上面的代码),反而会执行一个叫objc_opt_isKindOfClass的方法。

objc_opt_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);
}

可以看到这个的代码逻辑基本和isKindOfClass的代码一样,只是做了一些配置和编译器优化的判断,运行结果都是一样的。

(+/-)isMemberOfClass源码:

+ (BOOL)isMemberOfClass:(Class)cls {
    //获取类的元类,与 传入类对比
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    //获取对象的本类,与 传入类对比
    return [self class] == cls;
}

总结
isKindOfClass:

  • 类方法元类 --> 根元类 --> 根类 --> nil传入类的对比
  • 实例方法对象的类 --> 父类 --> 根类 --> nil传入类的对比

isMemberOfClass:

  • 类方法类的元类传入类对比
  • 实例方法对象的父类传入类 对比

有了结论之后下面对打印结果逐个分析:

对于BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];

分析:
①:Class tcls = self->ISA(),NSObject类的isa指向NSObject根元类根元类 != NSObject,循环继续
②:tcls = tcls->superclass,根元类的父类NSObject, 此时 NSObject == NSObject ,return YES;

对于BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];

分析:
NSObject类的isa指向NSObject根元类根元类 != NSObject,return NO;

对于BOOL re3 = [(id)[ZGPerson class] isKindOfClass:[ZGPerson class]];

分析:
①:ZGPerson类的isa指向ZGPerson元类ZGPerson 元类 != ZGPerson,循环继续;
②: 元类ZGPerson的父类即根元类根元类 != ZGPerson,循环继续;
③: ZGPerson 根元类指向根类NSObjectNSObject != ZGPerson,循环继续;
④:NSObject的父类为nilnil != ZGPerson,循环结束,return NO;

对于BOOL re4 = [(id)[ZGPerson class] isMemberOfClass:[ZGPerson class]];;

分析:
ZGPersonisa指向ZGPerson根元类ZGPerson根元类 != ZGPerson,return NO;

对于BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];

分析:
NSObject类本类与NSObject相比,NSObject == NSObject,return YES;

对于BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];

分析:
NSObject类本类与NSObject相比,NSObject == NSObject,return YES;

对于BOOL re7 = [(id)[ZGPerson alloc] isKindOfClass:[ZGPerson class]];

分析:
ZGPerson类本类与ZGPerson相比,ZGPerson == ZGPerson,return YES;

对于BOOL re8 = [(id)[ZGPerson alloc] isMemberOfClass:[ZGPerson class]];

分析:
ZGPerson类本类与ZGPerson相比,ZGPerson == ZGPerson,return YES;

以上便是今天的全部内容,如有不对之处,还望指正!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容