1. 类的底层实现

先写一个 Person 类:

  1. @interface Person : NSObject
  2. @property (nonatomic, copy) NSString *p_name;
  3. @property (nonatomic, assign) int p_age;
  4.  
  5. - (void)p_instanceMethod1;
  6. @end
  7.  
  8. @implementation Person
  9. - (void)p_instanceMethod1{
  10. NSLog(@"%s",__func__);
  11. }
  12. @end

使用 clang 编译器, clang -rewrite-objc Person.m -o Person.cpp  将 Person.m  编译成 Person.cpp 文件,部分代码如下:

  1. /// 1: Person 类型的底层结构
  2. struct NSObject_IMPL {
  3. Class isa;
  4. };
  5.  
  6. struct Person_IMPL {
  7. struct NSObject_IMPL NSObject_IVARS;
  8. int _p_age;
  9. NSString * _Nonnull _p_name;
  10. };
  11.  
  12. /// 2: p_name 属性的底层结构
  13. // get
  14. static NSString * _Nonnull _I_Person_p_name(Person * self, SEL _cmd) { return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_Person$_p_name)); }
  15. extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
  16. // set
  17. static void _I_Person_setP_name_(Person * self, SEL _cmd, NSString * _Nonnull p_name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _p_name), (id)p_name, 0, 1); }
  18.  
  19. /// 3: p_age 类型的底层结构
  20. // get
  21. static int _I_Person_p_age(Person * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_Person$_p_age)); }
  22. // set
  23. static void _I_Person_setP_age_(Person * self, SEL _cmd, int p_age) { (*(int *)((char *)self + OBJC_IVAR_$_Person$_p_age)) = p_age; }
  24.  
  25. /// 4: p_instanceMethod1 方法的底层结构
  26. static void _I_Person_p_instanceMethod1(Person * self, SEL _cmd) {
  27. NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_0y_j3bzj65z6vw6hy1chw_4m0000gp_T_Person_f010c0_mi_0,__func__);
  28. }
  • NSObject 类被编译成了 NSObject_IMPL 的结构体。
  • Person 类被编译成了 Person_IMPL 的结构体。
  • Person 类的内部还增加了一个 NSObject_IMPL 的结构体
    • 我们知道 Person 继承于 NSObject, 所以它的底层实现中是第一个成员是父类的结构体,就是底层继承的实现方式。用这样的方式拥有父类所有的成员变量。
    • NSObject_IMPL 是 NSObject 类的编译后的结构体,它的内部只有一个 Class 类型的 isa 成员变量。我们知道 isa 是 isa_t 类型的,那为什么在这里定义成 Class 类型呢?这是为了更加直观的提现出它代表的是类的信息,所以在获取isa 的方法中,将它强制转换成了Class 类型, 代码如下:
  1. inline Class objc_object::ISA() {
  2.  
  3. ...
  4.  
  5. return (Class)(isa.bits & ISA_MASK)
  6. }

总结:

1.类的底层实现是结构体。

2.继承是通过把父类的结构体声明为本类结构体的第一个成员变量实现的。

2. isa_t 的类型

联合体: 所有成员可以是不同的类型,但是公用一块内存区域,设置了一个成员变量就会覆盖另一个成员变量的数据。优点是节省空间。

  1. union isa_t { //联合体
  2. isa_t() { }
  3. isa_t(uintptr_t value) : bits(value) { }
  4. //提供了cls 和 bits ,两者是互斥关系
  5. Class cls;
  6. uintptr_t bits;
  7. #if defined(ISA_BITFIELD)
  8. struct {
  9. ISA_BITFIELD; // defined in isa.h
  10. };
  11. #endif
  12. };

isa 指针占用8字节,64位。64位中不同的位代表不同的含义:

对象与类的 isa 的指向关系

对象.isa -> 类.super -> 父类.super -> 根类.super -> nil

类.isa -> 元类.super -> 父元类.super -> 根元类.super -> 根类.super -> nil

元类.isa = 父元类.isa = 根元类.isa = 根元类

应用:判断对象类型

下面的打印结果是什么:

  1. // [NSObject class] = NSObject
  2. // object_getClass((id)[NSObject class]) = NSObject meta class
  3.  
  4. // 沿着 NSObject 的继承者链去找根元类 -> 根类 == NSObject meta class 或者 NSObject meta class 的父类的实例
  5. BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
  6. BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
  7. BOOL res3 = [(id)[TestObject class] isKindOfClass:[TestObject class]];
  8. BOOL res4 = [(id)[TestObject class] isMemberOfClass:[TestObject class]];

只有第一个是YES, 剩下的都是NO。 

isKindOfClass: 判断自己的isa 指向的类是否等于传入的类,不等于的话,找自己的继承连中的父类看有没有等于传入的类,有则YES,没有则NO

isMemberOfClass 判断自己的isa 指向的类是否等于传入的类,等于则YES,不等于则NO

源码:

  1. // 类对象,是否是指定的元类的实例
  2. + (BOOL)isMemberOfClass:(Class)cls {
  3. return object_getClass((id)self) == cls;
  4. }
  5.  
  6. // 实例对象,是否是指定的类的实例
  7. - (BOOL)isMemberOfClass:(Class)cls {
  8. return [self class] == cls;
  9. }
  10.  
  11. // 类对象,是否是指定的元类cls的实例,或者是cls继承者链中子类的实例
  12. + (BOOL)isKindOfClass:(Class)cls {
  13. for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->super_class) {
  14. if(tcls == cls) return YES;
  15. }
  16. return NO
  17. }
  18. // 实例对象,是否是指定的类的实例,或者是cls继承者链中子类的实例
  19. -(BOOL)isKindOfClass:(Class)cls {
  20. for(Class tcls = [self class]; tcls; tcls = tcls->super_class) {
  21. if(tcls == cls) return YES;
  22. }
  23. return NO;
  24. }

青山不改,绿水长流,感谢每位佳人支持!

OC源码剖析对象的本质的更多相关文章

  1. java源码剖析: 对象内存布局、JVM锁以及优化

    一.目录 1.启蒙知识预热:CAS原理+JVM对象头内存存储结构 2.JVM中锁优化:锁粗化.锁消除.偏向锁.轻量级锁.自旋锁. 3.总结:偏向锁.轻量级锁,重量级锁的优缺点. 二.启蒙知识预热 开启 ...

  2. Python源码剖析——01内建对象

    <Python源码剖析>笔记 第一章:对象初识 对象是Python中的核心概念,面向对象中的"类"和"对象"在Python中的概念都为对象,具体分为 ...

  3. 老李推荐:第2章4节《MonkeyRunner源码剖析》了解你的测试对象: NotePad窗口Activity之菜单简介

    老李推荐:第2章4节<MonkeyRunner源码剖析>了解你的测试对象: NotePad窗口Activity之菜单简介   NotePad窗口Activity之菜单简介 这里我们总共用到 ...

  4. 老李推荐:第2章3节《MonkeyRunner源码剖析》了解你的测试对象: NotePad窗口Activity之NoteEditor简介

    老李推荐:第2章3节<MonkeyRunner源码剖析>了解你的测试对象: NotePad窗口Activity之NoteEditor简介   我们在增加和编辑一个日记的时候会从NotesL ...

  5. 老李推荐:第2章2节《MonkeyRunner源码剖析》了解你的测试对象: NotePad窗口Activity之NotesList简介

    老李推荐:第2章2节<MonkeyRunner源码剖析>了解你的测试对象: NotePad窗口Activity之NotesList简介   NotePad窗口Activity之NotesL ...

  6. 老李推荐:第2章1节《MonkeyRunner源码剖析》了解你的测试对象: NotePad应用简介

    老李推荐:第2章1节<MonkeyRunner源码剖析>了解你的测试对象: NotePad应用简介   本书脚本相关的示例常会用到Android SDK自带的NotePad这个应用,所以这 ...

  7. Python 源码剖析(一)【python对象】

    处于研究python内存释放问题,在阅读部分python源码,顺便记录下所得.(基于<python源码剖析>(v2.4.1)与 python源码(v2.7.6)) 先列下总结:      ...

  8. Python开发【源码剖析】 List对象

    前言 本文探讨的Python版本为2.7.16,可从官网上下载,把压缩包Python-2.7.16.tgz解压到本地即可 需要基本C语言的知识(要看的懂) PyListObject对象 PyListO ...

  9. Node 进阶:express 默认日志组件 morgan 从入门使用到源码剖析

    本文摘录自个人总结<Nodejs学习笔记>,更多章节及更新,请访问 github主页地址.欢迎加群交流,群号 197339705. 章节概览 morgan是express默认的日志中间件, ...

随机推荐

  1. Python - typing 模块 —— 常用类型提示

    前言 typing 是在 python 3.5 才有的模块 前置学习 Python 类型提示:https://www.cnblogs.com/poloyy/p/15145380.html 常用类型提示 ...

  2. 004 Ethernet(以太网)详解

    一.以太网 以太网是一种计算机局域网技术.IEEE组织的IEEE 802.3标准制定了以太网的技术标准,它规定了包括物理层的连线.电子信号和介质访问层协议的内容. 以太网有两类:第一类是经典以太网,第 ...

  3. SpringDataJpa使用原生sql(EntityManager)动态拼接,分页查询

    SpringDataJpa Spring Data JPA是较大的Spring Data系列的一部分,可轻松实现基于JPA的存储库.该模块处理对基于JPA的数据访问层的增强支持.它使构建使用数据访问技 ...

  4. C++ native 转 C++的CLI

  5. uwp 的个人名片

    xml code ---------------------------------------------------------------------- <Page x:Class=&qu ...

  6. JDBC基础篇(MYSQL)——使用CallabeStatement调用存储过程

    注意:其中的JdbcUtil是我自定义的连接工具类:代码例子链接: package day04_callable; import java.sql.CallableStatement; import ...

  7. Flink Data transformation(转换)

    Flink Data transformation 算子学习 1.Source:数据源,Flink在流处理和批处理上的source大概有4类: 基于本地集合的source.基于文件的source.基于 ...

  8. Go测试--子测试

    目录 简介 简单的例子 子测试命名规则 过滤筛选 子测试并发 总结 简介 简单的说,子测试提供一种在一个测试函数中执行多个测试的能力,比如原来有TestA.TestB和TestC三个测试函数,每个测试 ...

  9. 高性能利器:CDN我建议你好好学一下!

    硬核干货分享,欢迎关注[Java补习课]成长的路上,我们一起前行 ! <高可用系列文章> 已收录在专栏,欢迎关注! CDN 概述 CDN 全称 Content Delivery Netwo ...

  10. 第一次实战:XX漫画的XSS盲打

    第一次实战:XX漫画的XSS盲打 XSS盲打 盲打是一种惯称的说法,就是不知道有没有XSS漏洞存在的情况下,不顾一切的输入XSS代码在留言啊投诉窗口啊之类的地方,尽可能多的尝试XSS的语句,就叫盲打. ...