首先我们来创建一个类,然后再创建这个类的一个对象
创建一个父类Person
@interface Person : NSObject
{
int _age;
int _no;
int _height;
}
-(void)personInstanceMethod;
+(void)personClassMethod;
@end
@implementation Person
- (void)personInstanceMethod{
}
+ (void)personClassMethod{
}
@end
创建Person
的子类Student
,为什么要这样创建,方便后面讲清楚对象与类,类与父类,类与元类之间的关系
@interface Student : Person
{
int _sex;
}
-(void)studentInstanceMethod;
+(void)studentClassMethod;
@end
@implementation Student
- (void)studentInstanceMethod{
}
+ (void)studentClassMethod{
}
@end
然后我们创建一个Student的对象
Student *student = [[Student alloc]init];
[student personInstanceMethod];
在OC中每个对象都是一个结构体,结构体中都包含一个isa
的成员变量,其位于成员变量的第一位。isa
的成员变量之前都是Class类型的,后来苹果将其改为isa_t
。
struct objc_object {
private:
isa_t isa;
};
上面是对象的结构体,你们发现里面只有一个isa
指针,下面我们来看看类的结构体
查看网上相关资料,我们会发现出现了两种结构体
第一种
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 *` */
但是看里面的相关代码会发现有个前置条件#if !__OBJC2__
,而我们现在基本上用的都是objective-c 2.0
,所以上面的结构体只能反映以前的结构
下面是新的结构体
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags 获取具体的类信息
class_rw_t *data() {
return bits.data();
}
}
注意,objc_class
是继承于objc_object
的,因此objc_class
中也包含isa_t
类型的isa
。objc_class
的定义可以理解成下面这样:
struct objc_class {
isa_t isa;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
对象与类,类与父类,类与元类之间的关系
了解完内部结构后,我们就可以进一步来阐述对象与类,类与父类,类与元类之间的关系,其中元类和类采用的是同一种数据结构
其中isa
必须指向一个地方,而superclass
当没有父类的时候可以为nil
对象里面的
isa
指向自己的类,类里面的superclass
指向自己的父类,isa
指向元类,通过上面这张图,再来理解下面这张随处可见的图,就容易理解多了,到此,我们对象与类,类与父类,类与元类的关系就说清楚了
类中的实例方法、类方法和属性的结构
在objc_class
中,我们主要研究class_rw_t
,rw_t
是read_write_table
的缩写,意思是可读可写的表。bits.data()
返回class_rw_t
,class_rw_t
里面存放的什么信息呢?我们点击进入class_rw_t
看看:
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;//方法信息
property_array_t properties;//属性信息
protocol_array_t protocols;//协议信息
}
可以看到我们想看的方法,属性,协议信息.只是还没看到成员变量信息. class_rw_t
中有一个class_ro_t
,ro_t
是readOnly_table
的缩写,意思是只读的表.我们点击进入const class_ro_t
看看里面存放哪些信息:
struct class_ro_t {
const char * name;//类名
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;//成员变量
}
下面一张图,能一目了然展示Class
的内存结构:
元类的方法列表中主要是类方法的信息
结合上面的结构,再讲讲面试中经常被问到的东西
SEL
:SEL(选择器)
是方法的selector
的指针。方法的selector
表示运行时方法的名字。OC在编译时,会依据每一个方法的名字、参数,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL
。
IMP
:IMP
是一个函数指针,指向方法最终实现的首地址。SEL
就是为了查找方法的最终实现IMP
。
Method
:用于表示类定义中的方法,它的结构体中包含一个SEL
和IMP
,相当于在SEL
和IMP
之间作了一个映射。
消息机制:任何方法的调用本质就是发送一个消息。编译器会将消息表达式
[receiver message]
转化为一个消息函数objc_msgSend(receiver, selector)
。
objc_msgSend
做了如下事情:
- 通过对象的isa指针获取类的结构体。
- 在结构体的方法表里查找方法的selector。
- 如果没有找到selector,则通过objc_msgSend结构体中指向父类的指针找到父类,并在父类的方法表里查找方法的selector。
- 依次会一直找到NSObject。
- 一旦找到selector,就会获取到方法实现IMP。
- 传入相应的参数来执行方法的具体实现。
- 如果最终没有定位到selector,就会走消息转发流程
Runtime的使用:获取属性列表,获取成员变量列表,获得方法列表,获取协议列表,方法交换(黑魔法),动态的添加方法,调用私有方法,为分类添加属性。
1.什么是isa
指针?
isa
指针的作用:当我们向一个对象发送消息时,runtime
会根据这个对象的isa
指针找到这个对象所属的类,在这个类的方法列表及父类的方法列表中,寻找与消息对应的selector
指向的方法,找到后就运行这个方法。
2.是如何找到IMP
的?
在寻找IMP
的地址时,runtime
提供了两种方法
IMP class_getMethodImplementation(Class cls, SEL name);
IMP method_getImplementation(Method m)
而根据官方描述,第一种方法可能会更快一些
对于第一种方法而言,类方法和实例方法实际上都是通过调用class_getMethodImplementation()
来寻找IMP
地址的,不同之处在于传入的第一个参数不同类方法(假设有一个类A)
class_getMethodImplementation(objc_getMetaClass("A"),@selector(methodName));
类方法当中传入的是元类
实例方法
class_getMethodImplementation([A class],@selector(methodName));
而对于第二种方法而言,传入的参数只有Method
,区分类方法和实例方法在于封装Method
的函数
类方法
Method class_getClassMethod(Class cls, SEL name)
实例方法
Method class_getInstanceMethod(Class cls, SEL name)
最后调用
IMP method_getImplementation(Method m)
获取IMP地址
3.消息转发机制
当最后我们要调用的方法找不到时,通常我们会报一个“没有此方法的错误”,在报错之前,我们实际上会有三次解决该错误造成闪退的机会
当对象无法接收消息,就会启动消息转发机制,通过这一机制,告诉对象如何处理未知的消息。
①动态方法解析
对象接收到未知的消息时,首先会调用所属类的方法
(实例方法)
+resolveInstanceMethod:
或 者
(类方法)
+resolveClassMethod:
在这个方法中,我们有机会为该未知消息新增一个”处理方法”。使用该“处理方法”的前提是已经实现,只需要在运行时通过class_addMethod
函数,动态的添加到类里面就可以了。
②备用接收者
如果在上一步无法处理消息,则Runtime
会继续调下面的方法。
- (id)forwardingTargetForSelector:(SEL)aSelector
如果这个方法返回一个对象,则这个对象会作为消息的新接收者。
注意这个对象不能是self自身,否则就是出现无限循环。如果没有指定对象来处理aSelector
,则应该
return [super forwardingTargetForSelector:aSelector]。
但是我们只将消息转发到另一个能处理该消息的对象上,无法对消息进行处理,例如操作消息的参数和返回值。
③完整消息转发
如果在上一步还是不能处理未知消息,则唯一能做的就是启用完整的消息转发机制。此时会调用以下方法:
- (id)forwardInvocation:(NSInvocation *)anInvocation
这是最后一次机会将消息转发给其它对象。创建一个表示消息的NSInvocation
对象,把与消息的有关全部细节封装在anInvocation
中,包括selector
,目标(target)
和参数。在forwardInvocation
方法中将消息转发给其它对象。
forwardInvocation:
方法的实现有两个任务:
a. 定位可以响应封装在
anInvocation
中的消息的对象。b. 使用
anInvocation
作为参数,将消息发送到选中的对象。anInvocation
将会保留调用结果,runtime
会提取这一结果并发送到消息的原始发送者。
在这个方法中我们可以实现一些更复杂的功能,我们可以对消息的内容进行修改。另外,若发现消息不应由本类处理,则应调用父类的同名方法,以便继承体系中的每个类都有机会处理。
另外,必须重写下面的方法:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
消息转发机制从这个方法中获取信息来创建NSInvocation对象。
NSObject
的forwardInvocation
方法只是调用了doesNotRecognizeSelector
方法,它不会转发任何消息。如果不在以上所述的三个步骤中处理未知消息,则会引发异常。这里为什么提到NSObject
,因为NSObject
是最后的父类,一直找不到就会找到NSObject
中。
forwardInvocation
就像一个未知消息的分发中心,将这些未知的消息转发给其它对象。或者也可以像一个运输站一样将所有未知消息都发送给同一个接收对象,取决于具体的实现。
消息的转发机制可以用下图来帮助理解。
4.isa
结构体内容
isa_t
定义
isa_t
是一个union
的结构对象,union
类似于C++
结构体,其内部可以定义成员变量和函数。
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1; // 是32位还是64位
uintptr_t has_assoc : 1; // 对象是否含有或曾经含有关联引用,如果没有关联引用,可以更快的释放对象
uintptr_t has_cxx_dtor : 1; // 表示是否有C++析构函数或OC的析构函数
uintptr_t shiftcls : 33; // 对象指向类的内存地址,也就是isa指向的地址
uintptr_t magic : 6; // 对象是否初始化完成
uintptr_t weakly_referenced : 1; // 对象是否被弱引用或曾经被弱引用
uintptr_t deallocating : 1; // 对象是否被释放中
uintptr_t has_sidetable_rc : 1; // 对象引用计数太大,是否超出存储区域
uintptr_t extra_rc : 19; // 对象引用计数
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
# elif __x86_64__
// ····
# else
// ····
# endif
};
具体例子参考
runtime运行时 isa指针 SEL方法选择器 IMP函数指针 Method方法 runtime消息机制 runtime的使用
内容有部分参考:
OC对象的底层结构及isa、superClass详解
探秘Runtime - 剖析Runtime结构体