数据结构
我们主要学习四种数据结构:
objc_object
objc_class
isa指针
-
method_t
typedef struct objc_class *Class;
typedef struct objc_object *id;
- objc_object
我们平时所使用的对象都是id类型的,id类型的对象对应到 runtime
中实际上代表的就是 objc_object
的结构体,这个结构体主要包含以下几个部分
其中 isa_t
是一个共用体。
- objc_class
我们平时用到的 Class
就对应 objc_class
的数据结构,这也是一个结构体,objc_class
继承自 objc_object
。
superClass
指向它的父类,cache
表示方法缓存,bits
里主要存放定义的属性等相关信息
- isa指针
数据类型总结
类对象与元类对象
实例对象、类对象、元类对象
类对象存储实例方法列表等信息
元类对象存储类方法列表等信息
需要注意的地方:
元类对象的isa指针,都是指向根元类对象。包括根元类对象本身,也是指向根元类对象
根元类对象的superclass指针,指向的是根类对象
问:元类对象的isa指针指向哪里?
元类对象的isa指针,都是指向根元类对象。包括根元类对象本身,也是指向根元类对象
问:如果一个类方法没有实现,但是有同名的对象方法实现,会崩溃?还是会调用?
对象方法存储在类对象里面;
类方法存储在元类对象里面;
这个问题应该这样看:
如果一个类方法没有实现,根据superclass指针,会去父类里面找同名类方法,直至到根元类对象里面去查找;
如果中间找到了对应的同名类方法,则会调用。
如果中间没有找到对应的同名类方法,则根元类对象的superclass指向的是根类对象
如果根类对象里面有对应的同名实例方法(类对象(包括根类对象)只能存储实例方法),则会调用。
如果根类对象里面没有对应的同名实例方法,则根据消息传递原则,进入动态方法解析阶段。
举个例子:
@interface YZPerson : NSObject
- (void)run;
- (void)run;
@end
import "YZPerson.h"
@implementation YZPerson
//+ (void)run
//{
// NSLog(@"类方法-run");
//}
(void)run
{
NSLog(@"实例方法-run");
}
@end-
(void)viewDidLoad {
[super viewDidLoad];
[YZPerson run];
}
结果:崩溃
也就是说,person里面虽然有同名的对象方法-(void)test;,但是,不好意思,superclass指针最多也就到达根类对象里面,到达不了类对象person里面,所以,还是找不到-(void)test;
换句话说,如果类方法没有实现,只有在根类对象里面有同名实例方法,才能调用。
根类对象一般指的是NSObject,如果需要在根类对象添加,也就是给NSObject添加自定义实例方法,这就需要分类了。分类可以给系统添加方法。
@interface YZPerson : NSObject
- (void)run;
- (void)run;
@end
import "YZPerson.h"
@implementation YZPerson
//+ (void)run
//{
// NSLog(@"类方法-run");
//}
- (void)run
{
NSLog(@"实例方法-run");
}
@end
NSObject+test.h文件
@interface NSObject (test)
- (void)run;
@end
NSObject+test.m文件
import "NSObject+test.h"
@implementation NSObject (test)
- (void)run
{
NSLog(@"NSObject-实例方法-run");
}
@end
-
(void)viewDidLoad {
[super viewDidLoad];
[YZPerson run];
}
打印结果:
NSObject-实例方法-run
消息传递
消息传递
void objc_msgSend(void /* id self, SEL op, ... */ )
里面是两个默认参数,self和SEL。
[self class]经过编译器转换为objc_msgSend(self, @selector(class));
void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
里面是两个默认参数,super和SEL。
[super class]经过编译器转换为objc_msgSendSuper(super, @selector(class));
而,其中struct objc_super的数据结构是:
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
if !defined(__cplusplus) && !OBJC2
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
else
__unsafe_unretained _Nonnull Class super_class;
endif
/* super_class is the first class to search */
};
里面有句话:super_class is the first class to search
也就是,搜索方法,是从super_class开始的
objc_msgSendSuper({self, super_class}, @selector(class));
receiver是当前对象,也就是self,才是真正的消息接收者
也就是,不论是[self class]还是[super class],消息接收者都是当前对象self。
因此,上面的图片中,打印结果是:
Phone
Phone
可以看出,在调用前,会先进行缓存操作。
缓存操作有两种可能:
1.缓存到receiver的cache_t的bucket_t中
2.缓存到父类的cache_t的bucket_t中
首先,肯定是参数cls的
那么,参数cls是谁?
注释倒是有一句:
从上面代码可以看出,父类中缓存中有或者方法列表中有,则将方法缓存到原类中的cache_t的bucket_t中。
正确流程图:
1.当前缓存中查找
2.当前类中查找
3.父类缓存中查找
4.父类类中查找
1.当前缓存中查找
根据给定的值key,找到bucket_t里面的IMP
是一个hash查找
2.当前类中查找
对于已经排序好的列表,采用二分查找算法查找对应的执行函数
对于没有排序好的列表,采用一般遍历查找方法对应的执行函数
3.父类缓存中查找
4.父类中查找
消息转发
动态添加方法
动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
Method_Swizzing方法交换
void method_exchangeImplementations(Method m1, Method m2)
更多学习请参考:
iOS-探究Runtime
+(BOOL)resolveInstanceMethod:(SEL)sel;//为对象方法进行决议
+(BOOL)resolveClassMethod:(SEL)sel;//为类方法进行决议
-(id)forwardingTargetForSelector:(SEL)aSelector;//方法转发目标
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
-(void)forwardInvocation:(NSInvocation *)anInvocation;
那么最后消息未能处理的时候,还会调用到
- (void)doesNotRecognizeSelector:(SEL)aSelector这个方法,我们也可以在这个方法中做处理,避免掉crash,但是只建议在线上环境的时候做处理,实际开发过程中还要把异常抛出来
动态添加方法
动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
Method_Swizzing方法交换
void method_exchangeImplementations(Method m1, Method m2)
更多学习请参考:
iOS-探究Runtime
动态方法解析
@dynamic
这句话写上,编译器将不再生成属性值的setter和getter方法,也不会生成成员变量,而在运行时我们用到的时候再给它动态添加get方法和set方法
然后,我们可以在运行时+ (BOOL)resolveInstanceMethod:(SEL)sel方中,动态的实现setter和getter方法
在MJ课程中,动态方法解析指的是runtime中的第二个流程+ (BOOL)resolveInstanceMethod:(SEL)sel
在于海本节课程中,动态方法解析是一个具体例子@dynamic,动态实现setter和getter方法
都是指的+ (BOOL)resolveInstanceMethod:(SEL)sel的方法调用
动态运行时语言将函数决议推迟到运行时(指在运行时再给对象添加函数)
编译时语言在编译期进行函数决议
问:[obj foo]与objc_msgSend()函数之间有什么关系?
[obj foo]编译后,就转换为了objc_msgSend()类型
问:runtime如何通过selector找到对应的IMP的?
其实就是objc_msgSend()的消息传递流程
问:能否向编译后的类中增加实例变量?
实例变量就是ivar
(成员变量 = 实例变量 + 基本数据类型的变量)
不能
可以向动态添加的类中增加实例变量
只需在注册之前加就可以