OC底层原理(五):类的内存结构优化

424 阅读12分钟

一、WWDC2020 - 类优化

WWDC2020的Advancements in the Objective-C runtime重点通过讲解了Objective-C底层优化过程,可以了解到class类的构成与运行时的DirtyMemoryCleanMemory的情况。本质上就是iOS没有swap方法管理DirtyMemory,尽量保存不改变的类数据到CleanMemory

CleanMemory与DirtyMemory

dirty memory要比clean memory昂贵的多,只要进行运行它就必须一直存在,通过分离出那些不会被改变的数据,可以把大部分的类数据存储为clean memory,这是苹果追求的。

Clean Memory

  1. clean memory 加载后会发生改内存
  2. class_ro_t 就属于clean memory,因为它是只读的,会对其内存进行修改
  3. clean memory 是可以进行移除的,从而节省更多的内存空间,因为如果你有需要clean memory,系统可以从磁盘中重新加载

Dirty Memory

  1. dirty memory 是指在进程运行时发生改内存
  2. 类结构一经使用就会变成 dirty memory,因为运行时会向它写入新的数据。例如创建一个新的方法缓存并从类中指向它,初始化类相关的子类和父类
  3. dirty memory是类数据被分成两部分的主要原因

类class的运行时的Memory变化与优化

  1. 最开始在app的二进制码中存在的是dirty memoryMYClass用户自定的class,与生成该类的clean memoryclass_ro_t
  2. 当这个类MYClass首次被使用时,运行时会为它分配额外的dirty memoryclass_rw_t用于读取-编写数据(重点通过这个数据结果观察运行时状态)
  3. 但是在这次WWDC的优化过程把这class_rw_t经常变动的部分单独分离出来到class_rw_ext_t,只留下类的指向。
  • 老结构图:

截屏2021-07-01 下午4.01.26.png

  • 优化完的结构图:

截屏2021-07-01 下午4.02.30.png

  • class_rw_t的数据结构:
  1. First SubclassNext Subling Class:包含了运行时才会生成的信息

  2. First SubclassNext Subling Class,所有的类都会变成一个树状结构,就是通过First SubclassNext Subling Class指针实现的,它允许运行时遍历当前使用的所有

  • class_rw_ext_t的数据结构:
  1. MethodsPropertiesprotocols:包含这3个是因为它们可以在运行时进行修改,当category被加载时,它可以向类中添加新的方法,也可以通过runtime API添加它们

  2. Demangled Name:这个是只有Swift才会使用的字段,因为整个数据结构OCSwift是共享的,但是Swift类本身并不需要这个字段,是为了有人要访问Swfit的OC名称的时候使用的,利用率比较低。

  • 关系图:
classDiagram

MYClass --|> class_rw_t:DirtyMemory
class_rw_t --|> class_rw_ext_t:DirtyMemory
class_rw_ext_t --|> class_ro_t:CleanMemory

class MYClass{
Metaclass
SuperClass
Flags
Method cache
}
class class_rw_t{
Flags
First Subclass
Next Sibling Class
}
class class_rw_ext_t{
Methods
Properties
Protocols
Demangled Name
}
class class_ro_t{
Flags
Size
Name
Methods
Protocols
Ivars
Properties
}


二、类的结构编码

SELIMP关系

  1. SEL方法编号,相当于书本的目录名称

  2. IMP函数指针地址,相当于书的页码

查找方法文档

类型编码图的获取途径:打开xcode--> command+shift+0--> 搜索ivar_getTypeEncoding--> 点击Type Encodings

官方编码表

  • 编码表: | Code | Meaning | | --- | --- | | c |A char| | i | An int | | s |A short| | l |A long, is treated as a 32-bit quantity on 64-bit programs.| | q | A long long | | c |An unsigned char| | I |An unsigned int| | S | An unsigned short | | L |An unsigned long | | Q | An unsigned long long | | f |A float| | d |A double| | B | A C++ bool or a C99 _Bool | | v |A void | | * | A character string (char *) | | @ |An object (whether statically typed or typed id)| | # |A class object (Class)| | : | A method selector (SEL) | | [array type] |An array| | {name=type...} |A structure| | (name=type...) |A union| | bnum |A bit field of num bits| | ^type |A pointer to type| | ? |An unknown type (among other things, this code is used for function pointers)|

  • 验证代码:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

#pragma mark - 各种类型编码
void lgTypes(void){
    NSLog(@"char --> %s",@encode(char));
    NSLog(@"int --> %s",@encode(int));
    NSLog(@"short --> %s",@encode(short));
    NSLog(@"long --> %s",@encode(long));
    NSLog(@"long long --> %s",@encode(long long));
    NSLog(@"unsigned char --> %s",@encode(unsigned char));
    NSLog(@"unsigned int --> %s",@encode(unsigned int));
    NSLog(@"unsigned short --> %s",@encode(unsigned short));
    NSLog(@"unsigned long --> %s",@encode(unsigned long long));
    NSLog(@"float --> %s",@encode(float));
    NSLog(@"bool --> %s",@encode(bool));
    NSLog(@"void --> %s",@encode(void));
    NSLog(@"char * --> %s",@encode(char *));
    NSLog(@"id --> %s",@encode(id));
    NSLog(@"Class --> %s",@encode(Class));
    NSLog(@"SEL --> %s",@encode(SEL));
    int array[] = {1,2,3};
    NSLog(@"int[] --> %s",@encode(typeof(array)));
    typedef struct person{
        char *name;
        int age;
    }Person;
    NSLog(@"struct --> %s",@encode(Person));
    
    typedef union union_type{
        char *name;
        int a;
    }Union;
    NSLog(@"union --> %s",@encode(Union));

    int a = 2;
    int *b = {&a};
    NSLog(@"int[] --> %s",@encode(typeof(b)));
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        // objc_object VS objc_class
        // person VS NSObject
        // id objc_object *
        // class objc_class *
      
        lgTypes();
    }
    return 0;
}

  • llvm打印:
2021-07-01 01:03:52.622628+0800 001-类的属性与变量[92033:3820537] char --> c
2021-07-01 01:03:52.623543+0800 001-类的属性与变量[92033:3820537] int --> i
2021-07-01 01:03:52.623621+0800 001-类的属性与变量[92033:3820537] short --> s
2021-07-01 01:03:52.623666+0800 001-类的属性与变量[92033:3820537] long --> q
2021-07-01 01:03:52.623698+0800 001-类的属性与变量[92033:3820537] long long --> q
2021-07-01 01:03:52.623817+0800 001-类的属性与变量[92033:3820537] unsigned char --> C
2021-07-01 01:03:52.623884+0800 001-类的属性与变量[92033:3820537] unsigned int --> I
2021-07-01 01:03:52.623939+0800 001-类的属性与变量[92033:3820537] unsigned short --> S
2021-07-01 01:03:52.623995+0800 001-类的属性与变量[92033:3820537] unsigned long --> Q
2021-07-01 01:03:52.624076+0800 001-类的属性与变量[92033:3820537] float --> f
2021-07-01 01:03:52.624146+0800 001-类的属性与变量[92033:3820537] bool --> B
2021-07-01 01:03:52.624191+0800 001-类的属性与变量[92033:3820537] void --> v
2021-07-01 01:03:52.624248+0800 001-类的属性与变量[92033:3820537] char * --> *
2021-07-01 01:03:52.631074+0800 001-类的属性与变量[92033:3820537] id --> @
2021-07-01 01:03:52.631115+0800 001-类的属性与变量[92033:3820537] Class --> #
2021-07-01 01:03:52.631153+0800 001-类的属性与变量[92033:3820537] SEL --> :
2021-07-01 01:03:52.631189+0800 001-类的属性与变量[92033:3820537] int[] --> [3i]
2021-07-01 01:03:52.631226+0800 001-类的属性与变量[92033:3820537] struct --> {person=*i}
2021-07-01 01:03:52.631262+0800 001-类的属性与变量[92033:3820537] union --> (union_type=*i)
2021-07-01 01:03:52.631301+0800 001-类的属性与变量[92033:3820537] int[] --> ^i

方法编码符号

定义一个LGPerson类,自定义属性和成员变量,在终端使用xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp生成.cpp文件。

  • 源代码:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface LGPerson : NSObject
{
    // STRING   int  double  float char bool
    NSString *hobby; // 字符串
    int a;
    NSObject *objc;  // 结构体
}

@property (nonatomic, copy) NSString *nickName;
@property (atomic, copy) NSString *acnickName;
@property (nonatomic) NSString *nnickName;
@property (atomic) NSString *anickName;
@property (nonatomic, strong) NSString *name;
@property (atomic, strong) NSString *aname;

@end


@implementation LGPerson


@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
     
        LGPerson *person = [LGPerson alloc];
        person.name = @"KC"; // setName -> objc_setPropety
       
        NSLog(@"Hello, World!");
    }
    return 0;
}
  • 生成main.cpp的部分源码:
static struct /*_method_list_t*/ {
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[24];
} _OBJC_$_INSTANCE_METHODS_LGPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	24,
	{{(struct objc_selector *)"nickName", "@16@0:8", (void *)_I_LGPerson_nickName},
	{(struct objc_selector *)"setNickName:", "v24@0:8@16", (void *)_I_LGPerson_setNickName_},
	{(struct objc_selector *)"acnickName", "@16@0:8", (void *)_I_LGPerson_acnickName},
	{(struct objc_selector *)"setAcnickName:", "v24@0:8@16", (void *)_I_LGPerson_setAcnickName_},
	{(struct objc_selector *)"nnickName", "@16@0:8", (void *)_I_LGPerson_nnickName},
	{(struct objc_selector *)"setNnickName:", "v24@0:8@16", (void *)_I_LGPerson_setNnickName_},
	{(struct objc_selector *)"anickName", "@16@0:8", (void *)_I_LGPerson_anickName},
	{(struct objc_selector *)"setAnickName:", "v24@0:8@16", (void *)_I_LGPerson_setAnickName_},
	{(struct objc_selector *)"name", "@16@0:8", (void *)_I_LGPerson_name},
	{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LGPerson_setName_},
	{(struct objc_selector *)"aname", "@16@0:8", (void *)_I_LGPerson_aname},
	{(struct objc_selector *)"setAname:", "v24@0:8@16", (void *)_I_LGPerson_setAname_},
	{(struct objc_selector *)"nickName", "@16@0:8", (void *)_I_LGPerson_nickName},
	{(struct objc_selector *)"setNickName:", "v24@0:8@16", (void *)_I_LGPerson_setNickName_},
	{(struct objc_selector *)"acnickName", "@16@0:8", (void *)_I_LGPerson_acnickName},
	{(struct objc_selector *)"setAcnickName:", "v24@0:8@16", (void *)_I_LGPerson_setAcnickName_},
	{(struct objc_selector *)"nnickName", "@16@0:8", (void *)_I_LGPerson_nnickName},
	{(struct objc_selector *)"setNnickName:", "v24@0:8@16", (void *)_I_LGPerson_setNnickName_},
	{(struct objc_selector *)"anickName", "@16@0:8", (void *)_I_LGPerson_anickName},
	{(struct objc_selector *)"setAnickName:", "v24@0:8@16", (void *)_I_LGPerson_setAnickName_},
	{(struct objc_selector *)"name", "@16@0:8", (void *)_I_LGPerson_name},
	{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LGPerson_setName_},
	{(struct objc_selector *)"aname", "@16@0:8", (void *)_I_LGPerson_aname},
	{(struct objc_selector *)"setAname:", "v24@0:8@16", (void *)_I_LGPerson_setAname_}}
};
  • 找到nickName:"@16@0:8"做讲解:

Screenshot 2021-07-01 at 3.08.03 PM.png

@16@0:8

CodeMeaning
@id
16占用内存
@id
0从0位置开始
:SEL
8从8号位置开始

三、成员变量、实例变量与属性

  • 实例变量+基本数据类型变量=成员变量
  • ivar成员变量(分类没有)+setter方法+getter方法=propetry属性
  1. 成员变量:intdoublefloatcharbool等基础类型变量与实例变量,在OC底层用ivar表示

  2. 实例变量:对象等引用类型变量

  3. 属性:是@interface有通过@property定义的变量,编译器会自动生成settergetterivar(分类没有生成成员变量,要用关联对象)


四、属性的settergetter方法实现方式

关系图:

graph TD
setter --> objc_setProperty 
setter --> 内存偏移赋值

getter --> objc_getProperty
getter --> 读取偏移内存

验证属性的方法

  1. objc_setProperty是一个中间层代码。为了每一个属性在使用setter方法的时候,不必为其在底层创建单独的实现方法;当检测到属性对象使用copy修饰的时候,会将此对象的setter方法重定向到objc_setProperty(),然后只要在底层实现setProperty方法就可以将属性对象的setter方法实现了。

  2. objc_getProperty也是个中间层代码。只有属性既是atomic,也是copy时,才会将对象属性的getter方法重定向到objc_getProperty

定义一个LGPerson类,添加各种修饰词(weakstrongweaknonatomicatomic)的自定义属性和成员变量,终端输出xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp生成main.cpp文件。

  • objc代码:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface LGPerson : NSObject
{
    // STRING   int  double  float char bool
    NSString *hobby; // 字符串
    int a;
    NSObject *objc;  // 结构体
}

@property (nonatomic, copy) NSString *nickName;
@property (atomic, copy) NSString *acnickName;
@property (nonatomic) NSString *nnickName;
@property (atomic) NSString *anickName;

@property (nonatomic, strong) NSString *name;
@property (atomic, strong) NSString *aname;
@property (nonatomic,assign) NSInteger height;
@property (atomic,assign) NSInteger weight;
@end

@implementation LGPerson


@end

@interface LGTeacher : NSObject

@end


@implementation LGTeacher

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        // objc_object VS objc_class
        // person VS NSObject
        // id objc_object *
        // class objc_class *
        
        LGPerson *person = [LGPerson alloc];
        person.name = @"KC"; // setName -> objc_setPropety
        LGTeacher *teacher = [LGTeacher alloc];
        // Class tClass     = object_getClass(teacher);
        Class pClass     = object_getClass(person);
        
        NSLog(@"Hello, World!");
    }
    return 0;
}

  • 生成c++部分代码:
static NSString * _I_LGPerson_nickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nickName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_LGPerson_setNickName_(LGPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _nickName), (id)nickName, 0, 1); }

extern "C" __declspec(dllimport) id objc_getProperty(id, SEL, long, bool);

static NSString * _I_LGPerson_acnickName(LGPerson * self, SEL _cmd) { typedef NSString * _TYPE;
return (_TYPE)objc_getProperty(self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acnickName), 1); }
static void _I_LGPerson_setAcnickName_(LGPerson * self, SEL _cmd, NSString *acnickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _acnickName), (id)acnickName, 1, 1); }

static NSString * _I_LGPerson_nnickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nnickName)); }
static void _I_LGPerson_setNnickName_(LGPerson * self, SEL _cmd, NSString *nnickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nnickName)) = nnickName; }

static NSString * _I_LGPerson_anickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_anickName)); }
static void _I_LGPerson_setAnickName_(LGPerson * self, SEL _cmd, NSString *anickName) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_anickName)) = anickName; }

static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)) = name; }

static NSString * _I_LGPerson_aname(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_aname)); }
static void _I_LGPerson_setAname_(LGPerson * self, SEL _cmd, NSString *aname) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_aname)) = aname; }

static NSInteger _I_LGPerson_height(LGPerson * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_LGPerson$_height)); }
static void _I_LGPerson_setHeight_(LGPerson * self, SEL _cmd, NSInteger height) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_LGPerson$_height)) = height; }

static NSInteger _I_LGPerson_weight(LGPerson * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_LGPerson$_weight)); }
static void _I_LGPerson_setWeight_(LGPerson * self, SEL _cmd, NSInteger weight) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_LGPerson$_weight)) = weight; }
  • c++图片:

Screenshot 2021-07-01 at 10.01.30 PM.png

  • 结论:
  1. 属性的setter方法的实现方式有两种:objc_setProperty内存偏移

  2. 属性的getter方法也有两种实现方式:objc_getProperty内存偏移

  3. 属性用copyatomic修饰的时候,会将此变量的settergetter方法重定向到objc_setProperty()objc_getProperty()

  4. 属性只用copy就会使用objc_setProperty(),而只有同时使用copyatomic才会使用objc_getProperty()

  5. 如果声明的对象不设置任何除原子性之外的属性,那么默认属性是strong,不会触发objc_setPropertyobjc_getProperty

objc_setPropertyobjc_getProperty

objc_setPropertyobjc_getProperty是objc4.8源码里属性的成员变量的settergetter方法,本质上还是使用内存偏移找到成员变量进行读写;但是过程中通过atomic判断是否在读写过程加自旋锁spinlock_t),或者copy涉及新值的深浅拷贝问题。

  • objc4.8各种类型objc_setProperty源代码:
// 属性设置方法
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{
    bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
    bool mutableCopy = (shouldCopy == MUTABLE_COPY);
    reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
// 原子属性设置方法
void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, true, false, false);
}
// 非原子属性设置方法
void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, false, false, false);
}

// 原子复制属性设置方法
void objc_setProperty_atomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, true, true, false);
}

// 非原子复制属性设置方法
void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, false, true, false);
}
  • objc4.8objc_getPropertyreallySetProperty源代码:
// 属性获取方法
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
    if (offset == 0) {
        return object_getClass(self);
    }

    // Retain release world
    id *slot = (id*) ((char*)self + offset);
    if (!atomic) return *slot;
        
    // Atomic retain release world
    spinlock_t& slotlock = PropertyLocks[slot];
    slotlock.lock();
    id value = objc_retain(*slot);
    slotlock.unlock();
    
    // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
    return objc_autoreleaseReturnValue(value);
}
// 属性设置方法
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}
  • 结论:
  1. 无论使用什么修饰词的属性的setter方法objc_setPropertyobjc_setProperty_atomic等等,最终都是调用了reallySetProperty
  2. 属性的setter方法只有一个objc_getProperty方法

LLVM验证对象属性为copy时,setter与getter

  • llvm源码验证流程:
  1. objc_setProperty -> getSetPropertyFn - GetPropertySetFunction -> PropertyImplStrategy -> IsCopy(判断)
  2. objc_getProperty -> getGetPropertyFn - GetPropertyGetFunction -> PropertyImplStrategy -> IsCopy(判断)
  • objc_setPropertyobjc_getProperty图:

Screenshot 2021-07-01 at 10.58.39 PM.png

  • getSetPropertyFngetGetPropertyFn图: Screenshot 2021-07-01 at 11.03.48 PM.png

  • GetPropertySetFunctionGetPropertyGetFunction图:

Screenshot 2021-07-01 at 11.47.37 PM.png

  • GetPropertySetFunction被调用图: Screenshot 2021-07-01 at 11.53.28 PM.png

  • GetPropertyGetFunction被调用图: Screenshot 2021-07-01 at 11.25.53 PM.png

  • PropertyImplStrategy图: Screenshot 2021-07-01 at 11.29.31 PM.png


五、补充其他内容点

isKindOfClassisMemberOfClass

  1. 查找官方文档可以知道isKindOfClass是方法调用者是传入的实例对象,或者调用者是传入类的继承者链中的实例对象,则返回YES。
  2. isMemberOfClass是方法调用者必须是传入的实例对象才返回YES。
  • 通过一个实例来探究下isKindOfClassisMemberOfClass

  • 代码:

#import <Foundation/Foundation.h>

@interface LGPerson : NSObject

@end

@implementation LGPerson

@end

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        lgKindofDemo();
    }
    return 0;
}

  • llvm打印:
2021-07-03 00:11:28.157280+0800 KCObjcBuild[11864:4389325]  re1 :1
 re2 :0
 re3 :0
 re4 :0
2021-07-03 00:11:28.157470+0800 KCObjcBuild[11864:4389325]  re5 :1
 re6 :1
 re7 :1
 re8 :1
  • 汇编调试方法图:

Screenshot 2021-07-03 at 1.09.53 AM.png

  • 结论:
  1. 在汇编代码中发现其实编译器做了优化,hookisKindOfClass方法为调用了objc_opt_isKindOfClass;hookisMemberOfClass方法为调用了objc_opt_class
  2. 下一步分析objc_opt_isKindOfClassisKindOfClassobjc_opt_classisMemberOfClass这四个方法。

objc_opt_isKindOfClass底层原理

在objc4.8源码中的objc-internal.h中可以知道支持的版本masOS大于10.15,iOS大于13.0

OBJC_EXPORT BOOL
objc_opt_isKindOfClass(id _Nullable obj, Class _Nullable cls)
	OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0);

  • 具体实现:
// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__ //现在基本上都用OBJC2版本
    //slowpath(!obj)  obj为空的是小概率事件基本不会发生
    if (slowpath(!obj)) return NO;
    // 获取类或者是元类:obj是对象就获取类,如果obj是类就获取元类
    Class cls = obj->getIsa();
    //fastpath(!cls->hasCustomCore()) (类或者父类中大概率没有默认的isKindOfClass方法)
    if (fastpath(!cls->hasCustomCore())) {
        for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif //OBJC版本直接走消息转发
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}

  • 结论:
  1. obj->getIsa()获取类或者元类:obj是对象就获取类,obj是类就获取元类 然后就接着for循环,for里面的代码和isKindOfClass逻辑一样就不进行分析了,可以直接看 isKindOfClass的底层原理。

isKindOfClass底层原理

  • 具体实现:
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
        if (tcls == cls) return YES;
    }
    return NO;
}

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

  • 结论:
  1. + isKindOfClass流程。 类的元类 vs cls(需要比较的类),不同继续比较 。元类的父类 vs cls,不同继续比较直到找到根元类根元类 vs cls,不同继续比较。根类(NSObject) vs cls,如果还不相同则根类(NSObject)的父类为nil,跳出循环返回NO

  2. - isKindOfClass流程。获取当前对象所属类, vs cls,不同继续比较 。类的父类 vs cls,不同继续比较直到找到根类(NSObject)根类(NSObject) vs cls,如果还不相同则根类(NSObject)的父类为nil,跳出循环返回NO

objc_opt_class底层原理

在objc4.8源码中的objc-internal.h中可以知道支持的版本masOS大于10.15,iOS大于13.0

OBJC_EXPORT Class _Nullable
objc_opt_class(id _Nullable obj)
	OBJC_AVAILABLE(10.15, 13.0, 13.0, 6.0, 5.0);

  • 具体实现:
// Calls [obj class]
Class
objc_opt_class(id obj)
{
#if __OBJC2__
    if (slowpath(!obj)) return nil;
    // 获取类或者是元类:obj是对象就获取类,如果obj是类就获取元类
    Class cls = obj->getIsa();
    //(类或者父类中大概率没有默认的class方法)
    if (fastpath(!cls->hasCustomCore())) {
        //cls是类 返回cls,如果cls是元类,obj是类,返回obj还是类
        return cls->isMetaClass() ? obj : cls;
    }
#endif
    return ((Class(*)(id, SEL))objc_msgSend)(obj, @selector(class));
}

  • 结论:
  1. objc_opt_class的实现就是获取,如果参数是对象则返回,如果是就返回

isMemberOfClass底层原理

  • 具体实现:
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

  • 结论:
  1. + isMemberOfClass 流程。 类的元类 vs cls(需要比较的类),相同就返回YES,否则返回NO

  2. - isMemberOfClass 流程。 vs cls(需要比较的类),相同就返回YES,否则返回NO