1. WWDC 类结构的优化
WWDC 2020
-Runtime
优化:
- 类结构的优化
- 方法列表的优化
Tagged Pointer
格式的优化
下面我们只针对第一项“类结构的优化”进行详细说明
1.1 Clean Memory & Dirty Memory
1.1.1 Clean Memory
- 加载后不会再改变的内存
class_ro_t
是只读的,属于Clean Memory
- 可移除,从而节省更多内存空间。如果你有需要,系统可以从磁盘中重新加载
1.1.2 Dirty Memory
- 进程运行时会发生变化的内存
- 类结构体一旦被使用就是
Dirty Memory
,因为运行时会写入新的数据,例如:方法缓存 Dirty Memory
是类数据被分为两部分的原因
Dirty Memory
比Clean Memory
更昂贵,因为在进程运行的整个过程中,都需要被保留。通过分离出那些永远不会改变的数据,将大部分的类数据存储为Clean Memory
1.2 类结构的优化
类第一次从磁盘被加载到内存时的结构
- 类对象包含了最常用的信息:元类、父类、以及方法的缓存。它还有一个指针,指向额外信息
class_ro_t
,其中ro
表示只读 class_ro_t
中包含了类名、方法、协议、实例变量和属性等信息
当一个类首次被使用,运行时会为它分配额外的存储容量,即:class_rw_t
- 其中
rw
表示可读写
通过firstSubclass
和nextSiblingClass
,所有的类都会链接成一个树状结构
- 允许运行时遍历当前使用的所有类
为什么方法和属性,在class_ro_t
和class_rw_t
各存一份?
- 可以在运行时进行更改
- 当
category
被加载时,它可以向类中添加新的方法 - 使用
Runtime API
动态添加属性和方法 - 因为
class_ro_t
是只读的,所以需要在class_rw_t
中来跟踪这些东西
class_rw_t
结构在设备中,占用很多的内存,那么我们如何去缩小这些结构呢?
在实践中发现,大约只有10%的类,真正的更改了它们的方法
所有,我们可以将不常用的部分进行拆分
class_rw_t
的大小可减少一半
对于确实需要额外信息的类,我们可以分配这些扩展记录中的一个,并把它滑到类中供其使用
如果原来的代码直接访问class_rw_t
结构,由于结构内存布局发生了变化,可能产生崩溃。苹果推荐使用Runtime API
,这样底层的细节会由系统处理
2. 成员变量 & 实例变量 & 属性
2.1 成员变量与属性的区别
- 成员变量:在底层只是变量的声明
- 属性:系统会自动在底层添加
_
属性名变量,同时生成setter
和getter
方法
2.2 成员变量与实例变量的区别
- 实例变量是一种特殊的成员变量
- 成员变量为基本数据类型
- 实例变量为对象类型,例如:
NSObject
类型 NSString
为常量类型,属于成员变量
2.3 属性的底层实现
打开main.m
文件,写入以下代码:
#import <Foundation/Foundation.h>
@interface LGPerson : NSObject{
NSString *desc;
NSObject *obj;
}
@property (strong,nonatomic) NSString *name;
@end
@implementation LGPerson
@end
生成cpp
文件
clang -rewrite-objc main.m -o main.cpp
打开main.cpp
文件,查看属性的底层实现
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *desc;
NSObject *obj;
NSString *_name;
};
// @property (strong,nonatomic) NSString *name;
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; }
- 在底层代码中,属性被优化掉
- 生成
_
开头的成员变量 +getter
/setter
方法
3. 编码
底层的方法列表中,生成的编码含义是什么?
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[4];
} _OBJC_$_INSTANCE_METHODS_LGPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
4,
{{(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 *)"name", "@16@0:8", (void *)_I_LGPerson_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LGPerson_setName_}}
};
3.1 Code
对照表
编码中Code
与数据类型的对照表
- 可查看官方文档:Type Encodings
3.2 编码的含义
编码中,除了Code
之外,还有数字,它们组合在一起的含义是什么?
案例1
name
属性的getter
方法
NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd)
-------------------------
@16@0:8
@16
:@
表示返回值类型为id
类型,16
表示方法所占用的内存为16字节
@0
:@
表示第一个参数类型为id
类型,0
表示参数1
存储的起始位置,占8字节
:8
::
表示第二个参数类型为SEL
类型,8
表示参数2
存储的起始位置,占8字节
- 参数分别为
objc_msgSend
的两个隐含参数,id
类型的self
,SEL
类型的_cmd
案例2
name
属性的setter
方法
void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name)
-------------------------
v24@0:8@16
v24
:v
表示返回值类型为void
类型,24
表示方法所占用的内存为24字节
@0
:@
表示第一个参数类型为id
类型,0
表示参数1
存储的起始位置,占8字节
:8
::
表示第二个参数类型为SEL
类型,8
表示参数2
存储的起始位置,占8字节
@16
:@
表示第三个参数类型为id
类型,16
表示参数3
存储的起始位置,占8字节
3.3 使用代码查看类型编码
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)));
}
-------------------------
//输出结果:
char --> c
int --> i
short --> s
long --> q
long long --> q
unsigned char --> C
unsigned int --> I
unsigned short --> S
unsigned long --> Q
float --> f
bool --> B
void --> v
char * --> *
id --> @
Class --> #
SEL --> :
int[] --> [3i]
struct --> {person=*i}
union --> (union_type=*i)
int[] --> ^i
@encode
:获取指定类型的编码字符串
扩展内容
为什么底层生成属性的getter
/setter
方法有同样的两份?
使用MachOView
查看MachO
文件
- 以
setName:
为例:在符号表中,同样的方法,因符号的功能及种类不同,出现了两份
使用objdump
命令,按符号功能查看
objdump --macho --syms KCObjcBuild | grep setName:
-------------------------
0000000100003d20 l F __TEXT,__text -[LGPerson setName:]
0000000100003d20 l d *UND* -[LGPerson setName:]
F
:表示Function
d
:表示Debug
使用nm
命名,按符号种类查看
nm -pa KCObjcBuild | grep setName:
-------------------------
0000000100003d20 t -[LGPerson setName:]
0000000100003d20 - 01 0000 FUN -[LGPerson setName:]
t
:表示符号存储在代码段(__TEXT.__text
),小写表示符号为本地符号(local
)-
:表示调试符号
4. setter方法的底层实现
4.1 准备工作
打开main.m
文件,写入以下代码:
#import <Foundation/Foundation.h>
@interface LGPerson : NSObject
@property (strong,nonatomic) NSString *name;
@property (copy,nonatomic) NSString *nick;
@end
@implementation LGPerson
@end
生成cpp
文件
clang -rewrite-objc main.m -o main.cpp
4.2 底层实现的差异
在OC
中,使用不同修饰符定义属性,例如:strong
和copy
。会导致setter
方法在底层实现的差异
name
属性的setter
方法
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)) = name; }
- 使用内存平移方式
nick
属性的setter
方法
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_LGPerson_setNick_(LGPerson * self, SEL _cmd, NSString *nick) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _nick), (id)nick, 0, 1); }
- 调用
objc_setProperty
函数,相当于所有属性setter
方法的抽取封装,因为setter
方法的本质都是修改内存中的数据
找到setter
方法和objc_setProperty
在底层的关联时机:
方法在编译阶段已经确定函数地址
所以setter
方法和objc_setProperty
的关联,在编译时期由llvm
完成
4.3 探索llvm
源码
打开llvm-project
源码
搜索objc_setProperty
关键字
打开CGObjCMac.cpp
文件
4.3.1 getSetPropertyFn
函数
- 找到
objc_setProperty
函数的创建 - 由下至上反推,找出函数的创建时机
搜索getSetPropertyFn
关键字
4.3.2 GetPropertySetFunction
函数
- 找到中间层的调用,继续向上层反推
搜索GetPropertySetFunction
关键字
打开CGObjC.cpp
文件
4.3.3 generateObjCSetterBody
函数
- 当策略为
GetSetProperty
或SetPropertyAndExpressionGet
时,有可能触发GetPropertySetFunction
函数
判断strategy.getKind()
的策略值
搜索GetSetProperty
关键字
4.3.4 PropertyImplStrategy
函数
- 使用
copy
修饰,Kind
设置GetSetProperty
4.4 探索objc
源码
找到objc_setProperty
的函数实现
打开objc-accessors.mm
文件
4.4.1 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);
}
- 调用
reallySetProperty
函数
4.4.2 reallySetProperty
函数
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);
}
- 底层触发内存拷贝的逻辑,和
strong
使用内存平移赋值是有很大差异的
总结:
objc_setProperty
的函数实现,在objc4-818.2
源码中
5. Runtime API
5.1 class_copyMethodList
class_copyMethodList
:获取类的实例方法列表
5.1.1 获取方法列表
封装objc_copyMethodList
方法
void objc_copyMethodList(Class pClass){
unsigned int count = 0;
Method *methods = class_copyMethodList(pClass, &count);
for (unsigned int i=0; i < count; i++) {
Method const method = methods[i];
NSString *key = NSStringFromSelector(method_getName(method));
NSLog(@"Method:%@", key);
}
free(methods);
}
打印类的方法列表
objc_copyMethodList(LGPerson.class);
-------------------------
Method:sayNB
Method:nick
Method:setNick:
Method:init
Method:name
Method:.cxx_destruct
Method:setName:
打印元类的方法列表
objc_copyMethodList(objc_getMetaClass(class_getName(LGPerson.class)));
-------------------------
Method:good
5.2 class_getInstanceMethod
class_getInstanceMethod
:获取指定类中的实例方法
5.2.1 验证实例方法
封装instanceMethod_classToMetaclass
函数
void instanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(sayNB));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayNB));
NSLog(@"类的sayNB实例方法:%p", method1);
NSLog(@"元类的sayNB实例方法:%p", method2);
Method method3 = class_getInstanceMethod(pClass, @selector(good));
Method method4 = class_getInstanceMethod(metaClass, @selector(good));
NSLog(@"类的good实例方法:%p", method3);
NSLog(@"元类的good实例方法:%p", method4);
}
传入LGPerson
类对象
instanceMethod_classToMetaclass(LGPerson.class);
-------------------------
类的sayNB实例方法:0x1000081d0
元类的sayNB实例方法:0x0
类的good实例方法:0x0
元类的good实例方法:0x100008168
- 类中存储了
sayNB
实例方法 - 元类中存储了
good
实例方法
总结:
- 在
OC
底层,所有方法都是实例方法 - 类方法,元类中存储的实例方法
5.3 class_getClassMethod
class_getClassMethod
:获取指定类中的类方法
5.3.1 验证类方法
封装classMethod_classToMetaclass
函数
void classMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(sayNB));
Method method2 = class_getClassMethod(metaClass, @selector(sayNB));
NSLog(@"类的sayNB类方法:%p", method1);
NSLog(@"元类的sayNB类方法:%p", method2);
Method method3 = class_getClassMethod(pClass, @selector(good));
Method method4 = class_getClassMethod(metaClass, @selector(good));
NSLog(@"类的good类方法:%p", method3);
NSLog(@"元类的good类方法:%p", method4);
}
传入LGPerson
类对象
classMethod_classToMetaclass(LGPerson.class);
-------------------------
类的sayNB类方法:0x0
元类的sayNB类方法:0x0
类的good类方法:0x100008158
元类的good类方法:0x100008158
- 类中存储了
good
类方法 - 元类中存储了
good
类方法
疑问:
- 为什么元类中存储了
good
类方法?
5.3.2 探索objc
源码
找到class_getClassMethod
函数
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
- 调用
class_getInstanceMethod
函数 - 本质:查找当前类对象所属元类中的实例方法
找到getMeta
函数
Class getMeta() {
if (isMetaClassMaybeUnrealized()) return (Class)this;
else return this->ISA();
}
- 传入的类对象是元类,返回自己
所以,元类中存储good
类方法,其实找到的还是元类中的good
实例方法
5.4 class_getMethodImplementation
class_getMethodImplementation
:获取指定方法的函数实现地址
5.4.1 SEL
& IMP
SEL
与IMP
的关系
SEL
:书的⽬录名称(⽅法编号)IMP
: ⽬录的⻚码(函数指针地址)
查找流程:
- ⾸先明⽩我们要找到书本的什么内容(
SEL
⽬录⾥⾯的名称) - 通过名称找到对应的⻚码(通过
SEL
找IMP
) - 通过⻚码去定位具体的内容(通过
IMP
找到函数实现)
5.4.2 验证方法IMP
封装imp_classToMetaclass
函数
void imp_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
IMP imp1 = class_getMethodImplementation(pClass, @selector(sayNB));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayNB));
NSLog(@"类的sayNB方法的IMP:%p", imp1);
NSLog(@"元类的sayNB方法的IMP:%p", imp2);
IMP imp3 = class_getMethodImplementation(pClass, @selector(good));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(good));
NSLog(@"类的good方法的IMP:%p", imp3);
NSLog(@"元类的good方法的IMP:%p", imp4);
}
传入LGPerson
类对象
imp_classToMetaclass(pClass);
-------------------------
类的sayNB方法的IMP:0x100003aa0
元类的sayNB方法的IMP:0x7fff202405c0
类的good方法的IMP:0x7fff202405c0
元类的good方法的IMP:0x100003ab0
疑问:
- 元类中没有
sayNB
实例方法,为什么存在方法IMP
? - 类中没有
good
类方法,为什么存在方法IMP
? - 元类中
sayNB
和类中good
,两个方法IMP
为什么相同?
5.4.3 探索objc
源码
找到class_getMethodImplementation
函数
__attribute__((flatten))
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
lockdebug_assert_no_locks_locked_except({ &loadMethodLock });
imp = lookUpImpOrNilTryCache(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}
- 如果找不到方法
IMP
,返回_objc_msgForward
函数地址
找到_objc_msgForward
的定义
OBJC_EXPORT void
_objc_msgForward(void /* id receiver, SEL sel, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
Runtime
的消息转发机制
所以,当方法IMP
不存在时,触发Runtime
的消息转发机制,返回_objc_msgForward
函数地址,所以IMP
地址相同
6. isKindOfClass
6.1 方法的作用
打开objc4-818.2
源码
查看汇编代码
- 无论调用类对象还是实例对象的
isKindOfClass
方法,入口函数统一为objc_opt_isKindOfClass
找到objc_opt_isKindOfClass
函数
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
- 获取对象isa指向的类,和传入的
Class
对比 - 遍历对象isa指向类的父类,和传入的
Class
对比
isKindOfClass
方法的作用:
- 类对象
◦ 元类 vs Class
◦ 遍历:元类的父类 vs Class
- 实例对象
◦ 类 vs Class
◦ 遍历:类的父类 vs Class
6.2 验证类对象与实例对象
封装isKindOfClass
函数
void isKindOfClassDemo(){
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];
NSLog(@"NSObject类对象:%hhd", re1);
NSLog(@"LGPerson类对象:%hhd", re2);
BOOL re3 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL re4 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];
NSLog(@"NSObject实例对象:%hhd", re3);
NSLog(@"LGPerson实例对象:%hhd", re4);
}
调用isKindOfClassDemo
函数
isKindOfClassDemo();
-------------------------
NSObject类对象:1
LGPerson类对象:0
NSObject实例对象:1
LGPerson实例对象:1
7. isMemberOfClass
7.1 方法的作用
打开objc4-818.2
源码
查看汇编代码
- 调用指定对象的
objc_msgSend
函数
找到isMemberOfClass
方法
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
- 类方法:获取类的元类,和传入的
cls
对比 - 实例方法:获取对象所属的类,和传入的
cls
对比
7.2 验证类对象与实例对象
封装isMemberOfClassDemo
方法
void isMemberOfClassDemo(){
BOOL re1 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re2 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];
NSLog(@"NSObject类对象:%hhd", re1);
NSLog(@"LGPerson类对象:%hhd", re2);
BOOL re3 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL re4 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];
NSLog(@"NSObject实例对象:%hhd", re3);
NSLog(@"LGPerson实例对象:%hhd", re4);
}
调用isMemberOfClassDemo
函数
isMemberOfClassDemo();
-------------------------
NSObject类对象:0
LGPerson类对象:0
NSObject实例对象:1
LGPerson实例对象:1
总结
WWDC 2020
-Runtime
优化
- 类结构的优化
- 方法列表的优化
Tagged Pointer
格式的优化
WWDC
-类结构的优化
Clean Memory
◦ 加载后不会再改变的内存
◦ class_ro_t
是只读的,它就属于Clean Memory
◦ 可移除,从而节省更多内存空间。如果你有需要,系统可以从磁盘中重新加载
Dirty Memory
◦ 进程运行时会发生变化的内存
◦ 类结构体一旦被使用就是Dirty Memory
,因为运行时会写入新的数据,例如:方法缓存
◦ Dirty Memory
是类数据被分为两部分的原因
- 方法和属性在
class_ro_t
和class_rw_t
各存一份
◦ 可以在运行时进行更改
◦ 当category
被加载时,它可以向类中添加新的方法
◦ 用Runtime API
动态添加属性和方法
◦ 因为class_ro_t
是只读的,所以需要在class_rw_t
中来跟踪这些东西
- 优化
class_rw_t
空间
◦ 拆分class_rw_ext_t
,存储方法、属性、协议等不常用的部分
成员变量与属性的区别
- 成员变量:在底层只是变量的声明
- 属性:系统会自动在底层添加
_
属性名变量,同时生成setter
和getter
方法
成员变量与实例变量的区别
- 实例变量是一种特殊的成员变量
- 成员变量为基本数据类型
- 实例变量为对象类型,例如:
NSObject
类型 NSString
为常量类型,属于成员变量
属性的底层实现
- 在底层代码中,属性被优化掉
- 生成
_
开头的成员变量 +getter
/setter
方法
编码
Code
对照表:Type Encodings@encode
:获取指定类型的编码字符串
setter
方法的底层实现
- 在
OC
中,使用不同修饰符定义属性,例如:strong
和copy
。会导致setter
方法在底层的实现产生差异
◦ strong
:内存平移
◦ copy
:调用objc_setProperty
函数
objc_setProperty
◦ setter
方法和objc_setProperty
的关联,在编译时期由llvm
完成
◦ objc_setProperty
的函数实现,在objc4-818.2
源码中
◦ 底层触发内存拷贝的逻辑,和strong
使用内存平移赋值是有很大差异的
SEL
与IMP
的关系
SEL
:书的⽬录名称(⽅法编号)IMP
: ⽬录的⻚码(函数指针地址)
Runtime API
class_copyMethodList
:获取类的实例方法列表class_getInstanceMethod
:获取指定类中的实例方法
◦ 在OC
底层,所有方法都是实例方法
◦ 类方法,元类中存储的实例方法
class_getClassMethod
:获取指定类中的类方法
◦ 本质:查找当前类对象所属元类中的实例方法
◦ 传入getMetah
函数的类对象是元类,返回自己
class_getMethodImplementation
:获取指定方法的函数实现地址
◦ 当方法IMP
不存在时,触发Runtime
的消息转发机制,返回_objc_msgForward
函数地址
isKindOfClass
- 类对象
◦ 元类 vs Class
◦ 遍历:元类的父类 vs Class
- 实例对象
◦ 类 vs Class
◦ 遍历:类的父类 vs Class
isMemberOfClass
- 类方法:获取类的元类,和传入的
cls
对比 - 实例方法:获取对象所属的类,和传入的
cls
对比