一、问题
1. 对象如何找到对应的方法去调用
根据对象的isa去对应的类查找方法,isa:判断去哪个类查找对应的方法 指向方法调用的类 。 根据传入的方法编号SEL,里面有个哈希列表,在列表中找到对应方法Method(方法名) 。 根据方法名(函数入口)找到函数实现,函数实现在方法区。
2. SEL、isa、super、cmd
-
SEL:
- 一种类型,表示方法名称,类似字符串(可互转)
-
IMP:
- 定义为 id (*IMP) (id, SEL, …)。
- 这样说来, IMP是一个
指向函数的指针,这个被指向的函数包括id(“self”指针),调用的SEL(方法名),再加上一些其他参数.说白了IMP就是实现方法
-
ISA:
- 在方法底层对应的objc_msgSend调用时,会根据isa找到对象所在的类对象,类对象中包含了调度表(dispatch table),该表将类的sel和方法的实际内存地址关联起来
-
super_class:
- 每一个类中还包含了一个super_class指针,用来指向父类对象。
-
cmd:
- 在Objective-C的方法中表示当前方法的selector,正如同self表示当前方法调用的对象实例
3. 能否向运行时创建的类中添加实例变量?
不能
-
因为编译后的类已经注册在runtime中,类结构体中的
objc_ivar_list实例变量的链表和instance_size实例变量的内存大小已经确定, -
运行时创建的类是可以添加实例变量,调用 class_addIvar函数,但是得在调用
objc_allocateClassPair之后,objc_registerClassPair之前,原因同上。 -
源码如下:
Class newClass = objc_allocateClassPair( [NSError class], "RuntimeErrorSubclass", 0 );
class_addMethod(newClass, @selector(report), (IMP) ReportFunction, "v@:");
objc_registerClassPair(newClass);
4. class方法和objc_getClass方法有什么区别?
-
当参数obj为Object实例对象 object_getClass(obj)与[obj class]输出结果一直,均获得isa指针,即指向类对象的指针。
-
当参数obj为Class类对象 object_getClass(obj)返回类对象中的isa指针,即指向元类对象的指针;[obj class]返回的则是其本身。
-
当参数obj为Metaclass类对象 object_getClass(obj)返回元类对象中的isa指针,因为元类对象的isa指针指向根类,所有返回的是根类对象的地址指针;[obj class]返回的则是其本身。
-
obj为Rootclass类对象 object_getClass(obj)返回根类对象中的isa指针,因为跟类对象的isa指针指向Rootclass‘s metaclass(根元类),即返回的是根元类的地址指针;[obj class]返回的则是其本身。
总结:
- 经上面初步的探索得知,object_getClass(obj)返回的是obj中的isa指针;而[obj class]则分两种情况:一是当obj为实例对象时,[obj class]中class是实例方法:- (Class)class,返回的obj对象中的isa指针;二是当obj为类对象(包括元类和根类以及根元类)时,调用的是类方法:+ (Class)class,返回的结果为其本身。
5. runtime 中,SEL和IMP的区别?
每个类对象都有一个方法列表,方法列表存储方法名、方法实现、参数类型,SEL是方法名(编号),IMP指向方法实现的首地址
6. iOS中isKindOfClass和isMemberOfClass 底层
7. 为什么要设计metaclass
-
元类的存在巧妙的简化了实例方法和类方法的调用流程,大大提升了消息发送的效率。
-
通过元类就可以,让各类各司其职,实例对象就干存储属性值的事,类对象存储实例方法列表,元类对象存储类方法列表。
8. _objc_msgForward 函数是做什么的,直接调用它将会发生什么?
_objc_msgForward 是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward 会尝试做消息转发。
详解:_objc_msgForward 在进行消息转发的过程中会涉及以下这几个方法:
- resolveInstanceMethod:方法 (或resolveClassMethod:)。
- forwardingTargetForSelector:方法
- methodSignatureForSelector:方法
- forwardInvocation:方法
doesNotRecognizeSelector: 方法
9. 讲一下 OC 的消息机制
-
OC中的方法调用其实都是转成了objc_msgSend 函数的调用,给 receiver(方法调用者)发送了一条消息(selector方法名)
-
objc_msgSend底层有3大阶段,消息发送(当前类、父类中查找)
- 动态方法解析(可以通过 runtime 添加方法)
- 消息转发(指定一个对象实现)
- 完整消息转发(可以做任何事)
10. IMP、SEL、Method的区别和使用场景
Method:是objc_method类型指针,它是一个结构体,包含 IMP,SEL。
- IMP:是方法的实现,即:一段c函数。
- SEL:是方法名。
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
11. class、objc_getClass、object_getclass 方法有什么区别?
+ (Class)class {
return self; 本身
}
- (Class)class {
return object_getClass(self) 类对象;
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa(); ISA 指向
else return Nil;
}
二、Runtime 知识
1. 什么是 Runtime
OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行,OC的动态性就是由Runtime来支撑和实现的。 Runtime 基本是用 C/C++和 汇编写的一套 API,封装了很多动态性相关的函数平时编写的OC代码,底层都是转换成了Runtime API进行调用。 runtime 的源码是开源的,源码在:opensource.apple.com/tarballs/ob…
2. Runtime API 平时项目哪些地方会用到
- 利用关联对象(AssociatedObject)给分类添加属性
- 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
- 交换方法实现(交换系统的方法)
- 利用消息转发机制解决方法找不到的异常问题。
- HOOK 方法进行埋点记录等。
3. 位域
4. Class 的结构
(1)class_rw_t
class_rw_t里面包含了类的:
方法列表 methods属性列表 properties协议信息 protocols只读信息 class_ro_t
methods,protocols, properties 最后会合并 class_ro_t 里面的 baseMethodList, baseProtocols, baseProperties,汇总到class_rw_t 。
(2)class_ro_t
- class_ro_t 和 class_rw_t 的主要区别是 class_ro_t
是只读的包含:成员变量 ivars类名 name方法列表 baseMethodList属性列表 baseProperties协议信息 baseProtocols
(3)method_t
- method_t是对方法\函数的封装,里面包含
函数名 name(SEL 类型)指向函数的指针 imp函数参数返回值编码 types
1. IMP
- IMP 是一个函数指针,这个被指向的函数包含一个接收消息的对象id(self 指针)
- 调用方法的选标 SEL (方法名),以及不定个数的方法参数,并返回一个id。
- 也就是说 IMP 是消息最终调用的执行代码,是方法真正的实现代码。
2. SEL
- SEL代表
方法\函数名,一般叫做选择器,底层结构跟char *类似,可以说 SEL 就是方法编号。
可以通过 @selector() 和 sel_registerName() 获得。 可以通过sel_getName()和NSStringFromSelector()转成字符串。
3. types
types 包含了函数返回值、参数编码的字符串。
3.1 Type Encoding 编码表示
三、方法缓存
-
Class内部结构中有个方法缓存
(cache_t)用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度. -
缓存查找
- objc-cache.mm
- bucket_t * cache_t::find(cache_key_t k, id receiver)
缓存查找原理
- 通过 & Mask 找到具体的 bucket 缓存。
四、objc_msgSend 消息发送
源码跟读
消息转发
消息发送流程
-
1,首先去该类的方法 cache 中查找,如果找到了就返回它;
-
2,如果没有找到,就去该类的方法列表中查找。如果在该 类的方法列表中找到了,则将 IMP 返回,并将它加入cache中缓存起来。
-
3,如果在该类的方法列表中没找到对应的 IMP,在通过该类结构中的 super_class指针 在其父类结构的方法列表中去查找,直到在某个父类的方法列表中找到对应的IMP,返回它,并加入cache中。
-
4,如果在自身以及所有父类的方法列表中都没有找到对应的 IMP,则进入下文中要讲的
动态方法解析。