美文网首页
IOS面试题 --- 类相关

IOS面试题 --- 类相关

作者: ShawnAlex | 来源:发表于2021-05-12 15:30 被阅读0次

问题1:

简单解释 OC中 "成员变量""属性" ?

答案:

这个可参考之前写的 IOS底层(十): 类相关: 成员变量与属性


问题2:

简单解释 OC中 atomic / nonatomic, strong / copy / weak / assign?

答案:

这个可参考之前写的, LLVM部分可做了解
IOS底层(十一): 属性修饰分析


问题3:

定义一个 TestObj 类, 里面有一个实例方法, 一个类方法

.h 文件

#import <Foundation/Foundation.h>

@interface TestObj : NSObject

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

@end

.m 文件

#import "TestObj.h"

@implementation TestObj

// 实例方法
- (void)sayHello{
    NSLog(@"Say : Hello!!!");
}

// 类方法
+ (void)sayHappy{
    NSLog(@"Say : Happy!!!");
}

@end

问: 这个几个打印结果是什么样?(有返回1, 无返回0 即可)


TestObj *person = [TestObj alloc];
Class pClass     = object_getClass(person);
classMethod_classToMetaclass(pClass);

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(@"%s - %p-%p-%p-%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));
    
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    NSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
    
}

答案:

打印一下:  
instanceMethod_classToMetaclass - 0x1000081b0-0x0-0x0-0x100008148
classMethod_classToMetaclass-0x0-0x0-0x100008148-0x100008148
//后面指针可不同

所以第一个结果为 1 0 0 1
所以第二个结果为 0 0 1 1

解释:

建议先看下: IOS底层(九): 类相关: 类结构分析

首先要清楚实例方法是存在对应bits中, 而类方法存在元类bits中

那么instanceMethod_classToMetaclass 返回

  • 第一个, 在当前类中找实例方法 sayHello, 没问题有返回值
  • 第二个, 在元类中找实例方法 sayHello, 没有0x0
  • 第三个, 在当前类中找实例方法 sayHappy, 没有0x0
  • 第四个, 在元类中找实例方法 sayHappy, 这个可能有点疑问, 看下源码底层, 其实底层是_class_getMethod(cls, sel);, 没问题有返回值
Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.

#warning fixme build and search caches
        
    // Search method lists, try method resolver, etc.
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}

那么classMethod_classToMetaclass 返回

  • 第一个, 在当前类中找类方法sayHello, 没有0x0
  • 第二个, 在元类中找类方法sayHello, 没有0x0
  • 第三个, 在当前类中找 类方法sayHappy, 没问题有返回值
  • 第四个, 在元类中找实例方法sayHappy, 这个可能有点疑问, 看下源码, 底层其实是class_getInstanceMethod, 并且元类的话这里cls->getMeta()返回元类本身, 没问题有返回值
Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}

    Class getMeta() {
        if (isMetaClassMaybeUnrealized()) return (Class)this;
        else return this->ISA();
    }


问题4:

定义一个 TestObj 类 (有返回1, 无返回0 即可)

        BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];      
        BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];    
        BOOL re3 = [(id)[TestObj class] isKindOfClass:[TestObj class]];      
        BOOL re4 = [(id)[TestObj class] isMemberOfClass:[TestObj 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)[TestObj alloc] isKindOfClass:[TestObj class]];
        BOOL re8 = [(id)[TestObj alloc] isMemberOfClass:[TestObj class]];
        NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);

答案:

打印一下:  
 re1 :1
 re2 :0
 re3 :0
 re4 :0

 re5 :1
 re6 :1
 re7 :1
 re8 :1

解题思路:

本题重点考察对 , 元类中isa以及集成指向的理解, 同时要掌握 isKindOfClass 以及 isMemberOfClass, 先看下方法源码以及走势图, 留意这里找的是 +是类方法, -是实例方法

// 类方法
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;   // 判断当前类的元类是否与传入类相等
}

// 实例方法
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;   // 判断当前类是否与传入类相等
}
// 类方法 
+ (BOOL)isKindOfClass:(Class)cls {
    // 依次循环 判断 superclass
    // 获取类的元类 vs 传入类
    // 根元类 vs 传入类
    // 根类 vs 传入类
    // 如果相等返回 true, 不相等返回false
    for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

// 实例方法 
- (BOOL)isKindOfClass:(Class)cls {

    // 依次循环 判断 superclass
    // 当前类 vs 传入类
    // 父类 vs 传入类
    // 根类 vs 传入类
    // 根类 vs 传入类
    // 如果相等返回 true, 不相等返回false

    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

源码可以看出

isMemberOfClass

  • 实例方法: 对象的类传入类 进行对比

  • 类方法: 元类传入类 进行对比

isKindOfClass:

  • 实例方法: 对象的类父类根类nil传入类 进行对比

  • 类方法: 元类根元类根类nil (依次找父类) 与 传入类 进行对比

但实际上 isKindOfClass运行时我们会发现, 它底层走了objc_opt_isKindOfClass这个方法, 那我们看一下objc_opt_isKindOfClass

objc_opt_isKindOfClass
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
    if (slowpath(!obj)) return NO;
    // 获取isa,
    // 如果obj 是对象,则isa是类,
    // 如果obj是类,则isa是元类
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        // 如果是类:  `对象的类` → `父类` → `根类` → `nil` 与 `传入类` 进行对比
        // 如果是元类:  `元类` → `根元类` → `根类` → `nil`  (依次找父类) 与 `传入类` 进行对比
        for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
isa以及集成关系走势图

// 类方法

  • re1 :1; 使用 + isKindOfClass

    • NSObject 根元类 VS NSObject(传入类) --- 不相等
    • NSObject 根类 VS NSObject(传入类) --- 相等
  • re2 :0; 使用 +isMemberOfClass

    • NSObject的元类即根元类 VS NSObject(传入类) --- 不相等
  • re3 :0; 使用 + isKindOfClass

    • TestObj 元类 VS TestObj(传入类) --- 不相等
    • TestObj 元类的父类根元类 VS TestObj(传入类) --- 不相等
    • TestObj 根元类父类根类 VS TestObj(传入类) --- 不相等
    • TestObj 根类的父类nil VS TestObj(传入类) --- 不相等
  • re4 :0; 使用 +isMemberOfClass

    • TestObj的元类 VS TestObj (传入类) --- 不相等

// 实例方法

  • re5 :1; 使用 - isKindOfClass

    • NSObject对象的类即: NSObject VS NSObject (传入类) --- 相等
  • re6 :1; 使用 -isMemberOfClass

    • NSObject对象的类即: TestObj VS NSObject (传入类) --- 相等
  • re7 :1 使用 - isKindOfClass

    • TestObj对象的类即: TestObj VS TestObj (传入类) --- 相等
  • re8 :1; 使用 -isMemberOfClass

    • TestObj对象的类即: TestObj VS TestObj (传入类) --- 相等

问题5:

类对象在内存中存几份(或者问: 类存在几份) ?

答案:

类信息在内存中只存在一份, 所以类对象只有一份


问题6:

objc_object 与 对象的关系 ?

答案:

继承关系

所有对象都以objc_object为模板继承过来的, 但真正底层是一个objc_object(C/C++)的结构体类型


问题7第一问: main中代码运行是否会报错

// SAStudent.h
#import "SAPerson.h"
@interface SAStudent : SAPerson
+ (void)sayHello;
@end

// SAStudent.m
#import "SAStudent.h"
@implementation SAStudent
+ (void)sayHello {
    NSLog(@"SayHello : %s", __func__);
}
@end

// SAPerson.h
#import <Foundation/Foundation.h>
@interface SAPerson : NSObject
+ (void)sayHappy;
@end

// SAPerson.m
#import "SAPerson.h"
@implementation SAPerson
+ (void)sayHappy {
    NSLog(@"SayHappy : %s", __func__);
}
@end


#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "SAPerson.h"
#import "SAStudent.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
               
        SAStudent *p = [SAStudent alloc];
        [SAStudent sayHello];
        [SAStudent sayHappy];
        [SAStudent performSelector:@selector(sayEasy)];   

    }
    return 0;
}

答案:

会报错

问题7-1

本题其实考察isa走位图(图片见问题4),
给定类方法, 类方法存在元类里面, 元类里面其实是以实例方法存储, 查找循序 子元类父元类根元类根类nil

  • 类方法sayHello: 子元类 SAStudent 存在, 正常打印

  • 类方法sayHappy: 子元类 SAStudent 不存在, 去父元类查找存在, 正常打印

  • 类方法sayEasy: 子元类 SAStudent 不存在, 去父元类, 根元类, 根类查找不存在 直接报错返回

    其中[SAStudent performSelector:@selector(sayEasy)] = [SAStudent sayEasy]

问题7第二问: 第一问基础上多加一个分类 NSObject+NSObject_Cate.h, 分类里面有一个SayEasy方法, 内容如下是否会报错

#import <Foundation/Foundation.h>
@interface NSObject (NSObject_Cate)
+ (void)sayEasy;
@end


#import "NSObject+NSObject_Cate.h"
@implementation NSObject (NSObject_Cate)
+ (void)sayEasy {
    NSLog(@"SayEasy : %s", __func__);
}
@end

答案:

不会报错

问题7-2

其实相当于问题一, 只是在根类NSObject里面加个一个方法, 当找到根类时候发现有, 直接返回


问题8 :

如下例子, 请问回答打印结果情况(回答对应相等/不等即可)并解释原因

    TestObj *obj1 = [TestObj alloc];
    TestObj *obj2 = [obj1 init];
    TestObj *obj3 = [obj1 init];
       
    NSLog(@"%@ - %p - %p", obj1, obj1, &obj1);
    NSLog(@"%@ - %p - %p", obj2, obj2, &obj2);
    NSLog(@"%@ - %p - %p", obj3, obj3, &obj3);

答案:

首先了解下, 三个打印依次是打印 对象内容, 对象指针指向的内存地址, 指针地址
%@: 打印的是对象的内容
%p → &p1: 打印的是对象的指针地址
%p → p1: 打印的是指针地址

那么

  • 第一列: obj1, ob2, obj3 相等, 打印的是对象内容都是TestObj开辟的内存空间

  • 第二列: obj1, obj2, obj3 相等, 留意下init是不会对指针进行操作的, 而%p打印的是对象指针, obj1、obj2、 obj3, 三个都是指向同一片内存区域( [TestObj alloc]开辟的), 所以不变

  • 第三列: &obj1, &ob2, &obj3 不相等, 打印的是指针地址, obj1、obj2、 obj3三个不同指针, 地址不同

打印结果:

问题8-答案 指向图

拓展个知识点开头0x7栈区, 开头0x6堆区。指针在64位系统下为8个字节故依次相差8(其实也做了连续开辟, 在栈里面是连续指针, 所以依次相差8)。3 > 2 > 1是因为栈是从高地址往低地址存的。


(持续更新....)

相关文章

网友评论

      本文标题:IOS面试题 --- 类相关

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