1. 类的底层实现

先写一个 Person 类:

@interface Person : NSObject
@property (nonatomic, copy) NSString *p_name;
@property (nonatomic, assign) int p_age; - (void)p_instanceMethod1;
@end @implementation Person
- (void)p_instanceMethod1{
NSLog(@"%s",__func__);
}
@end

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

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

    ...

    return (Class)(isa.bits & ISA_MASK)
}

总结:

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

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

2. isa_t 的类型

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

union isa_t { //联合体
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
//提供了cls 和 bits ,两者是互斥关系
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};

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

对象与类的 isa 的指向关系

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

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

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

应用:判断对象类型

下面的打印结果是什么:

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

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

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

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

源码:

// 类对象,是否是指定的元类的实例
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
} // 实例对象,是否是指定的类的实例
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
} // 类对象,是否是指定的元类cls的实例,或者是cls继承者链中子类的实例
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->super_class) {
if(tcls == cls) return YES;
}
return NO;
}
// 实例对象,是否是指定的类的实例,或者是cls继承者链中子类的实例
-(BOOL)isKindOfClass:(Class)cls {
for(Class tcls = [self class]; tcls; tcls = tcls->super_class) {
if(tcls == cls) return YES;
}
return NO;
}

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

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默认的日志中间件, ...

  10. socket_server源码剖析、python作用域、IO多路复用

    本节内容: 课前准备知识: 函数嵌套函数的使用方法: 我们在使用函数嵌套函数的时候,是学习装饰器的时候,出现过,由一个函数返回值是一个函数体情况. 我们在使用函数嵌套函数的时候,最好也这么写. def ...

随机推荐

  1. 使用laravel一分钟搭建CURD后台页面

    配置即一切 一切皆于需求,后台从0开始搭建,但是写了一两个页面后发现太多的是对单表的增删改查操作,于是就想到了,能不能做一个快速搭建的后台.想到一句话,配置即一切.如果一个CURD后台能只进行配置就自 ...

  2. PHP读文件的一个乱码问题

    D:/3.txt是utf-8文件 $f1 = fopen('D:/3.txt','r');$str = fread($f1,10000);fclose($f1);echo substr($str,1, ...

  3. python 练习(一)代码统计工具的实现

    最近部门成立了一个python学习小组,旨在让大家在做项目中开始成长起来,于是老大就给布置了第一个小任务:代码统计工具,具体的需求如下: 需求: . 能够统计指定目录下C++程序的代码行数. . C+ ...

  4. [转]Linux下的Makefile

    Makefile 介绍——————— make命令执行时,需要一个 Makefile 文件,以告诉make命令需要怎么样的去编译和链接程序. 首先,我们用一个示例来说明Makefile的书写规则.以便 ...

  5. asp.net mvc5 设置Area下的为启动页

    只需修改App_Start文件夹下RouteConfig中RegisterRoutes方法 public static void RegisterRoutes(RouteCollection rout ...

  6. 500多条Linux信息

    http://www.cnblogs.com/zgqjymx/myposts.html?page=77 http://www.cnblogs.com/zgqjymx/tag/%E5%8E%9F%E5% ...

  7. SpringBoot Web学习笔记

    一.资源的访问: 情形一.所有的  /webjars/**  都会去 classpath:/META_INFO/resource/webjars/ 下找资源: webjars:以jar包的方式引入静态 ...

  8. 前端代理----whistle

    场景一:如何将本地的请求代理到服务器上(如果接口没有校验登陆的情况) 最简单的方法:在项目文件中找到webpack开发环境的配置文件,配置devServer对象 devServer: { conten ...

  9. Spring-IOC 扩展点 BeanFactoryPostProcessor及其子接口解析

    BeanFactoryPostProcessor 接口的英文描述: Allows for custom modification of an application context's bean de ...

  10. android studio 引用远程仓库下载慢(JCenter下载慢)的办法

    https://blog.csdn.net/linglingchenchen/article/details/62236723 解决android studio引用远程仓库下载慢(JCenter下载慢 ...