- 类、类实例、id 类型的数据结构是怎样的?
- isa 指针作用?
- 调用实例方法与类方法时,runtime 做了什么?
一、类的数据结构
runtime.h 源码中可以找到类的定义,如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE;
-
Class
的定义是typedef struct objc_class *Class
-
isa
: 在Objective-C
中,类自身也是一个对象,这个对象的Class
里面也有一个isa
指针,它指向metaClass
(元类) -
super_class
:指向该类的父类,如果该类已经是最顶层的根类(如NSObjec
t或NSProxy
),则super_class
为NULL
。 -
cache
:用于缓存最近使用的方法,增加命中率。runtime
会优先去cache
中查找需要调用的方法,如果cache
中没有,才去methodLists
中查找方法。
二、类实例的数据结构
在 runtime/objc.h 源码中可以找到objc_object
的数据结构定义,如下:
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
-
isa
:在类实例中,isa
指向该对象所属的类,比如NSString
类,NSArray
类 -
id
:是一个objc_object
结构类型的指针,可以指向任意类型的对象。id
类型也体现出了 OC 语言的动态性。根据id
指向的对象的isa
可以判断出该对象所属的类
三、isa 指针作用
类的 isa
和 类实例的 isa
是有区别的。类的 isa
指向的是 metaClass
,因此在查找类方法时,会去该类的 metaClass
中查找;类实例的 isa
指向该类,查找实例方法时,会去该类的方法列表中查找。 objc_msgSend
的汇编代码中,会在返回 isa
的时候,对这两种情况做区分:GetIsaFast NORMAL // r10 = self->isa
。
怎么验证这种区分呢?在 main.m
文件中,我们改写调用类方法和实例方法的方式:
int main(int argc, char * argv[]) {
@autoreleasepool {
// 实例方法调用
IMP objectM = class_getMethodImplementation([CKTestClass class], @selector(objectMethod));
// 执行
objectM();
// 类方法调用
IMP classM = class_getMethodImplementation(objc_getMetaClass(class_getName([CKTestClass class])), @selector(classMethod));
// 执行
classM();
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
- 在调用类方法时,传递的 self 对象 是 通过
objc_getMetaClass(name)
获取到的metaClass
- 如果传进去的是
[CKTestClass class]
,那么返回的IMP
=_objc_msgForward
,执行会报错:EXC_BAD_ACCESS (code=1, address=0x2d)
再看 metaClass
- 每一个
metaClass
的isa
指针都指向基类的metaClass
(图中的NSObject的metaClass
) - 基类的
metaClass
的isa
指针指向自己,形成一个回路 - 每一个
metaClass
的super_class
指针指向它原本 Class 的 Super Class 的metaClass
。但是基类的metaClass
的 Super Class指向 NSObject Class 本身 - 最上层的类
NSObject
的super class
指向nil
实例、isa
、metaClass
的关系图:
上图虚线是 isa
指针的指向;实线是 superclass
指针的指向。
四、调用实例方法和类方法时,runtime 做了什么?
首先,我们在 main.m 中,定义一个类如下:
@interface CKTestClass : NSObject
@property (nonatomic, assign) NSInteger numbers;
- (void)objectMethod;
+ (void)classMethod;
@end
@implementation CKTestClass
- (void)objectMethod {
NSLog(@"objectMethod");
}
+ (void)classMethod {
NSLog(@"classMethod");
}
@end
int main(int argc, char * argv[]) {
@autoreleasepool {
// alloc :类方法,init :实例方法
CKTestClass *aObject = [[CKTestClass alloc] init];
// 实例方法调用
[aObject objectMethod];
// 类方法调用
[CKTestClass classMethod];
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
然后,把这段代码转换为编译后的 c++ 伪代码。切换到 main.m
目录,执行命令:
/*系统的版本号不固定,主要看你模拟器中有哪些系统版本*/
xcrun -sdk iphonesimulator11.1 clang -rewrite-objc main.m
会在该目录下生成一个 main.cpp
文件,在该文件中找到 mian 函数:
// @property (nonatomic, assign) NSInteger numbers;
// - (void)objectMethod;
// + (void)classMethod;
/* @end */
// @implementation CKTestClass
// 实例方法 objectMethod 的定义
static void _I_CKTestClass_objectMethod(CKTestClass * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_c9_53fyy4bn4t700cbzpv02l9yw0000gn_T_main_8eb4b7_mi_0);
}
// 类方法 classMethod 的定义
static void _C_CKTestClass_classMethod(Class self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_c9_53fyy4bn4t700cbzpv02l9yw0000gn_T_main_8eb4b7_mi_1);
}
// 生成属性 numbers 的 get 、set 方法
static NSInteger _I_CKTestClass_numbers(CKTestClass * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_CKTestClass$_numbers)); }
static void _I_CKTestClass_setNumbers_(CKTestClass * self, SEL _cmd, NSInteger numbers) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_CKTestClass$_numbers)) = numbers; }
// @end
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// 生成 CKTestClass 的对象 aObject
CKTestClass *aObject = ((CKTestClass *(*)(id, SEL))(void *)objc_msgSend)((id)((CKTestClass *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CKTestClass"), sel_registerName("alloc")), sel_registerName("init"));
// 调用 实例方法
((void (*)(id, SEL))(void *)objc_msgSend)((id)aObject, sel_registerName("objectMethod"));
// 调用 类方法
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CKTestClass"), sel_registerName("classMethod"));
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
}
在 main 函数中,可以发现,实例方法和类方法的调用,都指向了 id objc_msgSend ( id self, SEL op, ... )
objc_msgSend
看一下它的前两个参数
id self
前面介绍过 id
是 objc_object
类型。objc_object
结构体包含 isa
指针,它可以找到参数 self
所属的类
SEL op
类型 SEL
可以理解为是一个方法名,可以通过方法选择器 @selector()
生成。有了这两个参数,objc_msgSend
可以通过 isa
找到self
所属的类,然后在该类的方法列表中找 SEL
objc_msgSend
用汇编实现的,至于原因,可以访问 为什么objc_msgSend必须要用汇编实现。大概是因为方法 objc_msgSend
会返回不同类型的值,比如:
/*
receiver 是 array,selector 是 @selector(count)和 @selector(objectAtIndex:); 还有个参数 6
*/
NSUInteger n = [array count];
id obj = [array objectAtIndex:6];
// clang 后大概是
NSUInteger n = (NSUInteger (*)(id, SEL))objc_msgSend(array, @selector(count));
id obj = (id (*)(id, SEL, NSUInteger))objc_msgSend(array, @selector(objectAtIndex:), 6);
好了,问题来了, 对于上面两个方法的调用,objc_msgSend
应该返回什么类型?NSUInteger
? id
?使用汇编实现,就是为了解决这个问题。
在文件 runtime/Messengers.subproj/objc-msg-x86_64.s 中可以找到 objc_msgSend
的汇编实现:
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
MESSENGER_START
// 检测(id self)是否是 nil,如果 !self, return nil
NilTest NORMAL
// 快速找到 isa
GetIsaFast NORMAL // r10 = self->isa
// 拿到 isa后,检查缓存中是否已经缓存了方法,如果缓存了就调用并返回
CacheLookup NORMAL, CALL // calls IMP on success
NilTestReturnZero NORMAL
GetIsaSupport NORMAL
// cache miss: go search the method lists
// 如果缓存里没有,那就跳到 LCacheMiss 标签
LCacheMiss:
// isa still in r10
MESSENGER_END_SLOW
jmp __objc_msgSend_uncached
END_ENTRY _objc_msgSend
objc_msgSend
传递消息的流程梳理如下:
- 检测
(id self)
是否是nil
,如果!self
,return nil
- 快速找到
isa
- 拿到
isa
后,检查缓存中是否已经缓存了方法,如果缓存了就调用并返回 - 如果缓存里没有,就跳到
LCacheMiss
,这里面会执行__objc_msgSend_uncached
:- 在该类的方法列表中查找,找到了就加入到缓存并返回
IMP
- 否则向父类的 Class 查找,重复前面的操作
- 如果一直查找到根类仍旧没有实现。则会进入
__objc_msgForward
或者__objc_msgForward_stret
; 分别对应的返回是:__objc_forward_handler
和__objc_forward_stret_handler
。下面是_objc_forward_handler
的默认实现,直接返回错误信息:
- 在该类的方法列表中查找,找到了就加入到缓存并返回
__attribute__((noreturn)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
Reference :
网友评论