Runtime系列:super调用函数本质、isMemberOfClass与isKindOfClass的区别、综合分析【05】

简介: Runtime系列:super调用函数本质、isMemberOfClass与isKindOfClass的区别、综合分析

1、super调用函数本质


class 方法的底层实现:


// 获取调用者对象的类
- (Class)class {
    return object_getClass(self);
}
// 获取调用者对象的夫类
- (Class)superclass {
    return class_getSuperclass(object_getClass(self));
}


super的底层实现【[super message]的底层实现】:


1、消息接收者仍然是子类对象。

2、从父类开始查找方法的实现。


示例:


#import "Person.h"
@interface Student : Person
@end


#import <Foundation/Foundation.h>
@interface Person : NSObject
- (void)run;
@end


- (instancetype)init
{
    if (self = [super init]) {
        NSLog(@"[self class] = %@", [self class]); // Student
        NSLog(@"[self superclass] = %@", [self superclass]); // Person
        NSLog(@"--------------------------------");
        /**
        * super调用实现本质:
        * objc_msgSendSuper({self, [Person class]}, @selector(class));
        */
        NSLog(@"[super class] = %@", [super class]); // Student
        NSLog(@"[super superclass] = %@", [super superclass]); // Person
    }
    return self;
}
@end


2、isMemberOfClass、isKindOfClass区别

先看下底层逻辑:


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


示例1:


   

id person = [[MJPerson alloc] init];
        // 判断左边的实例对象是否等于右边的类
        NSLog(@"%d", [person isMemberOfClass:[MJPerson class]]); // 1
        NSLog(@"%d", [person isMemberOfClass:[NSObject class]]); // 0
        // 判断左边的实例对象是否属于右边的类或子类
        NSLog(@"%d", [person isKindOfClass:[MJPerson class]]); // 1
        NSLog(@"%d", [person isKindOfClass:[NSObject class]]); // 1


示例2:


     

NSLog(@"%d", [NSObject isKindOfClass:object_getClass([NSObject class])]); // 1
        NSLog(@"%d", [NSObject isMemberOfClass:object_getClass([NSObject class])]); // 1
        NSLog(@"%d", [NSObject isKindOfClass:object_getClass([MJPerson class])]); // 0
        NSLog(@"%d", [MJPerson isMemberOfClass:object_getClass([MJPerson class])]); // 1
        // -------
        // 这句代码的方法调用者不管是哪个类(只要是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


总结:

1、实例方法是类的判断:


1、isMemberOfClass:判断左边的实例对象是否等于右边的类

2、isKindOfClass:判断左边的实例对象是否属于右边的类或子类


2、类方法是元类的判断:


1、正常情况下传参应该取元类对象进行判断断。

2、如果是传类对象判断,返回全部为0;除非右边入参为 [NSObject class],此时方法调用者不管是哪个类(只要是NSObject体系下的),都返回YES。


3、以下代码能不能执行成功?如果可以,打印结果是什么?

a0ac85d64f544e0684e18a7af04c010f.png

考察知识点:

1、super 调用的本质

2、函数栈空间的分配问题

3、消息机制

4、访问成员变量的本质


1、指针分析

1、此时 cls 存放的是 MJPerson类的地址

2、obj 是MJPerson类的地址的指针

3、MJPerson.name 是 MJPerson 结构体中的成员变量,位于isa的下一个栈中。

4、因为MJPerson 没有初始化,所以实际情况是 name 属性没有被转成员变量,成为堆中的一个变量。

5、因此,此时 self.name 取的值是 MJPerson结构体isa的下一个栈信息。

d24d60891f124299863c98c50654f75f.png



2、[super viewDidLoad]本质分析

底层结构:


 

struct abc = {
        self,
        [ViewController class]
    };
    objc_msgSendSuper2(abc, sel_registerName("viewDidLoad"));


从这个底层结构可知,MJPerson.isa 的下一个地址是self、再下一个是 [ViewController class]。那么其堆地址信息如下:


3、堆栈分析

9ba6b0ad29d44b7786fa7c4371a354ab.png


4、打印取值

- (void)print
{
    NSLog(@"my name is %@", self.name);
}


self.name 实际是取MJPerson结构体中isa的下一个地址【self->_name】,而MJPerson结构体中isa的下一个地址是ViewController中的self的实例对象。


综上分析,最终print打印出来的值是 self 对象。


验证:

04a1a754883e4cddaa22df4b76a727b3.png

相关文章
|
2月前
|
监控 前端开发 Java
分布式链路监控系统问题之执行原方法时不能调用method.invoke的问题如何解决
分布式链路监控系统问题之执行原方法时不能调用method.invoke的问题如何解决
|
5月前
|
存储 C++
函数嵌套调用:C++编程的核心技术
函数嵌套调用:C++编程的核心技术
48 1
|
2月前
|
存储 前端开发 rax
函数过程的调用
函数过程的调用
|
2月前
|
编译器 Go 开发者
Go 在编译时评估隐式类型的优点详解
【8月更文挑战第31天】
24 0
|
5月前
|
存储 安全 C++
【C++14保姆级教程】lambda 初始化捕获 new/delete 消除
【C++14保姆级教程】lambda 初始化捕获 new/delete 消除
235 0
|
编译器 Linux 调度
RT-Thread编程高阶用法-函数扩展之$Sub$与$Super$
RT-Thread编程高阶用法-函数扩展之$Sub$与$Super$
104 0
|
缓存 C++
12-objc_msgSend底层调用流程探究
12-objc_msgSend底层调用流程探究
49 0
普通函数中的this指向问题解决方案call
普通函数中的this指向问题解决方案call
48 0
|
缓存 自然语言处理 前端开发
【Java原理探索】「编译器专题」重塑认识Java编译器的执行过程(消除数组边界检查+公共子表达式)!
【Java原理探索】「编译器专题」重塑认识Java编译器的执行过程(消除数组边界检查+公共子表达式)!
159 0
【Java原理探索】「编译器专题」重塑认识Java编译器的执行过程(消除数组边界检查+公共子表达式)!