一、引言
Objective-C总是尽可能把事情从编译时期和链接时期,推迟到运行时期(Runtime)来动态执行。这就意味着Objective-C不仅需要编译器,还需要一套runtime system来执行编译后的代码。
二、与Runtime交互
苹果提供了三种方式与Runtime进行交互:
1、Objective-C源码(隐式交互)
在大多数情况下,只需要写好并编译Objective-C源码,runtime system就会自动在幕后默默为我们工作。
2、NSObject中的方法
3、直接调用Runtime函数
三、消息机制
引言中为什么说是动态执行呢?这需要深入理解Objective-C的消息(Messaging)机制。
[receiver message],表示给接收者发送一条消息。
clang -rewrite-objc demo.m
利用clang重写它,得到如下C代码:
((void (*)(id, SEL, NSString *, NSInteger))(void *)objc_msgSend)((id)receiver, sel_registerName("message:arg2:"), (NSString *)arg1, (NSInteger)arg2);
可以看到,这个表达式会被编译器转成函数调用:
1)objc_msgSend(receiver, selector)
如果消息中包含参数:
2)objc_msgSend(receiver, selector, arg1, arg2, ...)
在编译时期,只确定要向receiver发送message(即知道selector及相关参数),但是receiver如何响应这条message,就要看运行时期动态绑定到的selector对应的方法实现了。
1、动态绑定
在理解动态绑定之前,先要来看看底层一些重要结构体。
// id类型其实是对象结构体指针 typedef struct objc_object *id;
// 对象结构体 struct objc_object { Class isa; // 指向对象的类。通过isa指针,对象可以访问它的类 };
// Class类型其实是类结构体指针 typedef struct objc_class *Class;
// 类结构体 struct objc_class { Class isa; // 指向类的元类,Objective-C中,类也是一个对象 Class super_class; // 指向父类 const char *name; // 类名 struct objc_method_list **methodLists; // 方法链表,表项是struct objc_method * struct objc_cache *cache; // 缓存调用过的方法,加速方法查找 struct objc_protocol_list *protocols; // 协议列表 };
typedef struct objc_selector *SEL;
// 方法结构体 struct objc_method { SEL method_name; // selector其实就是方法实现的唯一标识 char *method_types; // 入参类型和返回值类型 IMP method_imp; // 方法实现 };
// IMP(implementation),其实就是一个函数指针,指向方法实现的地址 typedef id (*IMP)(id, SEL, ...);
当一条消息发送给一个对象时,objc_msgSend函数会通过对象的isa指针去对象的类结构体的方法列表里查找IMP;如果找不到,就顺着类的super_class去父类的方法列表里查找,以此类推,直到NSObject类,还找不到就会返回_objc_msgForward(默认IMP),由此进入消息转发流程。一旦找到,objc_msgSend函数就把receiver结构体、selector以及方法参数列表传给方法的具体实现。为了加快查找速度,runtime会缓存调用过的方法。示意图如下:
2、使用隐含参数
3、获取方法地址
规避动态绑定的唯一套路是拿到方法的地址,然后当成函数那样调用。但是这种做法的适用场景很少,除非一个方法在短时间内被大量调用,为了减少消息发送的开销才这么干。
4、动态方法解析
@dynamic propertyName;
instrumentObjcMessageSends(YES);
5、消息转发
当查找不到selector时,runtime会给receiver对象发送一条forwardInvocation消息,即进入消息转发流程。forwardInvocation消息的参数是一个NSInvocation对象,该对象封装了动态绑定时的原始消息及其参数。我们可以实现forwardInvocation方法来响应原始消息。forwardInvocation,顾名思义,即“转发调用”,通常用于转发原始消息给其他对象。示意图如下:
resolveInstanceMethod:Dynamically provides an implementation for a given selector for an instance method.
forwardingTargetForSelector:Returns the object to which unrecognized messages should first be directed.
methodSignatureForSelector:Returns an NSMethodSignature
object that contains a description of the method identified by a given selector.
doesNotRecognizeSelector:Handles messages the receiver doesn’t recognize.
unrecognized selector sent to instance xxxxxx
向nil对象发消息
objc_msgSendSuper
obj_msgSend的实现是用汇编写的
声明的属性
当编译器遇到属性声明,
四、Method Swizzling(方法搅拌)
Method Swizzling,利用category来实现在运行时动态改变selector对应的IMP。这有什么用呢?拦截、解耦。
#import <objc/runtime.h> @implementation UIViewController (Tracking) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL originalSelector = @selector(viewWillAppear:); SEL swizzledSelector = @selector(xxx_viewWillAppear:); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); // When swizzling a class method, use the following: // Class class = object_getClass((id)self); // ... // Method originalMethod = class_getClassMethod(class, originalSelector); // Method swizzledMethod = class_getClassMethod(class, swizzledSelector); BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); } #pragma mark - Method Swizzling - (void)xxx_viewWillAppear:(BOOL)animated { [self xxx_viewWillAppear:animated]; NSLog(@"viewWillAppear: %@", self); } @end
// 参考链接:http://limboy.me/tech/2018/03/04/ios-lightweight-hotfix.html?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
参考链接:
1)Runtime官方文档: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html
2)NSObject官方文档:https://developer.apple.com/documentation/objectivec/nsobject?language=objc