- 官方文档:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html
看不懂英文就用chrome浏览器看(可以将网页翻译成中文) - 参考文档:
http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/
http://yulingtianxia.com/blog/2016/06/15/Objective-C-Message-Sending-and-Forwarding/ - runtime源码地址
https://opensource.apple.com/tarballs/objc4/
https://opensource.apple.com/source/objc4/
一般使用
- 通过字符串实现创建对象或者对方法的调用
- 组件化:总结起来就是,组件通过中间件通信,中间件通过 runtime 接口解耦,通过 target-action 简化写法,通过 category 感官上分离组件接口代码。这里可以看到这个实现的
简介
Objc是一门动态语言,所以它总是想办法把一些决定工作从编译连接推迟到运行时。也就是说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的代码。这就是 Objective-C Runtime 系统存在的意义
objc_msgSend
id objc_msgSend ( id self, SEL op, ... );
其实编译器会根据情况在objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, 或 objc_msgSendSuper_stret
四个方法中选择一个来调用。如果消息是传递给超类,那么会调用名字带有”Super”的函数;如果消息返回值是数据结构而不是简单值时,那么会调用名字带有”stret”的函数。排列组合正好四个方法。
调用objc_msgSend警告处理
-
Implicitly declaring library function
需添加头文件#import <objc/message.h>
-
Too many arguments to function call,expected 0,have x.
解决方法Build Setting->App LLVM 6.1-Preprocessing->Enable Strict Checking of objc_msgSend Call
设置成No。
参数解析
-
id self
接受消息的对象是类的就传Class类对象,是实例对象就传实例对象 -
SEL op
选择子 -
...
后面跟随的是参数,按顺序填写
消息发送步骤:
- 检测这个
selector
是不是要忽略的。比如 Mac OS X 开发,有了垃圾回收就不理会retain
,release
这些函数了。 - 检测这个 target 是不是 nil 对象。ObjC 的特性是允许对一个 nil 对象执行任何一个方法不会 Crash,因为会被忽略掉。
- 如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了找得到就跳到对应的函数去执行。
- 如果 cache 找不到就找一下方法分发表。
- 如果分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止。
- 如果还找不到就要开始进入动态方法解析了,后面会提到。
Class
ObjC 类本身同时也是一个对象,为了处理类和对象的关系,runtime 库创建了一种叫做元类 (Meta Class) 的东西,类对象所属类型就叫做元类,它用来表述类对象本身所具备的元数据。类方法就定义于此处,因为这些方法可以理解成类对象的实例方法。
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();
}
...
}
- cache中存储了指针和IMP(函数指针)的键值对,方法执行的时候会先在缓存中查找IMP。
此外,objc_class结构体中还包含方法链表、协议链表和成员变量链表等数据
Associated Objects
向对象动态添加变量。涉及到的函数有以下三个
void objc_setAssociatedObject ( id object, const void *key, id value, objc_AssociationPolicy policy );
id objc_getAssociatedObject ( id object, const void *key );
void objc_removeAssociatedObjects ( id object );
Method Swizzling
可用于处理闪退问题,如数组越界
Method Swizzling相关函数介绍
//获取通过SEL获取一个方法
class_getInstanceMethod
//获取一个方法的实现
method_getImplementation
//获取一个OC实现的编码类型
method_getTypeEncoding
//給方法添加实现
class_addMethod
//用一个方法的实现替换另一个方法的实现
class_replaceMethod
//交换两个方法的实现
method_exchangeImplementations
消息转发需要我们了解并且能更改对应类的源代码。当我们无法触碰到某个类的源代码,却想更改这个类某个方法的实现时,该怎么办呢?可能继承类并重写方法是一种想法,但是有时无法达到目的。这时候看可以考虑使用 Method Swizzling
理解 selector, method, implementation 这三个概念之间关系的最好方式是:在运行时,类(Class)维护了一个消息分发列表来解决消息的正确发送。每一个消息列表的入口是一个方法(Method),这个方法映射了一对键值对,其中键值是这个方法的名字 selector(SEL),值是指向这个方法实现的函数指针 implementation(IMP)。 Method swizzling 修改了类的消息分发列表使得已经存在的 selector 映射了另一个实现 implementation,同时重命名了原生方法的实现为一个新的 selector。
我们可以将Method Swizzling代码写到任何地方,但是只有在这段Method Swilzzling代码执行完毕之后互换才起作用。
Swizzling 应该在+load方法中实现,因为+load是在一个类最开始加载时调用。
+ load方法加载顺序
- A class’s +load method is called after all of its superclasses’ +load methods.
- A category +load method is called after the class’s own +load method.
- 先调用父类和子类的 class’s +load method再调用父类和子类category +load method
#import <objc/runtime.h>
@implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class aClass = [self class];
// When swizzling a class method, use the following:
// Class aClass = object_getClass((id)self);
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);
//如果实现了viewWillAppear:方法,didAddMethod为NO,否则是YES
BOOL didAddMethod =
class_addMethod(aClass,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(aClass,
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
Method Swizzling方法封装
+ (void)swizzleMethods:(Class)class originalSelector:(SEL)origSel swizzledSelector:(SEL)swizSel {
Method origMethod = class_getInstanceMethod(class, origSel);
Method swizMethod = class_getInstanceMethod(class, swizSel);
BOOL didAddMethod = class_addMethod(class, origSel, method_getImplementation(swizMethod), method_getTypeEncoding(swizMethod));
if (didAddMethod) {
class_replaceMethod(class, swizSel, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
} else {
method_exchangeImplementations(origMethod, swizMethod);
}
}
+ (void)swizzleMethods:(Class)class originalSelector:(SEL)origSel swizzledSelector:(SEL)swizSel {
Method origMethod = class_getInstanceMethod(class, origSel);
Method swizMethod = class_getInstanceMethod(class, swizSel);
method_exchangeImplementations(origMethod, swizMethod);
}
当主类没有实现origin方法的时候,使用该种封装会造成调用父类的swiz方法的时候unrecognized selector,因为父类的selector指向了子类的IMP
Method m = class_getInstanceMethod([UIApplication class], @selector(openURL:));
class_addMethod([UIApplication class], @selector(hook_openURL:), method_getImplementation(m), method_getTypeEncoding(m));
method_setImplementation(m, class_getMethodImplementation([self class], @selector(hook_openURL:)));
动态方法解析和消息转发
转发不仅能模拟多继承,也能使轻量级对象代表重量级对象。弱小的女人背后是强大的男人,毕竟女人遇到难题都把它们转发给男人来做了
动态方法解析
- 当 Runtime 系统在Cache和方法分发表中(包括超类)找不到要执行的方法时,Runtime会调用
resolveInstanceMethod:
或resolveClassMethod:
来给程序员一次动态添加方法实现的机会。我们需要用class_addMethod
函数完成向特定类添加特定方法实现的操作:
如果你想让该方法选择器被传送到转发机制,那么就让resolveInstanceMethod:
或resolveClassMethod:
返回NO。
#import <Foundation/Foundation.h>
@interface Student : NSObject
+ (void)learnClass:(NSString *) string;
- (void)goToSchool:(NSString *) name;
@end
-----------------------------------
#import "Student.h"
#import <objc/runtime.h>
@implementation Student
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(learnClass:)) {
class_addMethod(object_getClass(self), sel, class_getMethodImplementation(object_getClass(self), @selector(myClassMethod:)), "v@:");
return YES;
}
return [class_getSuperclass(self) resolveClassMethod:sel];
}
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(goToSchool:)) {
class_addMethod([self class], aSEL, class_getMethodImplementation([self class], @selector(myInstanceMethod:)), "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
+ (void)myClassMethod:(NSString *)string {
NSLog(@"myClassMethod = %@", string);
}
- (void)myInstanceMethod:(NSString *)string {
NSLog(@"myInstanceMethod = %@", string);
}
@end
重定向
- 在消息转发机制执行前,Runtime 系统会再给我们一次偷梁换柱的机会,即通过重载
- (id)forwardingTargetForSelector:(SEL)aSelector
方法替换消息的接受者为其他对象。如果此方法返回nil或self,则会进入消息转发机制(forwardInvocation:
);否则将向返回的对象重新发送消息。:
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if(aSelector == @selector(xxx)) {
return NSClassFromString(@"Class name");
}
return [super forwardingTargetForSelector:aSelector];
}
转发
- 当动态方法解析不作处理返回NO时,消息转发机制会被触发。在这时
forwardInvocation:
方法会被执行,我们可以重写这个方法来定义我们的转发逻辑:
该消息的唯一参数是个NSInvocation类型的对象——该对象封装了原始的消息和消息的参数
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}