数据结构
- objc_object
objc_object结构体包含内容
- isa_t (union):
① 指针型isa
:isa
的值代表Class的地址
;
② 非指针型isa
:isa
的部分值代表Class地址
(在寻址过程中,可能30-40位就能代表Class,为了避免内存浪费,多出来的位就存放了一些内存管理相关的内容)
isa_t (union)- isa指向
① 关于对象(objc_object
),其指向类对象(objc_class
),如果通过对象调用实例方法,是通过objc_object
中的isa
指针到objc_class
对象中去查找;
② 关于类对象(objc_class
),其指向元类对象(MetaClass
),如果通过类调用类方法,实际上是通过objc_class
中的isa指针,到MetaClass
对象中去查找
- objc_class
objc_class
Class
是一个类对象,等同于objc_class
,继承自objc_object
- cache_t
① 用于快速查找方法执行函数
② 是可增量扩展的哈希表结构
③ 是局部性原理的最佳应用- class_data_bits_t
①class_data_bits_t
主要是对class_rw_t
的封装;
②class_rw_t
代表了类相关的读写信息(存储分类中的一些内容如 方法method_array_t methods
、属性property_array_t properties
、协议protocol_array_t protocols
等),是对class_ro_t
的封装;
如;
③class_ro_t
代表了类相关的只读信息(储存类本身一些内容如 类名name
,变量ivar_list_t * ivars
,属性property_list_t
,协议protocol_list_t
,方法method_list_t
class_rw_t
class_ro_t
- method_t
method_t
结构体代表了一个方法的所有内容
method_t
Type Encodings
- const char* types;
① v 代表返回值是void
类型;
② @ 代表第一个参数类型是id
,即消息的接受者,如self
([self init]
);
③ : 代表第二个参数是SEL
类型的,即@selector()
,如@selector(init)
;
types
type Encodings
- 整体数据结构
整体数据结构
对象、类对象、元类对象
- 什么是
类对象
,元类对象
(类(Class)和对象(id)以及方法(SEL))
① 类对象储存实例方法列表
等信息
② 元类对象存储类方法列表
等信息
消息传递 objc_msgSend
- 消息传递流程
对象接收到一个消息时,首先从方法缓存列表里寻找,如果找到了,进行函数调用,如果没有找到,则在当前类方法列表中寻找,找到了则进行函数调用,没找到就会逐级到父类的缓存列表、方法列表中寻找,找到了进行函数调用,如果到最上层还没找到则进入消息转发机制
消息传递流程图例
- void objc_msgSend(void / id self, SEL op, ... / )
消息传递转换成函数调用(这一步骤实际上是由编译器上做的)
objc_msgSend- void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
struct objc_super{ //specifies an instance of a class __unsafe_unretained id receiver;//实际上就是当前对象 }
objc_msgSendSuper下面是一个关于
[self class]
和[super class]
的例子@interface ClassA : NSObject @end @implementation ClassA - (instancetype)init { self = [super init]; if (self) { NSLog(@"%@", NSStringFromClass([self class])); NSLog(@"%@", NSStringFromClass([super class])); } return self; } @end
输出结果为:
2020-04-10 23:04:46.845874+0800 test[12339:2077774] ClassA 2020-04-10 23:04:46.846015+0800 test[12339:2077774] ClassA
①
[self class]
被编译器转成objc_msgSend(self,@selector(class))
的函数调用;
②[super class]
被编译器转成objc_msgSendSuper(super, @selector(class))
的函数调用;
③super
是一个编译器关键字
,在编译器中会转换成struct objc_super *
结构体指针;
④objc_super
中的__unsafe_unretained id receiver
实际上就是当前对象
,所以[super class]
就相当于[self class]
;
⑤ 这就是两种调用输出值都为ClassA
的原因。
- 缓存方法查找(深入理解Objective-C:方法缓存)
缓存方法的存储使用了散列(hash)表,因为散列表检索起来更快,通过hash算法,查找目标函数指针- 当前类中方法查找
① 对于已经排序好的列表,采用二分查找算法查找方法对应的执行函数;
② 对于没有排序的列表,采用一般遍历查找方法对应的执行函数- 父类逐级查找
父类逐级查找流程- 消息转发(iOS消息转发机制实例、 iOS 消息转发机制)
消息转发流程简图- Method-Swizzling(方法混淆)
交换两个方法的实现+ (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method method1 = class_getInstanceMethod([self class], NSSelectorFromString(@"touchesShouldCancelInContentView:")); Method method2 = class_getInstanceMethod([self class], @selector(touchesShouldCancelInContentViewSwizzle:)); method_exchangeImplementations(method1, method2); }); } - (BOOL)touchesShouldCancelInContentViewSwizzle:(UIView *)view{ [self touchesShouldCancelInContentViewSwizzle:view]; return YES; }
Method-Swizzling
动态添加方法 (iOS performSelector 及相关方法)
performSelector:
响应了OC语言的动态性:延迟到运行时才绑定方法。
当我们在使用以下方法时:[self performSelector:@selector(sureTestMethod)]; [self performSelector:@selector(sureTestMethod) withObject:params]; [self performSelector:@selector(sureTestMethod) withObject:params withObject:params2];
编译阶段并不会去检查方法是否有效存在,只会给出警告:
Undeclared selector ''
performSelector :
可以同GCD、NSThread和NSOperation一样,在异步线程线程执行
① performSelectorInBackground 开启新的线程在后台执行test方法
② performSelector:onThread:在指定线程执行
动态方法解析
@dynamic
不需要编译器来为我们生成getter
、setter
方法,这样声明的属性,即使你没有实现setter与getter方法,编译时,是不会报错的;但是当你使用到setter与getter方法时(运行时),就会报错,所以就需要由我们来在运行时动态添加;
- 动态运行时语言将函数决议推迟到运行时(运行时来确定方法的具体实现函数)
- 编译时语言在编译期进行函数决议(编译时来确定方法的具体实现函数)
思考
①
[obj foo]
和objc_msgSend()
函数之间有什么关系?
[obj foo]
编译器处理后就变成了objc_msgSend()
的函数调用,然后开始了runtime
的消息传递过程
。
②runtime
如何通过Selector
找到对应IMP
的地址的?
先查找当前类方法缓存
,再查找当前类的方法列表
,左后逐级查找父类的缓存及方法列表
③ 能否向编译后的类中增加实例变量?(注意与关联对象的区分)
不可以的,class_addIvar()
方法只能为动态添加的类添加实例方法,因为编译后的类已经注册在runtime中,类结构体中的ivar_list_t实例变量的链表和instance_size实例变量的内存大小已经确定
④ 能否向动态添加的类中增加实例变量?(动态添加类的示例)
可以的Class MyClass = objc_allocateClassPair([NSObject class], "MyClass", 0); BOOL isSuccess = class_addIvar(MyClass, "test", sizeof(NSString *), 0, "@");
⑤ 编译时语言与OC这种运行时语言有什么区别?
- 动态运行时语言将函数决议推迟到运行时(运行时来确定方法的具体实现函数)
- 编译时语言在编译期进行函数决议(编译时来确定方法的具体实现函数)
⑥ 消息传递与函数调用有什么区别?
C语言的函数调用在编译的时候决定调用那个函数。编译完之后直接顺序执行。
OC的消息发送是属于动态调用过程。在编译的时候决不能决定真正调用那个函数(在编译阶段,oc可以调用任何函数,而c语言在编译阶段会报错)⑦ 当我们调用一个没有实现的方法的时候,系统如何为我们实现消息转发过程的?
//首先调用: + (BOOL)resolveInstanceMethod:(SEL)sel;//实例方法 + (BOOL)resolveClassMethod:(SEL)sel;//类方法 #pragma mark - //上述返回NO后调用: - (id)forwardingTargetForSelector:(SEL)aSelector; #pragma mark - //上述方法返回nil继续调用: - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector; //上诉方法返回NSMethodSignature *后调用下面方法 - (void)forwardInvocation:(NSInvocation *)anInvocation;