美文网首页
iOS-底层原理(17)-runtime之面试题(class+s

iOS-底层原理(17)-runtime之面试题(class+s

作者: 路飞_Luck | 来源:发表于2018-09-09 22:43 被阅读74次
    面试题
    一 下面代码的执行结果
    • Student继承自Person类,下面代码执行结果
    #import "Student.h"
    
    @implementation Student
    - (instancetype)init {
        if (self = [super init]) {
            NSLog(@"[self class] = %@", [self class]); // Student
            NSLog(@"[self superclass] = %@", [self superclass]); // Person
            
            NSLog(@"--------------------------------");
            
            // objc_msgSendSuper({self, [Merson class]}, @selector(class));
            NSLog(@"[super class] = %@", [super class]); // Student
            NSLog(@"[super superclass] = %@", [super superclass]); // Person
        }
        return self;
    }
    @end
    

    运行结果

    image.png

    结论如下

    • [super message]的底层实现
      • 1.消息接收者仍然是子类对象
      • 2.从父类开始查找方法的实现
      • 3.super调用的receiver仍然是MJStudent对象
    struct objc_super {
        __unsafe_unretained _Nonnull id receiver; // 消息接收者
        __unsafe_unretained _Nonnull Class super_class; // 消息接收者的父类
    };
    
    • NSObject类实现猜测如下
    @implementation NSObject
    - (Class)class
    {
        return object_getClass(self);
    }
    
    - (Class)superclass
    {
        return class_getSuperclass(object_getClass(self));
    }
    @end
    

    所以[super class]的打印结果是Student,因为调用class方法最终都是调用NSObject的Class方法,又因为调用方法最终是走objc_msgSend方法,该方法取决于谁是接受者,通过底层代码可知,[super message]的消息接受者人仍然是子类对象,所以打印Student。

    二 利用消息转发做什么事情
    • 可以拦截方法找不到导致的奔溃

    • Person类文件

    @interface Person : NSObject
    - (void)run;
    - (void)test1;
    - (void)test2;
    @end
    
    @implementation Person
    - (void)run {
        NSLog(@"%s",__func__);
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        // 本来能调用的方法
        if ([self respondsToSelector:aSelector]) {
            return [super methodSignatureForSelector:aSelector];
        }
        
        // 找不到的方法
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    // 找不到的方法,都会来到这里
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        NSLog(@"找不到%@方法", NSStringFromSelector(anInvocation.selector));
    }
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Person *per = [[Person alloc] init];
            [per run];
            [per test1];
            [per test2];
        }
        return 0;
    }
    

    运行结果

    消息转发.png
    三 isKindOfClass 和 isMemberOfClass的理解
    // 这句代码的方法调用者不管是哪个类(只要是NSObject体系下的),都返回YES
    NSLog(@"%d", [[NSObject class] isKindOfClass:[NSObject class]]);
    NSLog(@"%d", [[NSObject class] isMemberOfClass:[NSObject class]]);
    NSLog(@"%d", [[Person class] isKindOfClass:[Person class]]);
    NSLog(@"%d", [[Person class] isMemberOfClass:[Person class]]);
    

    打印结果

    image.png
    NSObject底层方法的实现

    判断对象类型

    • -(BOOL) isKindOfClass: classObj判断是否是这个类或者这个类的子类的实例
    • -(BOOL) isMemberOfClass: classObj 判断是否是这个类的实例
    @implementation NSObject
    
    - (BOOL)isMemberOfClass:(Class)cls {
        return [self class] == cls;
    }
    
    - (BOOL)isKindOfClass:(Class)cls {
        for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    
    + (BOOL)isMemberOfClass:(Class)cls {
        return object_getClass((id)self) == cls;
    }
    
    + (BOOL)isKindOfClass:(Class)cls {
        for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    @end
    
    • 下面代码执行结果如何
    // 实例对象
    id person = [[Person alloc] init];
    NSLog(@"%d", [person isMemberOfClass:[Person class]]);
    NSLog(@"%d", [person isMemberOfClass:[NSObject class]]);
    NSLog(@"%d", [person isKindOfClass:[Person class]]);
    NSLog(@"%d", [person isKindOfClass:[NSObject class]]);
        
    NSLog(@"------------类对象-------------");
    // 类对象
    NSLog(@"%d", [Person isKindOfClass:[NSObject class]]);
    NSLog(@"%d", [Person isMemberOfClass:[NSObject class]]);
    NSLog(@"%d", [Person isMemberOfClass:object_getClass([Person class])]);
    NSLog(@"%d", [Person isKindOfClass:object_getClass([NSObject class])]);
    

    执行结果

    image.png

    下面代码执行结果如何

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSLog(@"%d", [[NSObject class] isKindOfClass:[NSObject class]]);
            NSLog(@"%d", [[NSObject class] isMemberOfClass:[NSObject class]]);
            NSLog(@"%d", [[MJPerson class] isKindOfClass:[MJPerson class]]);
            NSLog(@"%d", [[MJPerson class] isMemberOfClass:[MJPerson class]]);
            
            NSLog(@"-----------");
            // 这句代码的方法调用者不管是哪个类(只要是NSObject体系下的),都返回YES
            NSLog(@"%d", [NSObject isKindOfClass:[NSObject class]]); // 1
            NSLog(@"%d", [NSObject isMemberOfClass:[NSObject class]]); // 0
            NSLog(@"%d", [MJPerson isKindOfClass:[MJPerson class]]); // 0
            NSLog(@"%d", [MJPerson isMemberOfClass:[MJPerson class]]); // 0
            NSLog(@"-----------");
            
            id person = [[MJPerson alloc] init];
            
            NSLog(@"%d", [person isMemberOfClass:[MJPerson class]]);
            NSLog(@"%d", [person isMemberOfClass:[NSObject class]]);
            NSLog(@"%d", [person isKindOfClass:[MJPerson class]]);
            NSLog(@"%d", [person isKindOfClass:[NSObject class]]);
            NSLog(@"-----------");
            
            NSLog(@"%d", [MJPerson isMemberOfClass:object_getClass([MJPerson class])]);
            NSLog(@"%d", [MJPerson isKindOfClass:object_getClass([NSObject class])]);
            NSLog(@"%d", [MJPerson isKindOfClass:[NSObject class]]);
            NSLog(@"%d", [MJPerson isMemberOfClass:[NSObject class]]);
        }
        return 0;
    }
    

    打印结果

    image.png
    1. -(BOOL) isKindOfClass: classObj判断是否是这个类或者这个类的子类的实例
    对象是类的实例,类是元类的实例
    2. -(BOOL) isMemberOfClass: classObj 判断是否是这个类的实例
    对象是类的实例,类是元类的实例
    
    面试题四 下面代码执行结果
    @interface Person : NSObject
    @property (copy, nonatomic) NSString *name;
    - (void)print;
    @end
    
    @implementation Person
    - (void)print {
        NSLog(@"my name is %@", self.name);
    }
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        id cls = [Person class];
        
        void *obj = &cls;
        
        [(__bridge id)obj print];
    }
    
    image.png
    - (void)viewDidLoad {
        [super viewDidLoad];
        NSString *test = @"123";
    
        id cls = [Person class];
        
        void *obj = &cls;
        
        [(__bridge id)obj print];
    }
    
    image.png
    分析如下
    • print为什么能够调用成功
    image.png
    • 为什么self.name变成了ViewController等其他内容
    image.png image.png

    局部变量在栈空间,栈空间地址分配是由高到底分配,所以先声明的局部变量在更高的地址上

    一 super的本质

    • super调用,底层会转换为objc_msgSendSuper2函数的调用,接收2个参数
    • struct objc_super2
    • SEL
    struct objc_super2 {
      id receiver;
      Class current_class;
    };
    
    • receiver是消息接收者
    • current_classreceiver的Class对象

    打印当前栈内存信息

    image.png image.png

    本文主要参考MJ老师的教案,非常感谢MJ老师。


    项目连接地址 - runtime-super
    项目连接地址 - runtime-super2


    关于runtime更多文章请看如下链接
    iOS-runtime-API详解+使用
    iOS Runtime原理及使用
    iOS - runtime如何通过selector找到对应的 IMP地址(分别考虑类方法和实例方法)
    iOS - Runtime之面试题详解一
    iOS-runtime之面试题详解二
    iOS runtime的使用场景-实战篇

    相关文章

      网友评论

          本文标题:iOS-底层原理(17)-runtime之面试题(class+s

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