运行时是OC底层的一套C语言的API,编译器最终都会将OC代码转化为运行时代码,通过终端命令编译.m 文件:clang -rewrite-objc xxx.m可以看到编译后的xxx.cpp(C++文件)。
在OC中类本身被称为类对象,简称类。而类对象的实例被称为实例对象,简称对象。
注意:类对象和类的对象是两个东西。
OC中NSObject的定义
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
...
大家都很熟悉NSObject
。它遵守了<NSObject>
协议,定义了一些属性和方法外,还有一个Class
类型的isa
指针。
除了Class
外,还有一些id
、SEL
、Method
、IMP
等见不到底层结构的类型。但是在运行时中可以找到OC与C之间的桥梁。
typedef struct objc_object *id;
typedef struct objc_class *Class;
typedef struct objc_selector *SEL;
typedef struct objc_method *Method;
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
运行时中对象的定义
在#include <objc/objc.h>
中,对象的定义为:
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
结构体类型的对象中只有一个Class
类型的isa
指针。可见与OC中NSObject
的定义十分类似。而Class
定义为objc_class
类型的结构体指针:
//一个代表OC类的不透明类型
typedef struct objc_class *Class;
相当于给指向类objc_class
的指针起了个别名为Class
。那objc_class
又是什么呢?
运行时中类的定义
在#import <objc/runtime.h>
中类的定义为objc_class
类型的结构体:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
在该结构体中又有一个Class
类型的指针。
所以对象的isa
指针指向类对象。类对象中Class
类型的isa
指针指向了和类本身一样类型的类对象。即指向了元类。而元类中的isa
指针指向根元类,而根元类中的isa
指针指向他自己。
在类中除了有isa
指针之外,还有
-
Class _Nullable super_class
//指向父类的指针。 -
const char * _Nonnull name
//类名 -
long version
//版本 -
long info
//信息 -
long instance_size
//对象在内存中的大小 -
struct objc_ivar_list * _Nullable ivars
//类中成员变量列表 -
struct objc_method_list * _Nullable * _Nullable methodLists
//类中方法列表 -
struct objc_cache * _Nonnull cache
//缓存 -
struct objc_protocol_list * _Nullable protocols
//类所遵循的协议列表
运行时中方法的定义
//定义了objc_method类型的结构体指针Method
typedef struct objc_method *Method;
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
可见Method
由方法名、方法类型、方法实现这三个部分组成。
而只要知道了方法的名称,和方法所属的类就可以获得该方法的指针。
方法的类型编码规则可参考官方文档
通过以下方法分别获取实例方法和类方法。
Method class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name);
Method class_getClassMethod(Class _Nullable cls, SEL _Nonnull name);
如何获取类对象
在OC中不支持直接获取isa
指针。
实际上有三种方法可以获取:
//1.OC方法
Class a = [self class];
//2. object_getClass(id _Nullable obj, Class _Nonnull cls)
//传入实例对象
Class b = object_getClass(self);
//3.objc_getClass(const char * _Nonnull name)
//将OC字符串转为C字符串
const char *name = [[[self class] description] UTF8String];
id c = objc_getClass(name);
NSLog(@"类对象地址:%p %p %p",a,b,c);
这三种方法得到的是相同的类对象。
如何获取元类对象
//1.object_setClass(id _Nullable obj, Class _Nonnull cls)
Person *p = [[Person alloc] init];
Class a = object_getClass([p class]);
//2.object_setClass(id _Nullable obj, Class _Nonnull cls)
Class b = object_getClass([Person class]);
//3.objc_getMetaClass(const char * _Nonnull name)
Class c = objc_getMetaClass("Person");
NSLog(@"元类对象地址:%p %p %p",a,b,c);
方法一和方法二实际上是同一种方式。
object_getClass和objc_getClass有什么区别
- (void)demo1
{
id currentClass = self;
const char *a = object_getClassName(currentClass);
for (int i = 1; i < 7; i++) {
NSLog(@"Following the isa pointer %d times gives 1 %p---%s", i, currentClass,a);
currentClass = object_getClass(currentClass);
a = object_getClassName(currentClass);
}
}
- (void)demo2
{
id currentClass = [[Person alloc] init];
const char *a = object_getClassName(currentClass);
for (int i = 1; i < 7; i++) {
NSLog(@"Following the isa pointer %d times gives 2 %p---%s", i, currentClass,a);
currentClass = object_getClass(currentClass);
a = object_getClassName(currentClass);
}
}
- (void)demo3
{
Class currentClass = [self class];
const char *a = object_getClassName(currentClass);
for (int i = 1; i < 5; i++) {
NSLog(@"Following the isa pointer %d times gives 3 %p---%s", i, currentClass,a);
currentClass = objc_getClass([NSStringFromClass(currentClass) UTF8String]);
a = object_getClassName(currentClass);
}
}
Following the isa pointer 1 times gives 1 0x7fcf61e07060---ViewController
Following the isa pointer 2 times gives 1 0x10a0be1f0---ViewController
Following the isa pointer 3 times gives 1 0x10a0be218---NSObject
Following the isa pointer 4 times gives 1 0x10b068e58---NSObject
Following the isa pointer 5 times gives 1 0x10b068e58---NSObject
Following the isa pointer 6 times gives 1 0x10b068e58---NSObject
Following the isa pointer 1 times gives 2 0x60000020dff0---Person
Following the isa pointer 2 times gives 2 0x10a0be268---Person
Following the isa pointer 3 times gives 2 0x10a0be240---NSObject
Following the isa pointer 4 times gives 2 0x10b068e58---NSObject
Following the isa pointer 5 times gives 2 0x10b068e58---NSObject
Following the isa pointer 6 times gives 2 0x10b068e58---NSObject
Following the isa pointer 1 times gives 3 0x10a0be1f0---ViewController
Following the isa pointer 2 times gives 3 0x10a0be1f0---ViewController
Following the isa pointer 3 times gives 3 0x10a0be1f0---ViewController
Following the isa pointer 4 times gives 3 0x10a0be1f0---ViewController
对比一下不难发现:
在demo1中传入self
对象,
第一次打印了实例对象0x7fcf61e07060
,
第二次打印了类对象0x10a0be1f0
,
第三次打印了元类对象0x10a0be218
,
第四次及以后打印了根元类对象0x10b068e58
,因为根元类的isa指针指向它自己。
在demo2中同理,可见第四次及以后打印的根元类对象和demo1中的地址一样,由此可证所有的对象指向同一个根元类对象。
在demo3中,四次打印的都是0x10a0be1f0
,和demo1中的类对象地址相同。
得出以下结论:
-
object_getClass
返回的是传入对象的isa
指针所指向的类。 -
objc_getClass
只能返回类对象。 -
objc_getMetaClass
只能返回元类对象。
再奉上源码,与我们推测的是一样的。
/***********************************************************************
* object_getClass.
* Locking: None. If you add locking, tell gdb (rdar://7516456).
**********************************************************************/
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
/***********************************************************************
* objc_getClass. Return the id of the named class. If the class does
* not exist, call _objc_classLoader and then objc_classHandler, either of
* which may create a new class.
* Warning: doesn't work if aClassName is the name of a posed-for class's isa!
**********************************************************************/
Class objc_getClass(const char *aClassName)
{
if (!aClassName) return Nil;
// NO unconnected, YES class handler
return look_up_class(aClassName, NO, YES);
}
/***********************************************************************
* objc_getMetaClass. Return the id of the meta class the named class.
* Warning: doesn't work if aClassName is the name of a posed-for class's isa!
**********************************************************************/
Class objc_getMetaClass(const char *aClassName)
{
Class cls;
if (!aClassName) return Nil;
cls = objc_getClass (aClassName);
if (!cls)
{
_objc_inform ("class `%s' not linked into application", aClassName);
return Nil;
}
return cls->ISA();
}
通过运行时如何HOOK
+ (void)load
{
SEL sel_classMethod = @selector(classMethod);
SEL sel_hook_classMethod = @selector(hook_classMethod);
Class metaClass_obj = object_getClass(self);//元类对象
// Class class_obj = objc_getClass([[[self class] description] UTF8String]);//类对象
Method m_classMethod = class_getClassMethod(metaClass_obj, sel_classMethod);
Method m_hook_classMethod = class_getClassMethod(metaClass_obj, sel_hook_classMethod);
BOOL isAdd = class_addMethod(metaClass_obj,
sel_classMethod,
method_getImplementation(m_hook_classMethod),
method_getTypeEncoding(m_hook_classMethod));
if (isAdd) {
class_replaceMethod(metaClass_obj,
sel_hook_classMethod,
method_getImplementation(m_classMethod),
method_getTypeEncoding(m_classMethod));
}else{
method_exchangeImplementations(m_classMethod, m_hook_classMethod);
}
}
值得注意的点:
- class_addMethod的参数。有四个参数,后三个参数拼起来正好是
Method
的结构定义。如果方法已存在,会返回false
。 - 在类方法中
self
代表类对象,在实例方法中self
代表实例对象。object_getClass(self)
同样的写法在类方法中和在实例方法中得到的结果不一样。 - hook类方法需使用
class_getClassMethod
,参数传入元类对象。 - hook实例方法需使用
class_getInstanceMethod
,参数传入类对象。
总结:
对象方法列表是保存在类中的,如果想给类添加一个对象方法,是将该方法添加到类的方法列表中。
类方法列表是保存在元类中的,如果想要给类添加一个类方法,是将该方法添加到元类的方法列表中。